RCC 0024: Proof of Personhood as a Product — Explicit Ring VRF Key Management - #324
RCC 0024: Proof of Personhood as a Product — Explicit Ring VRF Key Management#324valentunn wants to merge 6 commits into
Conversation
|
|
||
| ## Summary | ||
|
|
||
| RFC-0004 makes the Host pick a ring VRF member key on the caller's behalf, with a hard-coded fallback to "the PoP ring". This RFC replaces that with an explicit, product-owned key registry: a product registers keys it owns against the rings it intends them for, other products discover those registrations by an anonymized handle, and the handle is passed to `create_account_proof`, `get_account_alias`, and a new `ring_vrf_sign`. With an `onLoad` executable modality for global lifetime and the Accounts Protocol companions, full and light personhood become a standalone product whose key index no consumer — including the Host — has to know. |
There was a problem hiding this comment.
The current name is account_get_alias and not get_account_alias (?)
|
|
||
| ## Summary | ||
|
|
||
| RFC-0004 makes the Host pick a ring VRF member key on the caller's behalf, with a hard-coded fallback to "the PoP ring". This RFC replaces that with an explicit, product-owned key registry: a product registers keys it owns against the rings it intends them for, other products discover those registrations by an anonymized handle, and the handle is passed to `create_account_proof`, `get_account_alias`, and a new `ring_vrf_sign`. With an `onLoad` executable modality for global lifetime and the Accounts Protocol companions, full and light personhood become a standalone product whose key index no consumer — including the Host — has to know. |
There was a problem hiding this comment.
Regarding "standalone product": Rings, membership, onboarding and suspension stay onchain. What moves to the product is key management and the client-side surfac. I propose saying so explicitly.
| | Product-extractable features | game, mobrule, identity | Yes | | ||
| | Cross-product shared | set identity account, set score alias | Yes, but needs cross-product reach | | ||
|
|
||
| The app-internal class forces a mechanism, and this RFC picks a **registration call**: the product declares which key is intended for which ring, and the Host uses that registration wherever it used a compiled-in key. The rejected alternative is in [Alternatives](#alternatives). |
There was a problem hiding this comment.
This bundles two conclusions with very different costs, and I think they should be separated.
Making the key an explicit parameter and deleting the selection contract solves the motivating problem by itself. Selection is fragile only because the key is missing from the request; once it is present, alias determinism holds by construction instead of by cross-implementation agreement. No registry needed for that.
The registry is needed for something else: when the Host does coinage unloading or allowance slot assignment there is no caller to supply a handle, so something must tell it which handle is the person key. That justification is solid. Discovery and phone-side bookkeeping are weaker (RFC-0022 already pins index 0 and 1, so discovery could be convention).
The parameter change looks uncontentious. The registry adds distributed state, an intent leak and a derivation rule that cannot be enforced. Presenting them as one conclusion means they get accepted or rejected together.
| - **Product developers building on personhood** — score / identity / mobrule / game; consume foreign handles, foreign contexts, and alias origins. | ||
| - **Host developers** — implement the registry, drop the compiled-in member-key selection, enforce the owner allowlist on proofs and signatures, add the `onLoad` modality. | ||
| - **Account Holder developers (Mobile App)** — become the authoritative registry, implement the new message pairs, extend the AutoSigning payload, answer registrations from the background. | ||
| - **Chain / individuality developers** — on-chain contexts (`score`, `resources`, `mob-rule`) must be derived with TrUAPI's product-scoped context function rather than a parallel namespace. |
There was a problem hiding this comment.
Two issues.
The list is incomplete. Coinage has its own contexts, including ones constructed at runtime from a base plus period and counter, and there is a dotNS gateway context.
The migration cost is missing entirely. See my comment on the "supersedes RFC-0022" paragraph.
There was a problem hiding this comment.
Will re-word so it is clear this is not a complete list (it is not intended to be one)
| G->>H: list_ring_vrf_keys("peopl.dot", Anonymized) | ||
| H-->>G: [ { handle: (peopl.dot, ?), rings: [People, PeopleLite] } ] | ||
| G->>G: select the entry whose rings contain the People ring | ||
| G->>H: create_account_proof(handle, game.dot/airdrop, People, message) |
There was a problem hiding this comment.
This step replaces work the Host does today by querying the chain.
Currently the Host derives both person keys and looks each up in the membership map, full before light, so full-versus-light is resolved against actual on-chain state. Here the consumer selects by declared intent and only finds out it chose wrong when the proof returns NotMember.
More explicit, and I think correct, but it makes "try full, fall back to light" every consuming product's problem. Is the product SDK expected to absorb this? Worth stating, otherwise every product reinvents it.
There was a problem hiding this comment.
"try full, fall back to light" use-case is not something that seems to be needed right now by any product, but yes, in case that's needed, the best place would be the product-sdk
| H-->>G: proof + contextual_alias + ring_index + ring_revision | ||
| ``` | ||
|
|
||
| **No product may assume a key index of another product.** The index is the owner's implementation detail; consumers select by declared `RingLocation` and treat the handle as opaque. Hardcoding `(peopl.dot, 0)` breaks the moment the owner rotates or adds a key. This is the one rule a consuming product has to remember. |
There was a problem hiding this comment.
The way it is specified here this rule cannot be enforced, because the index is part of the handle. Any caller that lists the registry can read it and hardcode it. Anonymized disclosure does not help: it withholds the member public key, not the index.
There was a problem hiding this comment.
I dont see a way to enforce this while elso preserving the coherence of the api itself. So, in my view, this stands as a implementation note for the product-sdk that should abstract those low-level details away
|
|
||
| Both `create_account_proof` and `ring_vrf_sign` hand the caller output produced with someone else's member key, and neither can be constrained by inspection. **A proof is a bearer token for its context's alias, and a signature is a bearer token for the key itself.** `message` is opaque — for an extrinsic it is a hash of the inherited implication, and accepting a caller-supplied preimage would still be blind signing — so nothing at call time can tell what the result will authorize. | ||
|
|
||
| The concrete consequence, worth stating because it is not obvious: a product holding a proof under the score context can build a `set_alias` binding that alias to an account of its own, sign it with its own product account, and submit it without involving the Host again. The score alias then resolves to an account it controls. Every check passes; the proof was the authority. `ring_vrf_sign` is the wider version of the same problem, since it has no context or ring to scope what the signature is good for. |
There was a problem hiding this comment.
The pallet already deployed is more permissive than this example suggests.
pallet-alias-accounts in individuality takes the proof as a call argument, accepts any 32-byte context rather than an allowlisted set, works for both People and People Lite, and signs over blake2_256(("alias-accounts", account, proof_valid_at)). So the binding is not limited to the score context, and the message is exactly the opaque hash you describe as uninspectable.
There was a problem hiding this comment.
pallet-alias-accounts in individuality takes the proof as a call argument, accepts any 32-byte context
This should be adjust in runtime so all allowed contexts are aligned with truAPI product-scoped derivation model
But I agree worth mentioning in the rfc that is not just about score
|
|
||
| The model is **user-approval driven**, per RFC-0002: an unapproved foreign access produces a one-time prompt with the persist-once lifecycle. The only way to avoid the prompt is for the *owner* to have allowed the caller in advance — **a product declares, in its manifest, the list of product ids it permits to access its data without a prompt.** For proofs and signatures that allowlist is not an optimization but the whole gate, per the rule above. | ||
|
|
||
| That declaration belongs to the product manifest, specified separately ([RFC: Product Manifest Format](https://github.com/paritytech/truapi/pull/206)), with two requirements from here: the allowlist must be **structurally extensible**, so a richer scheme (per-method grants, attestation thresholds) can replace a flat product-id list without a wire break; and it should be expressible **per method or category**, so "read my key handles" and "sign under my alias" need not be one grant. Until it lands, Hosts fall back to a one-time prompt per (caller, owner, call) triple, persisted per RFC-0002. |
There was a problem hiding this comment.
This contradicts the MUST NOT on line 222.
That paragraph says a prompt is not a substitute for the allowlist and must not be offered as a fallback for proofs and signatures. This one says Hosts fall back to a one-time prompt per (caller, owner, call) until the manifest lands. Since the manifest RFC has not landed, the fallback is the state everything ships in.
Which rule governs? If it is this one, the MUST NOT above needs rewording.
There was a problem hiding this comment.
Bad wording here:
The line 222 talks specifically about create_proof and ring_vrf_sign. This paragraph talks about all other calls that this RFC touches. But since both signing calls are present in the table above, this creates confusion. They should be separated explicitly, will fix
| - [RFC-0020 — `create_transaction` and its AP mirror](0020-create-transaction.md) — the pattern of specifying a TrUAPI call together with its AP companion, followed here. | ||
| - [RFC-0010 — W3S Allowance Management](0010-allowance.md) — AutoSigning and the PGAS / Bulletin / SSS flows that consume the person key. | ||
| - [RFC-0002 — Permission Model](0002-permission-model.md) — the prompt-once lifecycle every cross-product grant reuses · [RFC-0009](0009-unauthenticated-product-access.md) — `NotConnected` semantics · [RFC: Product Manifest Format](https://github.com/paritytech/truapi/pull/206) — where the allowlist is specified. | ||
| - *SSO background availability — common model* — the layered availability ladder referenced above. **TODO: link the HackMD document.** |
There was a problem hiding this comment.
Dead reference. Is this Ruslan's background signing model (hackmd.io/rBEBjBzLQdOHvzwJkfufIQ)? The onLoad and AutoSigning sections both lean on it, so those cannot be reviewed until it is linked.
There was a problem hiding this comment.
Yes. It should be https://hackmd.io/rBEBjBzLQdOHvzwJkfufIQ
|
|
||
| - **Expressing cross-product key use more narrowly than an allowlist.** The interim rule trades precision for time: an owner can say *who* may use its key but not *for what*. What an owner wants is closer to "you may prove personhood under your own airdrop context" than "you may do anything my key can do". The most developed candidate is in [Alternatives](#alternatives) — routing alias use through `create_transaction` so the Host sees the call — and any general answer has to cover `ring_vrf_sign`, where there is no call to inspect. | ||
| - **Revocation** — already deferred by RFC-0010 and made more urgent by the entropy transfer, including retraction of a registry entry by its owner. | ||
| - **Key rotation and recovery** — the registry makes rotation expressible (register a new index, retire the old), but the effect on in-flight aliases is unspecified. |
There was a problem hiding this comment.
Rotation is not just unspecified here, it is destructive. The alias depends on the key, so rotating changes the person's alias in every context at once, not only in-flight ones.
individuality already has migrate_included_key and migrate_onboarding_key plus an offchain worker cleaning up stale aliases, and the original PoP design treats the Bandersnatch key as something a person never changes.
There was a problem hiding this comment.
Agree, we should explicitly say that the rotation is not expected to happen
Rendered