Skip to content

feat: allow binding uniqueness proofs to an existing session#855

Open
kilianglas wants to merge 4 commits into
mainfrom
kilianglas/uniqueness-session-binding
Open

feat: allow binding uniqueness proofs to an existing session#855
kilianglas wants to merge 4 commits into
mainfrom
kilianglas/uniqueness-session-binding

Conversation

@kilianglas

@kilianglas kilianglas commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Summary

Allows a Uniqueness Proof request to carry an existing session_id, cryptographically binding the proof to that session: the proof carries SessionId.commitment as its id_commitment public signal, proving in-circuit that the session and the nullifier belong to the same World ID. Requires no circuit changes, the id_commitment check in oprf_nullifier.circom is proof-type-independent and was previously always disabled (0) for uniqueness proofs.

New flow for a bound session id

  1. RP sends a ProofType::CreateSession request.
  2. Authenticator generates the session_id and responds with corresponding session proofs.
  3. RP sends a ProofType::Uniqueness request that now also carries Some(session_id)
  4. Authenticator generates a uniqueness proof as before, but now also passes session_id as a public input.

Follow-up PRs may streamline this flow s.t. the initial request is not required, but the session_id can be generated as part of the uniqueness proof instead.

Changes:

  • world-id-primitives: validate_proof_type() now allows session_id on ProofType::Uniqueness; new ProofRequest::binds_session(); validate_response() requires bound responses to echo the session_id (and still rejects a session_id on plain uniqueness responses).
  • world-id-authenticator: generate_proof() resolves (session_id, r) for binding requests from the caller-cached session_id_r_seed, validating it opens the session's commitment. A missing seed fails with the new SessionSeedRequired error.
    Note: Re-deriving r requires a session-type request, since the uniqueness request's RP signature cannot authorize the seed query. This friction may be removed in a follow-up PR.
  • Docs: short spec section on session binding, including the verifier obligation (a proof verified with the signal at 0 is valid but unbound) and the intentional-linkability caveat.

On-chain, bound proofs can already be verified via verifyProofAndSignals by passing the session's commitment as the sessionId signal; the convenience verify() entry point pins the signal to 0 and rejects bound proofs. A dedicated entry point for bound proofs may land in a follow-up PR.

@kilianglas
kilianglas requested a review from a team as a code owner July 10, 2026 08:29
@paolodamico

Copy link
Copy Markdown
Collaborator

we should update the session proofs flow doc (https://github.com/worldcoin/world-id-protocol/tree/main/docs/world-id-4-specs#session-proofs) from this

@paolodamico paolodamico left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. updating docs
  2. conceptual: I wonder if it should be generally allowed to request a session_id without the uniqueness proof. it could be a footgun for RPs because then RPs would have to immediately request a session proof to verify it's valid.
  3. let's document details on privacy implications, for example, you could always request another uniqueness proof with the same session_id. at the very least, when doing this, the authenticator's UI MUST NOT display any "anonymous" / "one-time" / etc language.

@kilianglas

Copy link
Copy Markdown
Contributor Author

we should update the session proofs flow doc (https://github.com/worldcoin/world-id-protocol/tree/main/docs/world-id-4-specs#session-proofs) from this

I'd wait until I've implemented the final version of this (single uniqueness proof flow).

@kilianglas

kilianglas commented Jul 13, 2026

Copy link
Copy Markdown
Contributor Author

conceptual: I wonder if it should be generally allowed to request a session_id without the uniqueness proof. it could be a footgun for RPs because then RPs would have to immediately request a session proof to verify it's valid.

I'd say yes. Session proofs might be useful without a uniqueness proof, but might be wrong here.

@kilianglas

kilianglas commented Jul 13, 2026

Copy link
Copy Markdown
Contributor Author

et's document details on privacy implications, for example, you could always request another uniqueness proof with the same session_id. at the very least, when doing this, the authenticator's UI MUST NOT display any "anonymous" / "one-time" / etc language.

I don't fully get the privacy implications here. What's the point of requesting a different uniqunesess proof with the same session id?

@0xOsiris 0xOsiris left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is functionally identical to passing the sessionId as the signal to the uniqueness proof correct?

@kilianglas

kilianglas commented Jul 13, 2026

Copy link
Copy Markdown
Contributor Author

This is functionally identical to passing the sessionId as the signal to the uniqueness proof correct?

No, it is not. This also verifier, that the session_id and nullifier were created by the same world id, by essentially doing a session proof and a nullifier/ uniquneness proof in a single proof.

Ok(generate_ownership_proof_with_prover(input, prover)?)
}

fn validate_cached_session_r_seed(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: but maybe just validate_session_r_seed

cached implies there might be caching happening in this method

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this function also looks suspiciously close to functionality elsewhere, I recommend moving this to SessionId, it seems potentially problematic to have it here, if we're implementing validation here it means we could miss it elsewhere.

@paolodamico paolodamico left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high. if I think about the problem fresh, the conceptual solution in my mind is that RPs use a uniqueness proof to gate access to creating a session, so uniqueness proof -> session, while the other way around may be more confusing (though correct). this also maintains the clear separation that a uniqueness proof is anonymous one-time use and avoids authenticators having to manage this variant. for consideration @kilianglas

ProofType::Uniqueness => match proof_request.session_id {
// Bind the proof to the existing session. Requires the cached `r`.
Some(session_id) => {
let seed = session_id_r_seed.ok_or(AuthenticatorError::SessionSeedRequired)?;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this can be tackled in a separate PR, but I don't think we should fail here. if the RP provides a valid session_id we should recompute, especially because the RP has no control over what the user caches

Ok(generate_ownership_proof_with_prover(input, prover)?)
}

fn validate_cached_session_r_seed(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this function also looks suspiciously close to functionality elsewhere, I recommend moving this to SessionId, it seems potentially problematic to have it here, if we're implementing validation here it means we could miss it elsewhere.

.unwrap_err();
assert!(matches!(err, AuthenticatorError::SessionSeedRequired));

// a seed that does not open the session's commitment is rejected

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// a seed that does not open the session's commitment is rejected
// a seed that does not match the session's commitment is rejected

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants