Skip to content

feat: verify RP signature over signed action on session seed queries#865

Closed
kilianglas wants to merge 1 commit into
mainfrom
kilianglas/oprf-node-signed-action
Closed

feat: verify RP signature over signed action on session seed queries#865
kilianglas wants to merge 1 commit into
mainfrom
kilianglas/oprf-node-signed-action

Conversation

@kilianglas

Copy link
Copy Markdown
Contributor

Summary

Adds an optional signed_action field to NullifierOprfRequestAuthV1 and teaches the OPRF node's session module to verify the RP signature over the action-inclusive message when it is present.

This is the node-side prerequisite for the create-and-bind session flow: a single RP signature over a uniqueness action authorizes both the session-seed query and the subsequent nullifier query, instead of requiring a prior session round-trip as in the bind-existing flow (#855, #862). No client sends the field yet; the client-side flow ships separately once the node fleet is deployed.

Changes

  • crates/primitives: new optional signed_action on NullifierOprfRequestAuthV1 (#[serde(default, skip_serializing_if)], same pattern as signature/wip101_data); two new auth error variants + close-frame codes (4524 invalid signed action, 4525 signed action not allowed); serde compat tests (JSON + CBOR round-trip, missing-field default, unknown-field tolerance)
  • services/oprf-node: session module verifies the RP signature over compute_rp_signature_msg(..., Some(signed_action)) when the field is present; validation restricts it to session-seed queries (0x01) with a nullifier-action value (MSB 0x00) from EOA-backed RPs — rejected on session-action queries (0x02), on the uniqueness module, and for WIP101 contract RPs (session support there is a pre-existing TODO)
  • services/oprf-node: new counter taceo.oprf.node.auth.session_signed_action for fleet-level visibility of create-and-bind seed queries
  • crates/proof, services/oprf-dev-client: construction sites set signed_action: None (no behavior change)

Rollout

  • Additive and default-inactive: nothing sends signed_action today, and requests without it are verified byte-identically to before.
  • Deploy-before-use: the node fleet must be fully rolled out before any client starts sending the field. A not-yet-upgraded node ignores the unknown field, reconstructs the action-less message, and rejects with invalid_rp_signature — loud fail-closed, never silent acceptance.
  • Nonce scoping is unchanged (SessionOprfSeed; scope derivation does not read signed_action).

Test plan

  • cargo test -p world-id-oprf-node — 79 passed, incl. 8 new signed-action tests (happy path, both signature/field mismatch directions, tampered value, invalid MSB, wrong query class, wrong module, contract RP)
  • cargo test -p world-id-primitives — 142 passed, incl. new serde compat tests
  • cargo clippy --workspace --all-targets -- -D warnings and cargo +nightly fmt --all -- --check

@kilianglas

Copy link
Copy Markdown
Contributor Author

Closing in favor of a redesign. The request model refactor in #866 makes the session intent (none / existing / create) an explicit three-state field, so instead of an optional signed_action side-channel on the OPRF auth payload, the RP signature message will carry a session-mode byte directly (version || nonce || created_at || expires_at || session_mode || [action]), verified by node-side reconstruction. That closes the create/prove signature-replay gap uniformly and makes the message self-describing rather than module-dependent. Will be re-implemented on top of #866.

@kilianglas kilianglas closed this Jul 14, 2026
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.

2 participants