Security · 2026-03-30
Cross-Referencing SIM Signals with Verifiable Credentials
By J. W. Bouckaert
The gap between "who passed KYC" and "who is holding the phone"
Identity verification platforms solve two problems separately. The first is legal identity: can this person prove they are who they claim to be? This is the domain of Know Your Customer (KYC) processes, document verification and, more and more, W3C Verifiable Credentials (VCs) that encode the result of that verification as a portable, signed assertion.
The second problem is device possession: is this person currently in control of the phone number or device on their account? This is the domain of carrier signals: SIM swap checks, number portability checks and GSMA Open Gateway APIs that report whether the SIM behind a number changed recently.
Each signal answers one question.
A Verifiable Credential shows that a person passed KYC at some point in the past. It does not show they still control the device on their account. A carrier signal shows the SIM is stable now. It does not show the person holding the device is the person who passed KYC. Account takeover, synthetic identity fraud and SIM-swap-enabled credential theft happen in the space between the two.
A cross-reference records that the two signals were checked together for one entity, without storing the identifiers themselves.
Deterministic cross-references
A deterministic cross-reference maps a carrier-signal identifier to a Verifiable Credential subject identifier and produces a composite entity profile hash for the pairing.
The operation has four steps:
- The carrier signal identifier (for example, a SIM check event ID) is keyed-hashed with HMAC-SHA-256 under a server-held pepper
- The VC subject identifier (for example, the
credentialSubject.idof a W3C VC) is keyed-hashed with HMAC-SHA-256 under a server-held pepper - Both digests are combined with the carrier name and credential type into a composite string
- The composite string is SHA-256 hashed to produce the entity profile hash. Its inputs are already keyed digests, so this outer hash needs no key of its own
The same SIM signal and VC subject always produce the same entity profile hash. The hash alone does not reveal either identifier, because both were keyed under the server-held pepper before the composite was formed.
This is the zero-PII architecture applied to cross-referencing: the system records that the binding exists without storing who the person is.
Temporal proximity
The caller can supply the time of the SIM check and the time of the VC verification. The gap between them sets the match score:
| Event times | Match score | Match method |
|---|---|---|
| Gap of 5 minutes or less | 1.00 | exact |
| Gap of 1 hour or less | 0.95 | temporal |
| Gap of 24 hours or less | 0.85 | fuzzy |
| Gap over 24 hours | 0.70 | fuzzy |
| No times supplied | 0.80 | partial |
| Times that cannot be parsed | 0.60 | partial |
The times come from the caller, so the score reflects what the caller reported. A score of 1.00 means the caller reported both checks within five minutes of each other, which is consistent with a single session.
A binding with a long gap can still be useful as a starting point. Downstream systems should weight the score when making authorization decisions and can ask for a fresh check.
From signal to binding
The cross-reference API has five actions: establish, verify, bind passkey, revoke and list. Cross-references are an Enterprise feature, and establish, bind passkey and revoke require a tenant admin.
Establishing a cross-reference
The establish action takes:
- SIM signal identifier: the carrier check identifier, keyed-hashed on receipt
- VC subject identifier: the credential subject identifier, keyed-hashed on receipt
- Carrier metadata: the carrier name and signal type (for example,
T-Mobile,sim_swap_check) - Credential metadata: the credential type and issuer (for example,
IdentityCredential,did:web:issuer.example.com) - Event times (optional): when the SIM check and the VC verification happened
Both identifiers are keyed-hashed with HMAC-SHA-256 under a server-held pepper before the database insert. The raw values exist only in request memory and never reach storage.
The system then computes the entity profile hash from both digests plus the carrier and credential type. A cross-reference expires 90 days after it is established.
Duplicate detection
Before inserting a new cross-reference, the system checks for an active binding with the same SIM digest and VC digest in the same tenant. If one exists, establish returns 409 Conflict with the existing cross-reference ID, so each SIM-to-VC pairing appears once per tenant.
Hybrid post-quantum signatures
For tenants with post-quantum signing enabled, the cross-reference is signed with a hybrid signature: an HMAC-SHA-256 layer and a NIST FIPS 204 ML-DSA-65 signature. The signed payload is the entity profile hash, both constituent digests and the match score.
Forging the bundle requires breaking both layers. The bundle is stored as JSON with both signatures, the key fingerprint and a quantum-seeded nonce. If signing fails, the cross-reference is stored without a signature.
Verification without the identifiers
The verify action accepts either an entity profile hash or the raw SIM and VC identifiers, which are keyed-hashed server-side before the lookup. It returns:
- Whether an active cross-reference exists
- The match score and method
- The carrier, credential type and issuer
- Whether a passkey has been bound to it
- The verification and expiry times
- The post-quantum verification result, when the row is signed
Verify never returns the SIM digest or the VC digest. The caller learns that a binding exists and its score, and cannot extract the constituent identifiers from the response.
Expiry
When verify finds an expired cross-reference, it deactivates it and returns verified: false with the reason Cross-reference has expired. A cleanup function also deactivates expired cross-references in bulk. See SOC 2 readiness on credential lifecycle.
Signature check
When a cross-reference carries a hybrid signature, verify rebuilds the signed payload from the stored row and checks both signatures. Both must pass for pqc_verified to be true.
If the bundle is malformed or either signature fails, pqc_verified is false. The cross-reference still verifies; the flag tells downstream systems that its integrity could not be confirmed. A row stored without a signature returns pqc_verified: null.
Binding to a passkey
The bind action links a passkey credential ID to an existing cross-reference. The passkey must be registered for the same tenant. The result is a three-part record:
- Carrier layer: a SIM check for the number
- Legal identity layer: a Verifiable Credential subject that passed KYC
- Device layer: a passkey registered for the tenant
Verify reports passkey_bound. It does not run a passkey ceremony; the relying party runs the ceremony and checks the cross-reference in the same flow. This addresses the limitation discussed in Passkeys Need a Carrier Signal Layer: a passkey shows device possession and user verification, but it cannot detect a SIM swap that happened moments before the ceremony.
Revocation
A tenant admin revokes a cross-reference with one API call. Revocation sets is_active to false and records the revocation time.
- Immediate: the next verify call returns no active binding
- Auditable: each revocation writes an entry to the audit log with the actor and the cross-reference ID
- Final: there is no reactivate action; a new establish creates a fresh binding
Cross-references stop verifying in three cases:
- An admin revokes it
- It passes its expiry time
- A playbook runs the quarantine action, which deactivates all of the tenant's cross-references
PasskeyBridge does not revoke a cross-reference on its own when a SIM swap signal or a VC status change arrives. A tenant that wants that response configures a playbook or calls revoke from its own system.
Carrier signal quality
A cross-reference is only as strong as the carrier check behind it. A PasskeyBridge carrier lookup asks one provider per request, so the binding inherits that provider's coverage and fields. The signal type and carrier stored on the cross-reference record which check it was.
Tenant isolation
Cross-references are tenant-scoped. Every row carries a tenant_id. The cross-reference function filters each query by the caller's tenant, and row-level security on the table lets only that tenant's admins read rows directly. Client roles have no write access.
This is the database-layer isolation pattern that SOC 2 auditors test, combined with tenant filters in server code that runs with elevated access.
Consequences for identity assurance
A cross-reference records that two independent identity signals, one from the telecom layer and one from the legal identity layer, were checked for the same entity, without storing who that entity is.
That supports several flows:
- Account recovery: before re-issuing credentials, check that the person requesting recovery passes a fresh SIM check and holds a valid VC. The cross-reference ties both to the earlier binding without asking the user to resubmit documents.
- Step-up authentication: for a high-value transaction, check the cross-reference instead of prompting for more personal information.
- Regulatory compliance: GDPR Article 25 requires data protection by design. Storing keyed digests in place of identifiers is one way to meet it. Under eIDAS 2.0, wallet credentials can supply the VC side.
- Fraud detection: a verify call with the current SIM identifier that finds no active binding for a VC subject that had one is a sign the SIM may have changed. The tenant can respond through its own systems or playbooks without knowing who the user is.
A cryptographic binding primitive
Most identity platforms treat KYC verification and device checks as separate concerns. They answer "did this person pass KYC?" and "is this SIM stable?" separately and leave the correlation to the application, where it is often skipped or done with reversible identifiers.
A deterministic cross-reference makes the binding its own record. The SIM signal and VC subject are keyed-hashed, combined and stored as an entity profile hash, signed when the tenant has post-quantum signing enabled. The binding carries a time-based score, can be tied to a passkey and is revoked with one API call.