Skip to content

feat(server): make the context scope effective - #730

Open
decrypto21 wants to merge 10 commits into
hrc-660-signing-manifestfrom
hrc-655-context-scope
Open

feat(server): make the context scope effective#730
decrypto21 wants to merge 10 commits into
hrc-660-signing-manifestfrom
hrc-655-context-scope

Conversation

@decrypto21

Copy link
Copy Markdown
Contributor

Closes #655. Stacked on #729, which the signing role needs to resolve a manifest at all.

A manifest granting context behaved identically to one granting nothing. The runtime admitted the
caller and the authority holding the keys refused it again with the error the grant seam would have
produced, so the two outcomes were byte-identical. #454 introduced the scope, could not make it
effective, and removed it in 3f6ec081.

Both seams change, not one. 3f6ec081 reverted create_account_proof and ring_vrf_sign to
comparing the key handle against the calling product, so restoring the frontend consult is part of
this, not just teaching the authority. Reverting only the frontend leaves a granted call refused at
the authority; reverting only the authority leaves it refused at the frontend. Both are covered by
mutation pairs below.

The authority resolves the grant itself. product_manifest::ring_vrf_key_access_granted is the
shared gate: the caller owns the key, or the owner's published manifest grants it context and the
user has not already refused. Both authorities call it, from both methods. It is not handed a
verdict from the capability layer, because on a paired host the request arrives over the wire and a
relayed boolean would take the manifest out of the decision entirely: the peer would reach every
handle on the device by setting one field, rather than only the handles a publisher really granted.
calling_product_id on that path is a SCALE field decoded from the peer's message, which is why the
component holding the keys has to decide for itself.

The handle is normalized in the gate, not only at the frontend. sso_responder hands a wire
request to the authority untouched, so without this an owner naming its own key PEOPL.DOT over the
wire was refused where the same request from a local product runtime succeeded. 3f6ec081's message
claims casing cannot do that, which was true of the frontend and false of the wire path. A handle
that does not normalize takes the uniform refusal.

A prior denial still wins. The stored AccountAccess decision is read before the manifest, and
read only, so a grant lookup never raises the prompt that would settle an undecided one.

The session is consulted before the grant on create_account_proof, matching ring_vrf_sign.
The other order makes the pair of refusals a probe for who granted whom: with no session a granting
target answers Rejected and a non-granting one NotAllowlisted. That had to land with the grant
rather than after it.

End to end

make e2e-cross-product-ringvrf is the sibling of e2e-cross-product-storage and the first run
anywhere in which a cross-product ring-VRF call is granted rather than refused. Every other run of
this path asserts the refusal, including the generated account-create-account-proof example and
ring-vrf-e2e.ts, which is how the scope shipped inert.

register       peopl.paseo   registered and signed  0x28ff7edb…983209
sign-granted   dim2.paseo    owner's own signature, 0x28ff7edb…983209
sign-untrusted stash.paseo   NotAllowlisted
sign-again     dim2.paseo    granted again

Real CLI, real wire frames, real chain, grant resolved from --product-config through the manifest
cache and the same adjudication code production runs. The byte comparison against the owner's own
signature is the assertion that matters: a host deriving the key from the caller still returns a
valid 64-byte signature.

Verification

1263 tests, cargo clippy --workspace --all-targets --all-features -- -D warnings clean,
cargo +nightly fmt --check clean. Battery unchanged against baseline.

Mutation pairs, each against a production function and each checked for which assertion goes red:

mutation test that fails
authority returns Ok(()) without consulting the manifest a_request_cannot_substitute_for_the_owners_manifest, first assertion
grants_scope never grants same test, second assertion, plus both granted arms
drop the prior-denial read a_stored_denial_survives_a_context_grant_at_the_authority
all stops satisfying a narrower scope a_grant_of_all_satisfies_context_at_the_authority
frontend reverts to the bare comparison a_context_grant_lets_a_foreign_product_prove_with_the_owners_key
grant check moved back before the session with_no_session_a_proof_refusal_never_discloses_whether_a_grant_exists
derive entropy from the caller, not the handle owner a_context_grant_lets_a_foreign_product_sign_with_the_owners_key
remove #729's Asset Hub install the_signing_role_adjudicates_grants_against_the_asset_hub_it_installed
drop context from the e2e fixture make e2e-cross-product-ringvrf, granted phase

Not covered

No end-to-end run drives the pairing wire path. The authority behaviour there is unit tested by
driving create_proof and ring_vrf_sign directly, the way sso_responder does, but a real paired
peer needs two hosts.

derive_ring_vrf_entropy still reads the raw handle (signing_host.rs), so over the wire a handle
spelled differently would derive a different key. Masked today because the registry lookup fails
first, and out of scope here: the fix belongs at the wire entry point, which five other calls pass
through. an_owner_naming_its_own_key_in_another_spelling_is_admitted_over_the_wire asserts
KeyNotRegistered exactly, so it goes red when that is closed.

@github-actions github-actions Bot added documentation Improvements or additions to documentation rfc labels Sep 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation rfc

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant