Architecture & How It Works
Self Connect: Architecture & How It Works
Protocol Architecture
Self Connect uses a federated architecture where multiple independent issuers can register attestations that map obfuscated identifiers to blockchain addresses.
Core Components
┌─────────────────┐
│ User/Client │
└────────┬────────┘
│
├──────────────┐
│ │
v v
┌─────────────┐ ┌─────────────┐
│ ODIS │ │ Issuer │
│ (Privacy) │ │(Verification)│
└──────┬──────┘ └──────┬──────┘
│ │
└────────┬───────┘
│
v
┌──────────────────────┐
│ FederatedAttestations│
│ Smart Contract │
└──────────────────────┘FederatedAttestations Smart Contract
The FederatedAttestations contract is the on-chain registry for all attestation mappings. It stores attestations in a structure that allows:
Registration: Issuers register mappings between obfuscated identifiers and addresses
Lookup: Anyone can query attestations for a given obfuscated identifier
Multi-Issuer Support: Attestations are organized by issuer, allowing trust-based filtering
Batch Operations: Multiple attestations can be queried simultaneously
Key Functions:
// Register an attestation as an issuer
function registerAttestationAsIssuer(
bytes32 identifier,
address account,
uint64 issuedOn
) external
// Lookup attestations for an identifier across multiple issuers
function lookupAttestations(
bytes32 identifier,
address[] calldata trustedIssuers
) external view returns (
uint256[] memory countsPerIssuer,
address[] memory accounts,
address[] memory signers,
uint64[] memory issuedOns,
uint64[] memory publishedOns
)Contract Address:
Mainnet: View on Celoscan
Alfajores Testnet: View on Celoscan
Issuer Ecosystem
Issuers are independent entities that:
Verify user ownership of identifiers through their chosen method
Request obfuscated identifiers from ODIS
Register attestations to the FederatedAttestations contract
Maintain ODIS quota for continued operations
Applications choose which issuers to trust based on:
Verification rigor
Reputation
Use case alignment
Uptime and reliability
ODIS (Oblivious Decentralized Identifier Service)
ODIS is the privacy-preserving component of Self Connect that ensures identifiers cannot be reverse-engineered from on-chain data.
What is ODIS
ODIS implements a rate-limited Oblivious Pseudorandom Function (OPRF) that allows users to compute a limited number of hashes without letting the service see the data being hashed.
Key Properties:
Oblivious: ODIS operators cannot see the plaintext identifiers
Decentralized: No single party can compute the pepper alone
Rate-Limited: Prevents rainbow table attacks through quota system
Deterministic: Same input always produces same output
Privacy Guarantees
ODIS provides strong privacy guarantees through its design:
1. Blinding Process
When a client queries ODIS:
1. Client blinds the identifier locally using a secret one-time key
2. Blinded value is sent to ODIS operators
3. Operators compute the OPRF on the hidden input
4. Operators return the blinded result
5. Client unblinds the result to get the final pepperThis process ensures that:
ODIS operators never see the plaintext identifier
Even if all operators are compromised, user privacy is maintained
No targeted censorship is possible
2. Threshold Cryptography
ODIS uses a (k, m) threshold signature scheme:
m: Total number of ODIS operators
k: Minimum number of signatures required
Security Properties:
If fewer than k operators are compromised: Attackers cannot compute unauthorized peppers
If at least k operators are honest: Service remains available for legitimate users
Example Configuration:
7 operators (m=7)
5 required signatures (k=5)
Security: Need 5 compromised operators to break privacy
Availability: Can tolerate 2 operators being offline
3. Distributed Key Generation (DKG)
Before deployment, ODIS operators participated in a DKG ceremony to generate a shared secret, split across all operators. Each operator holds a key share that can be used to sign responses.
When enough signatures (≥k) are combined, they produce the unique, deterministic pepper for that identifier.
Quota System and Rate Limiting
ODIS implements a quota system to prevent rainbow table attacks while allowing legitimate usage.
Quota Factors
Quota is based on:
Payment: Pay for quota using cUSD
10 cUSD = 10,000 queries
~0.001 cUSD per query
Account-based Limits: Rate limits per account to prevent abuse
How Quota Works
// Check current quota
const { remainingQuota } = await OdisUtils.Quota.getPnpQuotaStatus(
issuerAddress,
authSigner,
serviceContext
);
// Purchase quota if needed
if (remainingQuota < 1) {
const stableTokenContract = await kit.contracts.getStableToken();
const odisPaymentsContract = await kit.contracts.getOdisPayments();
const ONE_CENT_CUSD_WEI = 10000000000000000;
await stableTokenContract
.increaseAllowance(odisPaymentsContract.address, ONE_CENT_CUSD_WEI)
.sendAndWaitForReceipt();
await odisPaymentsContract
.payInCUSD(issuerAddress, ONE_CENT_CUSD_WEI)
.sendAndWaitForReceipt();
}Rate Limiting Strategy
The quota system makes it prohibitively expensive to:
Scrape large quantities of identifiers
Build rainbow tables
Perform mass surveillance
While still allowing:
Normal user flows
Legitimate application usage
Reasonable issuer operations
Key Rotation
If an operator's key is leaked or compromised, ODIS can perform key rotation:
New DKG ceremony with at least k old keys participating
New keys generated for all operators (including new ones)
Old keys destroyed after successful rotation
Public verification key remains unchanged
This allows:
Adding new operators
Removing compromised operators
Changing threshold values (k, m)
Maintaining service continuity
Identifier Obfuscation Process
The obfuscation process transforms a plaintext identifier into a privacy-preserving on-chain identifier.
Step-by-Step Process
1. Format the Identifier
Combine the identifier prefix with the plaintext identifier:
formatted = "{prefix}://{plaintextIdentifier}"Example:
Phone: "PHONE_NUMBER://+12345678901"
Twitter: "twitter://@alice"2. Hash the Formatted Identifier
hashedIdentifier = sha3(formatted)3. Blind the Hash
The client generates a random blinding factor and blinds the hash:
blindedHash = blind(hashedIdentifier, blindingFactor)This step ensures ODIS cannot see the actual identifier.
4. Query ODIS
Send the blinded hash to ODIS operators:
const { obfuscatedIdentifier } = await OdisUtils.Identifier.getObfuscatedIdentifier(
plaintextIdentifier,
identifierPrefix,
issuerAddress,
authSigner,
serviceContext
);5. ODIS Processing
Each ODIS operator:
Receives the blinded hash
Computes a partial signature using their key share
Returns the blinded partial signature
6. Combine Signatures
When k signatures are received:
Signatures are combined into a full signature
This is the "blinded pepper signature"
7. Unblind the Signature
The client unblinds the signature:
unblinedSignature = unblind(blindedPepperSignature, blindingFactor)8. Generate the Pepper
Extract the pepper from the unblinded signature:
pepper = first13Chars(sha256(unblindedSignature))9. Create Obfuscated Identifier
Combine the hashed identifier with the pepper:
obfuscatedIdentifier = sha3(hashedIdentifier + "__" + pepper)Final Formula:
obfuscatedIdentifier = sha3(sha3("{prefix}://{plaintext}") + "__" + pepper)Verification
Results from ODIS can be verified against the service's public key, which is shared with users through the client library.
Registration & Lookup Flow
Registration Flow
The process of creating an attestation mapping:
┌──────┐ ┌────────┐ ┌──────┐ ┌──────────────┐
│ User │ │ Issuer │ │ ODIS │ │ Blockchain │
└───┬──┘ └───┬────┘ └───┬──┘ └──────┬───────┘
│ │ │ │
│ 1. Request Verification│ │ │
├──────────────────────>│ │ │
│ │ │ │
│ 2. Verify Ownership │ │ │
│ (SMS, OAuth, etc.) │ │ │
│<─────────────────────>│ │ │
│ │ │ │
│ │ 3. Query for Pepper │ │
│ ├──────────────────────>│ │
│ │ │ │
│ │ 4. Return Pepper │ │
│ │<──────────────────────┤ │
│ │ │ │
│ │ 5. Register Attestation │
│ ├───────────────────────────────────────────────>│
│ │ │ │
│ │ 6. Attestation Registered │
│ │<───────────────────────────────────────────────┤
│ │ │ │
│ 7. Confirmation │ │ │
│<──────────────────────┤ │ │Detailed Steps:
User Requests Verification
User provides their identifier and wallet address to an issuer
User indicates they want to create an attestation
Issuer Verifies Ownership
Phone: Send SMS with verification code
Twitter: OAuth flow
Email: Verification link
Custom: Any verification method the issuer chooses
Issuer Queries ODIS
Issuer sends blinded identifier to ODIS
Consumes issuer's ODIS quota
Receives pepper for obfuscation
Issuer Computes Obfuscated Identifier
Combines hashed identifier with pepper
Creates the final obfuscated identifier
Register Attestation On-Chain
await federatedAttestationsContract .registerAttestationAsIssuer( obfuscatedIdentifier, userAccountAddress, attestationVerifiedTime ) .send();Gas Payment Options
Issuer Pays: Issuer executes transaction with their gas
User Pays: Issuer signs attestation, user submits transaction
Lookup Flow
The process of finding addresses from identifiers:
┌─────────────┐ ┌──────┐ ┌──────────────┐
│ Application │ │ ODIS │ │ Blockchain │
└──────┬──────┘ └───┬──┘ └──────┬───────┘
│ │ │
│ 1. Query for Pepper │ │
├────────────────────>│ │
│ │ │
│ 2. Return Pepper │ │
│<────────────────────┤ │
│ │ │
│ 3. Lookup Attestations │
├─────────────────────────────────────────>│
│ │ │
│ 4. Return Addresses │ │
│<─────────────────────────────────────────┤Detailed Steps:
Get Obfuscated Identifier
const { obfuscatedIdentifier } = await OdisUtils.Identifier.getObfuscatedIdentifier( plaintextIdentifier, identifierPrefix, lookupAddress, authSigner, serviceContext );Query FederatedAttestations Contract
const attestations = await federatedAttestationsContract.lookupAttestations( obfuscatedIdentifier, [issuer1Address, issuer2Address, ...] );Process Results
// Returns arrays for each issuer const { countsPerIssuer, // Number of attestations per issuer accounts, // Account addresses signers, // Signer addresses issuedOns, // Verification timestamps publishedOns // Registration timestamps } = attestations;
Multi-Issuer Lookup
Applications can query multiple issuers simultaneously:
const trustedIssuers = [
"0x6549aF2688e07907C1b821cA44d6d65872737f05", // Kaala
"0x388612590F8cC6577F19c9b61811475Aa432CB44" // Libera
];
const attestations = await federatedAttestationsContract.lookupAttestations(
obfuscatedIdentifier,
trustedIssuers
);
// Results are ordered by issuer
// attestations.countsPerIssuer[0] = number of attestations from Kaala
// attestations.countsPerIssuer[1] = number of attestations from LiberaTrust Model
Applications must decide which issuers to trust:
Single Issuer Trust:
Trust only attestations from your own issuer
Maximum control over verification quality
Limited to your own user base
Multiple Issuer Trust:
Trust attestations from multiple issuers
Broader coverage and interoperability
Must evaluate each issuer's verification quality
Consensus-Based Trust:
Require attestations from multiple issuers
Higher confidence in verification
Reduced coverage (fewer users will have multiple attestations)
Security Considerations
Privacy Assumptions
Self Connect's privacy model assumes:
ODIS Operators: At least k operators remain honest
Rate Limiting: Quota system prevents mass scraping
Blinding: Client-side blinding is implemented correctly
No Collusion: Attackers don't control k or more ODIS operators
If Assumptions Hold:
Identifiers cannot be reverse-engineered from blockchain data
Rainbow table attacks are prohibitively expensive
User privacy is preserved
If Assumptions Fail:
If k operators are compromised: Peppers for any identifier can be computed
If rate limiting is bypassed: Rainbow tables become feasible
Sybil Resistance
Self Connect provides Sybil resistance through:
Verification Requirements: Users must prove ownership of identifiers
Issuer Quality: Applications choose issuers with strong verification
Costly Registration: ODIS quota costs make mass fake registrations expensive
Unique Identifiers: Phone numbers and verified social accounts are limited per person
Limitations:
Relies on issuer verification quality
Some identifiers (email) are easier to create in bulk
Applications must choose issuers carefully
Comparison: ASv1 vs. Self Connect
Verification
3 randomly selected validators
Issuer (flexible method)
Trust Model
Single root: Validator collective
Multiple roots: Each issuer
Verification Quality
Standardized across network
Varies by issuer
Flexibility
Phone numbers only
Any identifier type
Censorship Resistance
Validator majority
Multi-issuer selection
Scalability
Limited by validator overhead
Scales with issuer ecosystem
Best Practices
Choose Trusted Issuers
Evaluate verification methods
Check issuer reputation
Monitor issuer behavior
Implement Rate Limiting
Limit lookup frequency per user
Monitor for abnormal query patterns
Validate Results
Check timestamps for recency
Verify issuer addresses
Handle multiple attestations appropriately
Secure Key Management
Protect issuer private keys
Use hardware security modules for production
Implement key rotation procedures
Monitor ODIS Quota
Set up alerts for low quota
Implement automatic top-ups
Track quota usage patterns
Last updated