From 74a215b17973f03acfc7f1e1d28072045232be34 Mon Sep 17 00:00:00 2001 From: Filippo Vecchiato Date: Thu, 23 Apr 2026 13:13:08 +0200 Subject: [PATCH 1/9] RFC-0004: Redesign RingLocation in host_account_create_proof --- docs/rfcs/0004-ringlocation-redesign.md | 170 ++++++++++++++++++++++++ docs/rfcs/_index.md | 1 + 2 files changed, 171 insertions(+) create mode 100644 docs/rfcs/0004-ringlocation-redesign.md diff --git a/docs/rfcs/0004-ringlocation-redesign.md b/docs/rfcs/0004-ringlocation-redesign.md new file mode 100644 index 00000000..e70e4658 --- /dev/null +++ b/docs/rfcs/0004-ringlocation-redesign.md @@ -0,0 +1,170 @@ +--- +title: "Redesign RingLocation in host_account_create_proof" +type: rfc +status: draft +owner: "@valentin-parity" +pr: +--- + +# RFC 0004 — Redesign RingLocation in `host_account_create_proof` + +| | | +| --------------- |-----------------------------------------------------------------------------------------------------------| +| **Start Date** | 2026-03-16 | +| **Description** | Replace RingLocation with a junction-based path to fix request invalidation and multi-ring pallet support | +| **Authors** | Valentin Sergeev | + +## Summary + +This RFC proposes replacing the current `RingLocation` struct in `host_account_create_proof` with a junction-based addressing scheme. The current design is fragile when rings change frequently (e.g. new members are added) because it relies on `ring_root_hash`, which becomes stale mid-request. It also cannot address rings within pallets that host multiple ring collections. The proposed design uses a `Vec` path that references only stable, immutable identifiers and returns the ring revision alongside the proof. + +## Motivation + +### Request invalidation + +The current `RingLocation` includes a `ring_root_hash` — a blake2b32 hash of the ring root. When a ring changes frequently (e.g. new members joining), this hash changes, invalidating any in-flight proof request that was constructed against the previous root. + +This is particularly problematic for coinage, where the recycler coin transaction extension requires both the ring-vrf proof **and** the revision at which the proof is valid. The current API has no way to communicate this revision back to the caller. + +### Hints are insufficient for multi-ring pallets + +With the introduction of the membership pallet, a single pallet instance can host rings from multiple ring collections. Each ring is identified by a `(collection_id, ring_index)` pair. The current `RingLocationHint` only supports an optional `pallet_instance`, which is not enough to disambiguate rings within the same pallet. This forces the host into guesswork or requires out-of-band coordination that the API should handle directly. + +## Detailed Design + +### Status Quo + +The current API: + +```rust +struct RingLocationHint { + pallet_instance: Option +} + +struct RingLocation { + genesis_hash: GenesisHash, + ring_root_hash: Vec, + hints: Option +} + +type RingVrfProof = Vec; + +fn host_account_create_proof( + domain: ProductAccountId, + ring: RingLocation, + message: Vec +) -> Result; +``` + +**Problems:** + +1. `ring_root_hash` changes whenever ring membership changes, causing request invalidation if the ring is updated while the host is processing the proof request. +2. `RingLocationHint` cannot address a specific ring within a multi-collection pallet — it only knows `pallet_instance`. +3. The return type (`Vec`) has no way to communicate the ring revision, which downstream consumers (e.g. coinage transaction extensions) may need. + +### Proposed Changes + +Replace `RingLocation` with a junction-based path (inspired by XCM's `MultiLocation` junctions) and extend the return type to include the ring revision: + +```rust +enum RingLocationJunction { + Chain(GenesisHash), + PalletInstance(u8), + CollectionId(Vec), + RingIndex(u32), +} + +type RingLocation = Vec; + +struct RingVrfProof { + proof: Vec, + ring_revision: Option, // None when the ring is immutable +} + +fn host_account_create_proof( + domain: ProductAccountId, + ring: RingLocation, + message: Vec +) -> Result; +``` + +### Design Rationale + +**Only stable identifiers in the request.** The product supplies a path of junctions that are constant for the lifetime of the ring — chain genesis hash, pallet instance, collection id, and ring index. None of these change when ring membership is updated. The host resolves the current ring root internally, eliminating the race condition. + +**Ring revision in the response.** The host knows which revision of the ring it used to generate the proof. By returning `ring_revision`, the product can pass it along to downstream consumers (e.g. coinage's recycler transaction extension) without a separate lookup. + +**Extensible junction set.** New junction variants can be added in the future without breaking existing consumers, since `RingLocation` is simply a vector of junctions. For example, if a new addressing dimension is introduced (e.g. a sub-collection), a new `RingLocationJunction` variant can be appended. + +### Migration + +The `ring_root_hash` field is removed entirely — products no longer need to fetch or compute ring roots. The `hints` field is also removed, as pallet instance addressing is now a first-class junction rather than an optional hint. + +Existing products using `host_account_create_proof` will need to update their `RingLocation` construction from: + +```rust +// Before +RingLocation { + genesis_hash: chain_genesis, + ring_root_hash: computed_hash, + hints: Some(RingLocationHint { pallet_instance: Some(42) }) +} + +// After +vec![ + RingLocationJunction::Chain(chain_genesis), + RingLocationJunction::PalletInstance(42), + RingLocationJunction::CollectionId(collection), + RingLocationJunction::RingIndex(index), +] +``` + +### Stakeholders + +- **Product developers** — consumers of `host_account_create_proof` who need reliable proof generation without worrying about ring root staleness. +- **Mobile app / host implementors** — responsible for resolving ring locations and generating proofs; benefit from unambiguous ring addressing. + +### Testing, Security, and Privacy + +- **Testing**: Implementations should verify that proof generation succeeds even when ring membership changes between request construction and proof generation. The returned `ring_revision` must match the actual ring state used for the proof. +- **Security**: The host must validate that the junction path resolves to a real ring. Invalid or malicious paths should return `CreateProofErr` rather than panicking or producing invalid proofs. +- **Privacy**: No change from the current model — the same information is exchanged, just addressed differently. + +### Performance, Ergonomics, and Compatibility + +#### Performance + +Proof generation performance is unchanged. The host may need an additional lookup to resolve the ring root from the junction path, but this is expected to be negligible compared to the proof computation itself. + +#### Ergonomics + +Products no longer need to fetch and hash ring roots before requesting a proof — they only need to know the stable addressing coordinates of the ring. This simplifies the product-side implementation and eliminates a common source of errors. + +#### Compatibility + +This is a breaking change to the `host_account_create_proof` method signature. Both the request type (`RingLocation`) and response type (`RingVrfProof`) are modified. A protocol version bump is required. + +## Drawbacks + +1. **Breaking change** — All existing consumers of `host_account_create_proof` must update their `RingLocation` construction and handle the new `RingVrfProof` struct instead of raw bytes. +2. **Host complexity** — The host must now resolve the ring root from the junction path internally, which may require additional chain queries. Previously the product supplied the root hash directly. +3. **Junction ordering** — The path is a flat vector with no enforced ordering or validation at the type level. Malformed paths (e.g. missing `Chain` junction) must be handled at runtime. + +## Alternatives + +- Keep `ring_root_hash` but add a retry mechanism on the product side — rejected because it doesn't solve the revision visibility problem and adds complexity to every product. +- Use a structured struct instead of junction vec — rejected in favor of extensibility; adding new addressing dimensions would require struct changes. +- XCM `MultiLocation` directly — rejected as overly general for this use case, but the junction pattern is borrowed as inspiration. + +## Unresolved Questions + +1. Should the junction ordering be enforced (e.g. `Chain` must come first), or is any order acceptable as long as the host can resolve it? +2. Should `CollectionId` use a fixed-size type (e.g. `[u8; 32]`) instead of `Vec` for consistency with other on-chain identifiers? +3. Are there ring identification schemes beyond `(collection_id, ring_index)` that we should anticipate in the junction design? +4. Should `CreateProofErr` gain a new variant (e.g. `RingLocationInvalid`) for unresolvable junction paths, or is the existing `RingNotFound` sufficient? + +## References + +- [Host API Design Document v0.5](https://docs.google.com/document/d/1AxKjF15y7gmdl-a6twc5wd8R5xcxKxMO8Ahp2l20v0g/edit?usp=sharing) +- XCM `MultiLocation` junction model — inspiration for the junction-based addressing pattern +- [Polkadot People Registry / Ring VRF](https://forum.polkadot.network/t/the-people-registry/12749) diff --git a/docs/rfcs/_index.md b/docs/rfcs/_index.md index 2e478a63..1f5b5ca9 100644 --- a/docs/rfcs/_index.md +++ b/docs/rfcs/_index.md @@ -12,6 +12,7 @@ created: 2026-03-13 |--------|--------------------------------------------------------------------------|----------|---------------|-----------------------------------------------------------------| | 0001 | [RFC Template](0001-template.md) | — | — | — | | 0002 | [Permission Model for Host API](0002-permission-model.md) | accepted | @johnthecat | [#66](https://github.com/paritytech/triangle-js-sdks/pull/66) | +| 0004 | [Redesign RingLocation](0004-ringlocation-redesign.md) | draft | @valentunn | [#81](https://github.com/paritytech/triangle-js-sdks/pull/81) | | 0006 | [Payment Host API](0006-payments.md) | accepted | @valentunn | [#94](https://github.com/paritytech/triangle-js-sdks/pull/94) | | 0007 | [Deterministic Entropy Derivation](0007-derive-entropy.md) | accepted | @valentunn | [#95](https://github.com/paritytech/triangle-js-sdks/pull/95) | | 0008 | [Statement Store Host API v0.2](0008-statement-store.md) | accepted | @johnthecat | [#118](https://github.com/paritytech/triangle-js-sdks/pull/118) | From c41d05738e82ac875d5450d9b0024265f3a3209b Mon Sep 17 00:00:00 2001 From: Filippo Vecchiato Date: Thu, 7 May 2026 15:49:48 +0200 Subject: [PATCH 2/9] Move ring_index from input to output, address review feedback Incorporate feedback from triangle-js-sdks#81 (@Zebedeusz): - Remove RingIndex from RingLocationJunction (no longer an input) - Add ring_index and ring_revision (non-optional) to RingVrfProof so products can pass them to downstream precompiles (e.g. personhoodInfoByProof from individuality#891) without extra queries - Add "Abstraction-friendly" rationale addressing whether products should need to know ring location details at all - Add unresolved question about a well-known default RingLocation - Reference individuality#878, #891, and the original review thread --- docs/rfcs/0004-ringlocation-redesign.md | 29 ++++++++++++++++--------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/docs/rfcs/0004-ringlocation-redesign.md b/docs/rfcs/0004-ringlocation-redesign.md index e70e4658..5d56c099 100644 --- a/docs/rfcs/0004-ringlocation-redesign.md +++ b/docs/rfcs/0004-ringlocation-redesign.md @@ -16,7 +16,7 @@ pr: ## Summary -This RFC proposes replacing the current `RingLocation` struct in `host_account_create_proof` with a junction-based addressing scheme. The current design is fragile when rings change frequently (e.g. new members are added) because it relies on `ring_root_hash`, which becomes stale mid-request. It also cannot address rings within pallets that host multiple ring collections. The proposed design uses a `Vec` path that references only stable, immutable identifiers and returns the ring revision alongside the proof. +This RFC proposes replacing the current `RingLocation` struct in `host_account_create_proof` with a junction-based addressing scheme. The current design is fragile when rings change frequently (e.g. new members are added) because it relies on `ring_root_hash`, which becomes stale mid-request. It also cannot address rings within pallets that host multiple ring collections. The proposed design uses a `Vec` path that references only stable, immutable identifiers and returns the ring index and revision alongside the proof, so products can pass them directly to downstream precompiles without additional on-chain queries. ## Motivation @@ -71,14 +71,14 @@ enum RingLocationJunction { Chain(GenesisHash), PalletInstance(u8), CollectionId(Vec), - RingIndex(u32), } type RingLocation = Vec; struct RingVrfProof { proof: Vec, - ring_revision: Option, // None when the ring is immutable + ring_index: u32, + ring_revision: u32, } fn host_account_create_proof( @@ -90,12 +90,14 @@ fn host_account_create_proof( ### Design Rationale -**Only stable identifiers in the request.** The product supplies a path of junctions that are constant for the lifetime of the ring — chain genesis hash, pallet instance, collection id, and ring index. None of these change when ring membership is updated. The host resolves the current ring root internally, eliminating the race condition. +**Only stable identifiers in the request.** The product supplies a path of junctions that are constant for the lifetime of the ring — chain genesis hash, pallet instance, and collection id. None of these change when ring membership is updated. The host resolves the current ring root and the caller's ring index internally, eliminating the race condition. -**Ring revision in the response.** The host knows which revision of the ring it used to generate the proof. By returning `ring_revision`, the product can pass it along to downstream consumers (e.g. coinage's recycler transaction extension) without a separate lookup. +**Ring index and revision in the response.** The host knows which ring index the caller occupies and which revision of the ring it used to generate the proof. By returning both `ring_index` and `ring_revision`, the product can pass them directly to downstream consumers (e.g. the `personhoodInfoByProof` precompile from [individuality#891](https://github.com/paritytech/individuality/pull/891), or coinage's recycler transaction extension) without a separate lookup. Moving `ring_index` to the output also means the product never needs to discover or track its own position in the ring — the host resolves it from the product's derived account. **Extensible junction set.** New junction variants can be added in the future without breaking existing consumers, since `RingLocation` is simply a vector of junctions. For example, if a new addressing dimension is introduced (e.g. a sub-collection), a new `RingLocationJunction` variant can be appended. +**Abstraction-friendly.** Products should not need to know low-level ring addressing details. The junction-based path lets the host abstract away pallet instances and collection ids behind a well-known alias or default path in the future, while the structured response (`ring_index`, `ring_revision`, `proof`) gives products everything they need to call downstream precompiles without any additional on-chain queries. + ### Migration The `ring_root_hash` field is removed entirely — products no longer need to fetch or compute ring roots. The `hints` field is also removed, as pallet instance addressing is now a first-class junction rather than an optional hint. @@ -111,12 +113,15 @@ RingLocation { } // After -vec![ +let location = vec![ RingLocationJunction::Chain(chain_genesis), RingLocationJunction::PalletInstance(42), RingLocationJunction::CollectionId(collection), - RingLocationJunction::RingIndex(index), -] +]; +let result = host_account_create_proof(domain, location, message)?; +// result.proof — the ring-vrf proof bytes +// result.ring_index — the caller's index in the ring (for precompile calls) +// result.ring_revision — the ring revision the proof was generated against ``` ### Stakeholders @@ -126,7 +131,7 @@ vec![ ### Testing, Security, and Privacy -- **Testing**: Implementations should verify that proof generation succeeds even when ring membership changes between request construction and proof generation. The returned `ring_revision` must match the actual ring state used for the proof. +- **Testing**: Implementations should verify that proof generation succeeds even when ring membership changes between request construction and proof generation. The returned `ring_index` and `ring_revision` must match the actual ring state used for the proof. - **Security**: The host must validate that the junction path resolves to a real ring. Invalid or malicious paths should return `CreateProofErr` rather than panicking or producing invalid proofs. - **Privacy**: No change from the current model — the same information is exchanged, just addressed differently. @@ -160,11 +165,15 @@ This is a breaking change to the `host_account_create_proof` method signature. B 1. Should the junction ordering be enforced (e.g. `Chain` must come first), or is any order acceptable as long as the host can resolve it? 2. Should `CollectionId` use a fixed-size type (e.g. `[u8; 32]`) instead of `Vec` for consistency with other on-chain identifiers? -3. Are there ring identification schemes beyond `(collection_id, ring_index)` that we should anticipate in the junction design? +3. Are there ring identification schemes beyond `(collection_id)` that we should anticipate in the junction design? 4. Should `CreateProofErr` gain a new variant (e.g. `RingLocationInvalid`) for unresolvable junction paths, or is the existing `RingNotFound` sufficient? +5. Should the host provide a well-known default `RingLocation` (e.g. for the canonical personhood ring on the relay chain) so products can request proofs without knowing any chain/pallet/collection details? This would address the ergonomics concern raised in [triangle-js-sdks#81](https://github.com/paritytech/triangle-js-sdks/pull/81). ## References - [Host API Design Document v0.5](https://docs.google.com/document/d/1AxKjF15y7gmdl-a6twc5wd8R5xcxKxMO8Ahp2l20v0g/edit?usp=sharing) - XCM `MultiLocation` junction model — inspiration for the junction-based addressing pattern - [Polkadot People Registry / Ring VRF](https://forum.polkadot.network/t/the-people-registry/12749) +- [individuality#878](https://github.com/paritytech/individuality/pull/878) — alias-account assignment for derived product addresses +- [individuality#891](https://github.com/paritytech/individuality/pull/891) — `personhoodInfoByProof` precompile (motivates `ring_index` in the response) +- [triangle-js-sdks#81 comment](https://github.com/paritytech/triangle-js-sdks/pull/81) — @Zebedeusz's feedback on moving `ring_index` to output and abstraction concerns From 5aa468a2f00c79a1f8d074d7cd5a16a70b452bac Mon Sep 17 00:00:00 2001 From: valentunn Date: Mon, 29 Jun 2026 13:53:03 +0300 Subject: [PATCH 3/9] Update RFC 4 --- docs/rfcs/0004-ringlocation-redesign.md | 210 +++++++++++++----------- 1 file changed, 111 insertions(+), 99 deletions(-) diff --git a/docs/rfcs/0004-ringlocation-redesign.md b/docs/rfcs/0004-ringlocation-redesign.md index 5d56c099..3d52ab51 100644 --- a/docs/rfcs/0004-ringlocation-redesign.md +++ b/docs/rfcs/0004-ringlocation-redesign.md @@ -11,169 +11,181 @@ pr: | | | | --------------- |-----------------------------------------------------------------------------------------------------------| | **Start Date** | 2026-03-16 | -| **Description** | Replace RingLocation with a junction-based path to fix request invalidation and multi-ring pallet support | +| **Description** | Junction-based RingLocation, person-keyed and product-context-scoped proofs, specified member-key selection | | **Authors** | Valentin Sergeev | ## Summary -This RFC proposes replacing the current `RingLocation` struct in `host_account_create_proof` with a junction-based addressing scheme. The current design is fragile when rings change frequently (e.g. new members are added) because it relies on `ring_root_hash`, which becomes stale mid-request. It also cannot address rings within pallets that host multiple ring collections. The proposed design uses a `Vec` path that references only stable, immutable identifiers and returns the ring index and revision alongside the proof, so products can pass them directly to downstream precompiles without additional on-chain queries. +Redesign `host_account_create_proof` and `host_account_get_alias`: -## Motivation +1. **Junction-based ring addressing** — replace the `ring_root_hash`-based `RingLocation` with a struct carrying a required `chain_id` and a `Vec` path of stable, immutable identifiers. +2. **Person-keyed, product-context-scoped proofs** — replace `domain: ProductAccountId` with `ProductProofContext = (ProductId, ProductProofContextSuffix)`. The proof is keyed by the user's person; the context provides unlinkability. +3. **Richer output and errors** — return `contextual_alias`, `ring_index`, and `ring_revision`; specify host member-key selection; add a `NotMember` error. + +No protocol version bump is required: the current shape of these methods is unusable (the `ring_root_hash` race makes it broken by construction) and is not implemented or consumed anywhere yet, so it can be replaced in place. -### Request invalidation +## Motivation -The current `RingLocation` includes a `ring_root_hash` — a blake2b32 hash of the ring root. When a ring changes frequently (e.g. new members joining), this hash changes, invalidating any in-flight proof request that was constructed against the previous root. +- **Request invalidation.** `ring_root_hash` changes whenever ring membership changes, invalidating any in-flight proof request built against the previous root. +- **No revision in the response.** Downstream consumers (coinage's recycler transaction extension, the `personhoodInfoByProof` precompile) need the ring revision and index, which the current `Vec` return cannot carry. +- **Hints can't address multi-ring pallets.** With the membership pallet, one pallet instance hosts rings from multiple collections, each identified by `(collection_id, ring_index)`. `RingLocationHint`'s optional `pallet_instance` cannot disambiguate them. +- **`domain: ProductAccountId` is the wrong key.** A personhood proof must be keyed by the user's _person_ — `personhoodInfoByProof` verifies the author is a ring member, not that a derived product account exists. Unlinkability comes from the _context_ (same person + different contexts → different aliases), so the request needs an explicit, product-scoped context rather than a derivation index. +- **Member-key selection is unspecified.** A host may hold several member keys but the API hides them (exposing them leaks identity). Without a defined selection contract, two hosts can derive different aliases for the same request. +- **No "not a member" error.** A user who has not reached full personhood is not in the ring. `CreateProofErr` cannot distinguish this from "ring does not exist", so products can't route the user to onboarding. -This is particularly problematic for coinage, where the recycler coin transaction extension requires both the ring-vrf proof **and** the revision at which the proof is valid. The current API has no way to communicate this revision back to the caller. +## Status Quo -### Hints are insufficient for multi-ring pallets +```rust +struct RingLocationHint { pallet_instance: Option } +struct RingLocation { genesis_hash: GenesisHash, ring_root_hash: Vec, hints: Option } +type RingVrfProof = Vec; -With the introduction of the membership pallet, a single pallet instance can host rings from multiple ring collections. Each ring is identified by a `(collection_id, ring_index)` pair. The current `RingLocationHint` only supports an optional `pallet_instance`, which is not enough to disambiguate rings within the same pallet. This forces the host into guesswork or requires out-of-band coordination that the API should handle directly. +fn host_account_create_proof(domain: ProductAccountId, ring: RingLocation, message: Vec) + -> Result; +fn host_account_get_alias(domain: ProductAccountId) + -> Result; +``` -## Detailed Design +## Design -### Status Quo +### Ring addressing -The current API: +`chain_id` is a required field (not a junction) so a location can never omit its chain; the junctions address the ring within it. All identifiers are stable for the ring's lifetime, so the host can resolve the current root and the caller's index internally without a membership-change race. New `RingLocationJunction` variants can be added without breaking consumers. (The junction pattern is borrowed from XCM's `MultiLocation`.) ```rust -struct RingLocationHint { - pallet_instance: Option +enum RingLocationJunction { + PalletInstance(u8), + CollectionId(Vec), } struct RingLocation { - genesis_hash: GenesisHash, - ring_root_hash: Vec, - hints: Option + chain_id: GenesisHash, + junctions: Vec, } - -type RingVrfProof = Vec; - -fn host_account_create_proof( - domain: ProductAccountId, - ring: RingLocation, - message: Vec -) -> Result; ``` -**Problems:** +### Product-scoped proof context -1. `ring_root_hash` changes whenever ring membership changes, causing request invalidation if the ring is updated while the host is processing the proof request. -2. `RingLocationHint` cannot address a specific ring within a multi-collection pallet — it only knows `pallet_instance`. -3. The return type (`Vec`) has no way to communicate the ring revision, which downstream consumers (e.g. coinage transaction extensions) may need. +`ProductId` is the existing dotNS product identifier (named here as a reminder of what scopes the context). `domain: ProductAccountId` is replaced by: -### Proposed Changes +```rust +type ProductProofContextSuffix = Vec; // arbitrary bytes -Replace `RingLocation` with a junction-based path (inspired by XCM's `MultiLocation` junctions) and extend the return type to include the ring revision: +struct ProductProofContext { + product_id: ProductId, + suffix: ProductProofContextSuffix, +} -```rust -enum RingLocationJunction { - Chain(GenesisHash), - PalletInstance(u8), - CollectionId(Vec), +// 32-byte context bound into the proof. +fn product_context_bytes(context: ProductProofContext) -> [u8; 32] { + blake2b256(utf8("product/") ++ utf8(context.product_id) ++ utf8("/") ++ context.suffix) } +``` -type RingLocation = Vec; +- **Product-scoped.** The `product//` prefix stops a malicious product from choosing a suffix that collides with another product's context and thereby links its aliases. This is a privacy boundary. +- **Arbitrary-byte suffix.** Some contexts need more than one index — e.g. a pgas claim derives its context from two `u32`s (period and sequence). A single-index suffix would make them unrepresentable. +### `create_proof` and `get_alias` + +The proof is generated against the logged-in user's person; the host selects the member key (see below). Both methods take the same `(context, ring)` so they derive the same alias. + +```rust struct RingVrfProof { proof: Vec, + contextual_alias: ContextualAlias, ring_index: u32, ring_revision: u32, } -fn host_account_create_proof( - domain: ProductAccountId, - ring: RingLocation, - message: Vec -) -> Result; -``` - -### Design Rationale +fn host_account_create_proof(context: ProductProofContext, ring: RingLocation, message: Vec) + -> Result; -**Only stable identifiers in the request.** The product supplies a path of junctions that are constant for the lifetime of the ring — chain genesis hash, pallet instance, and collection id. None of these change when ring membership is updated. The host resolves the current ring root and the caller's ring index internally, eliminating the race condition. +fn host_account_get_alias(context: ProductProofContext, ring: RingLocation) + -> Result; +``` -**Ring index and revision in the response.** The host knows which ring index the caller occupies and which revision of the ring it used to generate the proof. By returning both `ring_index` and `ring_revision`, the product can pass them directly to downstream consumers (e.g. the `personhoodInfoByProof` precompile from [individuality#891](https://github.com/paritytech/individuality/pull/891), or coinage's recycler transaction extension) without a separate lookup. Moving `ring_index` to the output also means the product never needs to discover or track its own position in the ring — the host resolves it from the product's derived account. +`ring_index` / `ring_revision` let products call downstream precompiles without a separate lookup. `contextual_alias` is an ergonomics optimization — the same value `get_alias` returns for the same `(context, ring)` — saving a round trip when a caller needs both proof and alias (e.g. a voting contract keying votes by alias). The host MUST select the member key identically in both methods so the two aliases match. -**Extensible junction set.** New junction variants can be added in the future without breaking existing consumers, since `RingLocation` is simply a vector of junctions. For example, if a new addressing dimension is introduced (e.g. a sub-collection), a new `RingLocationJunction` variant can be appended. +### Host member-key selection -**Abstraction-friendly.** Products should not need to know low-level ring addressing details. The junction-based path lets the host abstract away pallet instances and collection ids behind a well-known alias or default path in the future, while the structured response (`ring_index`, `ring_revision`, `proof`) gives products everything they need to call downstream precompiles without any additional on-chain queries. +The host may hold multiple member keys; the API exposes neither the keys nor their ids. The host MUST: -### Migration +1. Define the **"PoP" ring collection** as the collection corresponding to full-personhood rings. +2. Choose a member key that is present in / logically corresponds to the requested `RingLocation`. +3. If correspondence is not determinable, fall back to a key corresponding to the "PoP" ring. +4. If multiple keys correspond to the same ring, consistently pick any one — the choice MUST be stable across calls for the same inputs so the alias is stable. -The `ring_root_hash` field is removed entirely — products no longer need to fetch or compute ring roots. The `hints` field is also removed, as pallet instance addressing is now a first-class junction rather than an optional hint. +**Out of scope:** explicit member-key management (letting the caller reference a specific key rather than having the host infer one) is left to a future RFC — exposing keys or their ids is a separate, larger design with its own privacy considerations. -Existing products using `host_account_create_proof` will need to update their `RingLocation` construction from: +### Errors ```rust -// Before -RingLocation { - genesis_hash: chain_genesis, - ring_root_hash: computed_hash, - hints: Some(RingLocationHint { pallet_instance: Some(42) }) -} - -// After -let location = vec![ - RingLocationJunction::Chain(chain_genesis), - RingLocationJunction::PalletInstance(42), - RingLocationJunction::CollectionId(collection), -]; -let result = host_account_create_proof(domain, location, message)?; -// result.proof — the ring-vrf proof bytes -// result.ring_index — the caller's index in the ring (for precompile calls) -// result.ring_revision — the ring revision the proof was generated against +enum CreateProofErr { RingNotFound, NotMember, Rejected, Unknown } ``` -### Stakeholders +`NotMember` is returned when the selected member key is not a member of the requested ring — most importantly when the user has not yet reached full personhood — letting products distinguish it from `RingNotFound` and route to onboarding. -- **Product developers** — consumers of `host_account_create_proof` who need reliable proof generation without worrying about ring root staleness. -- **Mobile app / host implementors** — responsible for resolving ring locations and generating proofs; benefit from unambiguous ring addressing. +### Usage -### Testing, Security, and Privacy +`ring_root_hash`, `hints`, and the `domain` parameter are gone — products never fetch or hash ring roots or manage derivation indices. -- **Testing**: Implementations should verify that proof generation succeeds even when ring membership changes between request construction and proof generation. The returned `ring_index` and `ring_revision` must match the actual ring state used for the proof. -- **Security**: The host must validate that the junction path resolves to a real ring. Invalid or malicious paths should return `CreateProofErr` rather than panicking or producing invalid proofs. -- **Privacy**: No change from the current model — the same information is exchanged, just addressed differently. +```rust +let location = RingLocation { + chain_id: chain_genesis, + junctions: vec![ + RingLocationJunction::PalletInstance(42), + RingLocationJunction::CollectionId(collection), + ], +}; +let result = host_account_create_proof( + ProductProofContext { product_id, suffix }, + location, + message, +)?; +// result.proof / contextual_alias / ring_index / ring_revision +``` -### Performance, Ergonomics, and Compatibility +## Out of Scope: Product-SDK Helpers (Non-Normative) -#### Performance +These live at the product-sdk level, not in truAPI; the host implements none of them. Documented only because they shape how products build a `ProductProofContext`. -Proof generation performance is unchanged. The host may need an additional lookup to resolve the ring root from the junction path, but this is expected to be negligible compared to the proof computation itself. +**Default context.** For contexts that need no suffix, the sdk can use a canonical default: -#### Ergonomics +```rust +const SINGLETON_PROOF_SUFFIX: [u8; 1] = [0]; +fn singleton_proof_context(product_id: ProductId) -> ProductProofContext { + ProductProofContext { product_id, suffix: SINGLETON_PROOF_SUFFIX.to_vec() } +} +``` -Products no longer need to fetch and hash ring roots before requesting a proof — they only need to know the stable addressing coordinates of the ring. This simplifies the product-side implementation and eliminates a common source of errors. +**Context ↔ accountId linkability.** To set an account as the alias for a context, the sdk needs a canonical suffix → `DerivationIndex` mapping (`host_account_get_account` takes `ProductAccountId = (ProductId, DerivationIndex)`): -#### Compatibility +```rust +fn product_account_id_for_proof_context(product_id: ProductId, suffix: [u8; 4]) -> ProductAccountId { + ProductAccountId { product_id, derivation_index: u32_from_be_bytes(suffix) } +} +fn u32_from_be_bytes(bytes: [u8; 4]) -> u32; // big-endian +``` -This is a breaking change to the `host_account_create_proof` method signature. Both the request type (`RingLocation`) and response type (`RingVrfProof`) are modified. A protocol version bump is required. +Defined only for 4-byte suffixes to keep a bijection with `u32`. Hashing arbitrary bytes down to 4 was rejected — the space is too small (high collision risk). This is a helper-level limit only: truAPI still accepts arbitrary-byte suffixes, so products not needing a 1:1 context→account mapping are unaffected. ## Drawbacks -1. **Breaking change** — All existing consumers of `host_account_create_proof` must update their `RingLocation` construction and handle the new `RingVrfProof` struct instead of raw bytes. -2. **Host complexity** — The host must now resolve the ring root from the junction path internally, which may require additional chain queries. Previously the product supplied the root hash directly. -3. **Junction ordering** — The path is a flat vector with no enforced ordering or validation at the type level. Malformed paths (e.g. missing `Chain` junction) must be handled at runtime. +- **Host complexity** — the host must resolve the root from the junction path and implement member-key selection (PoP fallback + stable tiebreak). +- **No type-level junction validation** — `chain_id` is mandatory, but the `junctions` vector has no enforced ordering; malformed paths are handled at runtime. ## Alternatives -- Keep `ring_root_hash` but add a retry mechanism on the product side — rejected because it doesn't solve the revision visibility problem and adds complexity to every product. -- Use a structured struct instead of junction vec — rejected in favor of extensibility; adding new addressing dimensions would require struct changes. -- XCM `MultiLocation` directly — rejected as overly general for this use case, but the junction pattern is borrowed as inspiration. - -## Unresolved Questions - -1. Should the junction ordering be enforced (e.g. `Chain` must come first), or is any order acceptable as long as the host can resolve it? -2. Should `CollectionId` use a fixed-size type (e.g. `[u8; 32]`) instead of `Vec` for consistency with other on-chain identifiers? -3. Are there ring identification schemes beyond `(collection_id)` that we should anticipate in the junction design? -4. Should `CreateProofErr` gain a new variant (e.g. `RingLocationInvalid`) for unresolvable junction paths, or is the existing `RingNotFound` sufficient? -5. Should the host provide a well-known default `RingLocation` (e.g. for the canonical personhood ring on the relay chain) so products can request proofs without knowing any chain/pallet/collection details? This would address the ergonomics concern raised in [triangle-js-sdks#81](https://github.com/paritytech/triangle-js-sdks/pull/81). +- Keep `ring_root_hash` with product-side retry — doesn't solve revision visibility; adds complexity to every product. +- Keep `domain: ProductAccountId` plus a separate context — keeps the proof keyed by a derived account, not the person. +- Single-`u32` suffix — too narrow; real contexts (pgas claims) need more. +- XCM `MultiLocation` directly — overly general; only the junction pattern is borrowed. ## References - [Host API Design Document v0.5](https://docs.google.com/document/d/1AxKjF15y7gmdl-a6twc5wd8R5xcxKxMO8Ahp2l20v0g/edit?usp=sharing) -- XCM `MultiLocation` junction model — inspiration for the junction-based addressing pattern +- Technical Design: Sybil-Resistant Voting with Personhood — driving product for person-keyed proofs, the `contextual_alias` response, and `NotMember`. - [Polkadot People Registry / Ring VRF](https://forum.polkadot.network/t/the-people-registry/12749) - [individuality#878](https://github.com/paritytech/individuality/pull/878) — alias-account assignment for derived product addresses -- [individuality#891](https://github.com/paritytech/individuality/pull/891) — `personhoodInfoByProof` precompile (motivates `ring_index` in the response) -- [triangle-js-sdks#81 comment](https://github.com/paritytech/triangle-js-sdks/pull/81) — @Zebedeusz's feedback on moving `ring_index` to output and abstraction concerns +- [individuality#891](https://github.com/paritytech/individuality/pull/891) — `personhoodInfoByProof` precompile (motivates the richer response) +- [triangle-js-sdks#81 comment](https://github.com/paritytech/triangle-js-sdks/pull/81) — feedback on moving `ring_index` to output and abstraction concerns From 7979cf18444ee43b5114c5c9793b9b9243e1de92 Mon Sep 17 00:00:00 2001 From: valentunn Date: Mon, 29 Jun 2026 14:07:42 +0300 Subject: [PATCH 4/9] Update RFC 4 --- docs/rfcs/0004-ringlocation-redesign.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/rfcs/0004-ringlocation-redesign.md b/docs/rfcs/0004-ringlocation-redesign.md index 3d52ab51..909fc17f 100644 --- a/docs/rfcs/0004-ringlocation-redesign.md +++ b/docs/rfcs/0004-ringlocation-redesign.md @@ -11,7 +11,7 @@ pr: | | | | --------------- |-----------------------------------------------------------------------------------------------------------| | **Start Date** | 2026-03-16 | -| **Description** | Junction-based RingLocation, person-keyed and product-context-scoped proofs, specified member-key selection | +| **Description** | Junction-based RingLocation, context-scoped proofs, and a specified host member-key selection contract | | **Authors** | Valentin Sergeev | ## Summary @@ -19,7 +19,7 @@ pr: Redesign `host_account_create_proof` and `host_account_get_alias`: 1. **Junction-based ring addressing** — replace the `ring_root_hash`-based `RingLocation` with a struct carrying a required `chain_id` and a `Vec` path of stable, immutable identifiers. -2. **Person-keyed, product-context-scoped proofs** — replace `domain: ProductAccountId` with `ProductProofContext = (ProductId, ProductProofContextSuffix)`. The proof is keyed by the user's person; the context provides unlinkability. +2. **Member-key-based, context-scoped proofs** — replace `domain: ProductAccountId` with `ProductProofContext = (ProductId, ProductProofContextSuffix)`. The proof is created with a member key the host holds (selected for the requested ring); the context scopes the derived alias for unlinkability. 3. **Richer output and errors** — return `contextual_alias`, `ring_index`, and `ring_revision`; specify host member-key selection; add a `NotMember` error. No protocol version bump is required: the current shape of these methods is unusable (the `ring_root_hash` race makes it broken by construction) and is not implemented or consumed anywhere yet, so it can be replaced in place. @@ -29,7 +29,7 @@ No protocol version bump is required: the current shape of these methods is unus - **Request invalidation.** `ring_root_hash` changes whenever ring membership changes, invalidating any in-flight proof request built against the previous root. - **No revision in the response.** Downstream consumers (coinage's recycler transaction extension, the `personhoodInfoByProof` precompile) need the ring revision and index, which the current `Vec` return cannot carry. - **Hints can't address multi-ring pallets.** With the membership pallet, one pallet instance hosts rings from multiple collections, each identified by `(collection_id, ring_index)`. `RingLocationHint`'s optional `pallet_instance` cannot disambiguate them. -- **`domain: ProductAccountId` is the wrong key.** A personhood proof must be keyed by the user's _person_ — `personhoodInfoByProof` verifies the author is a ring member, not that a derived product account exists. Unlinkability comes from the _context_ (same person + different contexts → different aliases), so the request needs an explicit, product-scoped context rather than a derivation index. +- **`domain: ProductAccountId` is the wrong input.** Proof generation depends only on which member key proves membership in the requested ring — the host holds one or more member keys (possibly different keys for different rings) and selects the right one. A derived product account and its derivation index have nothing to do with that. The old signature conflated product-account derivation with proof generation; unlinkability instead comes from the `context` (the same member key under different contexts yields different, unlinkable aliases), so the request needs an explicit, product-scoped context rather than a derivation index. - **Member-key selection is unspecified.** A host may hold several member keys but the API hides them (exposing them leaks identity). Without a defined selection contract, two hosts can derive different aliases for the same request. - **No "not a member" error.** A user who has not reached full personhood is not in the ring. `CreateProofErr` cannot distinguish this from "ring does not exist", so products can't route the user to onboarding. @@ -87,7 +87,7 @@ fn product_context_bytes(context: ProductProofContext) -> [u8; 32] { ### `create_proof` and `get_alias` -The proof is generated against the logged-in user's person; the host selects the member key (see below). Both methods take the same `(context, ring)` so they derive the same alias. +The proof is created with a member key the host holds; the host selects which key based on the requested ring (see below). Both methods take the same `(context, ring)` so they derive the same alias. ```rust struct RingVrfProof { @@ -177,14 +177,14 @@ Defined only for 4-byte suffixes to keep a bijection with `u32`. Hashing arbitra ## Alternatives - Keep `ring_root_hash` with product-side retry — doesn't solve revision visibility; adds complexity to every product. -- Keep `domain: ProductAccountId` plus a separate context — keeps the proof keyed by a derived account, not the person. +- Keep `domain: ProductAccountId` plus a separate context — keeps proof generation tied to a derived product account instead of the host's member key for the ring. - Single-`u32` suffix — too narrow; real contexts (pgas claims) need more. - XCM `MultiLocation` directly — overly general; only the junction pattern is borrowed. ## References - [Host API Design Document v0.5](https://docs.google.com/document/d/1AxKjF15y7gmdl-a6twc5wd8R5xcxKxMO8Ahp2l20v0g/edit?usp=sharing) -- Technical Design: Sybil-Resistant Voting with Personhood — driving product for person-keyed proofs, the `contextual_alias` response, and `NotMember`. +- Technical Design: Sybil-Resistant Voting with Personhood — driving product for the member-key-based proof model, the `contextual_alias` response, and `NotMember`. - [Polkadot People Registry / Ring VRF](https://forum.polkadot.network/t/the-people-registry/12749) - [individuality#878](https://github.com/paritytech/individuality/pull/878) — alias-account assignment for derived product addresses - [individuality#891](https://github.com/paritytech/individuality/pull/891) — `personhoodInfoByProof` precompile (motivates the richer response) From 36ff40492a4201a83c7f136b79cd0072e9c40de3 Mon Sep 17 00:00:00 2001 From: valentunn Date: Mon, 29 Jun 2026 14:15:19 +0300 Subject: [PATCH 5/9] Rust types --- docs/rfcs/0004-ringlocation-redesign.md | 12 ++-- rust/crates/truapi/src/api/account.rs | 25 ++++--- rust/crates/truapi/src/v01/account.rs | 73 ++++++++++++++------- rust/crates/truapi/src/versioned/account.rs | 2 +- 4 files changed, 67 insertions(+), 45 deletions(-) diff --git a/docs/rfcs/0004-ringlocation-redesign.md b/docs/rfcs/0004-ringlocation-redesign.md index 909fc17f..0fb7a868 100644 --- a/docs/rfcs/0004-ringlocation-redesign.md +++ b/docs/rfcs/0004-ringlocation-redesign.md @@ -70,15 +70,11 @@ struct RingLocation { ```rust type ProductProofContextSuffix = Vec; // arbitrary bytes - -struct ProductProofContext { - product_id: ProductId, - suffix: ProductProofContextSuffix, -} +type ProductProofContext = (ProductId, ProductProofContextSuffix); // 32-byte context bound into the proof. fn product_context_bytes(context: ProductProofContext) -> [u8; 32] { - blake2b256(utf8("product/") ++ utf8(context.product_id) ++ utf8("/") ++ context.suffix) + blake2b256(utf8("product/") ++ utf8(context.0) ++ utf8("/") ++ context.1) } ``` @@ -138,7 +134,7 @@ let location = RingLocation { ], }; let result = host_account_create_proof( - ProductProofContext { product_id, suffix }, + (product_id, suffix), location, message, )?; @@ -154,7 +150,7 @@ These live at the product-sdk level, not in truAPI; the host implements none of ```rust const SINGLETON_PROOF_SUFFIX: [u8; 1] = [0]; fn singleton_proof_context(product_id: ProductId) -> ProductProofContext { - ProductProofContext { product_id, suffix: SINGLETON_PROOF_SUFFIX.to_vec() } + (product_id, SINGLETON_PROOF_SUFFIX.to_vec()) } ``` diff --git a/rust/crates/truapi/src/api/account.rs b/rust/crates/truapi/src/api/account.rs index 7c4e065f..968d2256 100644 --- a/rust/crates/truapi/src/api/account.rs +++ b/rust/crates/truapi/src/api/account.rs @@ -53,13 +53,16 @@ pub trait Account: Send + Sync { Err(CallError::unavailable()) } - /// Retrieve a contextual alias for a product account. + /// Retrieve the contextual alias for a context and ring. /// /// ```ts + /// import { PASEO_NEXT_V2_ASSET_HUB } from "@parity/truapi"; + /// /// const result = await truapi.account.getAccountAlias({ - /// productAccountId: { - /// dotNsIdentifier: "truapi-playground.dot", - /// derivationIndex: 0, + /// context: ["truapi-playground.dot", "0x00"], + /// ringLocation: { + /// chainId: PASEO_NEXT_V2_ASSET_HUB.genesis, + /// junctions: [{ palletInstance: 42 }], /// }, /// }); /// assert(result.isOk(), "getAccountAlias failed:", result); @@ -74,22 +77,18 @@ pub trait Account: Send + Sync { Err(CallError::unavailable()) } - /// Generate a ring VRF proof for a product account. + /// Generate a ring VRF proof; the host selects the member key for the ring. /// /// ```ts /// import { PASEO_NEXT_V2_ASSET_HUB } from "@parity/truapi"; /// /// const result = await truapi.account.createAccountProof({ - /// productAccountId: { - /// dotNsIdentifier: "truapi-playground.dot", - /// derivationIndex: 0, - /// }, + /// context: ["truapi-playground.dot", "0x00"], /// ringLocation: { - /// genesisHash: PASEO_NEXT_V2_ASSET_HUB.genesis, - /// ringRootHash: "0xd6eec26135305a8ad257a20d003357284c8aa03d0bdb2b357ab0a22371e11ef2", - /// hints: { palletInstance: 42 }, + /// chainId: PASEO_NEXT_V2_ASSET_HUB.genesis, + /// junctions: [{ palletInstance: 42 }], /// }, - /// context: "0x", + /// message: "0x", /// }); /// assert(result.isOk(), "createAccountProof failed:", result); /// console.log("account proof created:", result.value); diff --git a/rust/crates/truapi/src/v01/account.rs b/rust/crates/truapi/src/v01/account.rs index f3b928ec..b283c309 100644 --- a/rust/crates/truapi/src/v01/account.rs +++ b/rust/crates/truapi/src/v01/account.rs @@ -1,3 +1,4 @@ +use crate::v01::transaction::GenesisHash; use parity_scale_codec::{Decode, Encode}; /// Identifies a product-specific account by combining a dotNS domain name with a @@ -32,40 +33,55 @@ pub struct ProductAccount { /// A privacy-preserving alias derived via ring VRF, bound to a specific context. #[derive(Debug, Clone, PartialEq, Eq, Encode, Decode)] -pub struct HostAccountGetAliasResponse { - /// 32-byte context identifier. +pub struct ContextualAlias { + /// 32-byte context identifier the alias is bound to. pub context: [u8; 32], /// Ring VRF alias (variable length). pub alias: Vec, } -/// Hints for locating a ring on-chain. +/// A single step in a [`RingLocation`] path, addressing a ring within a chain. #[derive(Debug, Clone, PartialEq, Eq, Encode, Decode)] -pub struct RingLocationHint { - /// Optional pallet instance index. - pub pallet_instance: Option, +pub enum RingLocationJunction { + /// Pallet instance hosting the ring collection. + PalletInstance(u8), + /// Ring collection identifier within the pallet. + CollectionId(Vec), } -/// Locates a specific ring on a specific chain for ring VRF operations. +/// Locates a ring for ring VRF operations using only identifiers that are +/// stable across membership changes. #[derive(Debug, Clone, PartialEq, Eq, Encode, Decode)] pub struct RingLocation { - /// Chain genesis hash. - pub genesis_hash: Vec, - /// Root hash of the ring. - pub ring_root_hash: Vec, - /// Optional location hints. - pub hints: Option, + /// Genesis hash of the chain hosting the ring. + pub chain_id: GenesisHash, + /// Path addressing the ring within the chain. + pub junctions: Vec, } -/// Request to create a ring VRF proof for a product account. +/// dotNS product identifier (e.g. `"my-product.dot"`). +pub type ProductId = String; + +/// Arbitrary-byte suffix distinguishing contexts within a single product. +pub type ProductProofContextSuffix = Vec; + +/// A product-scoped proof context: a product and a context within it. +/// +/// Hashed (with a `product//` prefix) into the 32-byte context bound +/// to a ring VRF proof, so contexts cannot collide across products and the same +/// member key under different contexts yields unlinkable aliases. +#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode)] +pub struct ProductProofContext(pub ProductId, pub ProductProofContextSuffix); + +/// Request to create a ring VRF proof. #[derive(Debug, Clone, PartialEq, Eq, Encode, Decode)] pub struct HostAccountCreateProofRequest { - /// Product account that should create the proof. - pub product_account_id: ProductAccountId, - /// Ring location to use for proof generation. + /// Product-scoped context the derived alias is bound to. + pub context: ProductProofContext, + /// Ring to generate the proof against; the host selects the member key. pub ring_location: RingLocation, - /// Context bytes bound to the proof. - pub context: Vec, + /// Opaque message bound into the proof. + pub message: Vec, } /// User's authentication state. @@ -118,6 +134,8 @@ pub enum HostAccountGetError { pub enum HostAccountCreateProofError { /// Ring not available at the specified location. RingNotFound, + /// The selected member key is not a member of the requested ring. + NotMember, /// User or host rejected. Rejected, /// Catch-all. @@ -156,18 +174,27 @@ pub enum HostGetUserIdError { Unknown { reason: String }, } -/// Request to retrieve a contextual alias for a product account. +/// Request to retrieve the contextual alias for a context and ring. #[derive(Debug, Clone, PartialEq, Eq, Encode, Decode)] pub struct HostAccountGetAliasRequest { - /// Product account to derive the alias for. - pub product_account_id: ProductAccountId, + /// Product-scoped context to derive the alias for. + pub context: ProductProofContext, + /// Ring whose member key the host should use; matches `create_proof`. + pub ring_location: RingLocation, } -/// Response containing a ring VRF proof. +/// Response containing a ring VRF proof and the values needed to verify it +/// against a downstream precompile. #[derive(Debug, Clone, PartialEq, Eq, Encode, Decode)] pub struct HostAccountCreateProofResponse { /// Variable-length ring VRF proof bytes. pub proof: Vec, + /// Alias derived for the request's context. + pub contextual_alias: ContextualAlias, + /// Index of the selected member key within the ring. + pub ring_index: u32, + /// Ring revision the proof was generated against. + pub ring_revision: u32, } /// Response containing all legacy (user-imported) accounts owned by the user. diff --git a/rust/crates/truapi/src/versioned/account.rs b/rust/crates/truapi/src/versioned/account.rs index 1d82fc62..750af434 100644 --- a/rust/crates/truapi/src/versioned/account.rs +++ b/rust/crates/truapi/src/versioned/account.rs @@ -7,7 +7,7 @@ truapi_macros::versioned_type! { pub enum HostAccountGetResponse { V1 => v01::HostAccountGetResponse } pub enum HostAccountGetError { V1 => v01::HostAccountGetError } pub enum HostAccountGetAliasRequest { V1 => v01::HostAccountGetAliasRequest } - pub enum HostAccountGetAliasResponse { V1 => v01::HostAccountGetAliasResponse } + pub enum HostAccountGetAliasResponse { V1 => v01::ContextualAlias } pub enum HostAccountGetAliasError { V1 => v01::HostAccountGetError } pub enum HostAccountCreateProofRequest { V1 => v01::HostAccountCreateProofRequest } pub enum HostAccountCreateProofResponse { V1 => v01::HostAccountCreateProofResponse } From c3798e6c634431c0b3e3a45306327b4dd8245e0b Mon Sep 17 00:00:00 2001 From: valentunn <70131744+valentunn@users.noreply.github.com> Date: Mon, 29 Jun 2026 14:21:54 +0300 Subject: [PATCH 6/9] Update 0004-ringlocation-redesign.md --- docs/rfcs/0004-ringlocation-redesign.md | 8 -------- 1 file changed, 8 deletions(-) diff --git a/docs/rfcs/0004-ringlocation-redesign.md b/docs/rfcs/0004-ringlocation-redesign.md index 0fb7a868..e8102fb8 100644 --- a/docs/rfcs/0004-ringlocation-redesign.md +++ b/docs/rfcs/0004-ringlocation-redesign.md @@ -1,11 +1,3 @@ ---- -title: "Redesign RingLocation in host_account_create_proof" -type: rfc -status: draft -owner: "@valentin-parity" -pr: ---- - # RFC 0004 — Redesign RingLocation in `host_account_create_proof` | | | From bf8c006e665ea21c771531aee529528d2f6b3739 Mon Sep 17 00:00:00 2001 From: valentunn <70131744+valentunn@users.noreply.github.com> Date: Mon, 29 Jun 2026 14:22:47 +0300 Subject: [PATCH 7/9] Update 0004-ringlocation-redesign.md --- docs/rfcs/0004-ringlocation-redesign.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/rfcs/0004-ringlocation-redesign.md b/docs/rfcs/0004-ringlocation-redesign.md index e8102fb8..2463fee8 100644 --- a/docs/rfcs/0004-ringlocation-redesign.md +++ b/docs/rfcs/0004-ringlocation-redesign.md @@ -1,4 +1,4 @@ -# RFC 0004 — Redesign RingLocation in `host_account_create_proof` +# RFC 0004 — Redesign `host_account_create_proof` | | | | --------------- |-----------------------------------------------------------------------------------------------------------| From b9ee7e1e4246196be5c79e41a43e4df85c0caefe Mon Sep 17 00:00:00 2001 From: valentunn Date: Mon, 29 Jun 2026 15:10:20 +0300 Subject: [PATCH 8/9] =?UTF-8?q?=D0=95=D1=8B=20types?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- rust/crates/truapi/src/api/account.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rust/crates/truapi/src/api/account.rs b/rust/crates/truapi/src/api/account.rs index 968d2256..8b45fd9a 100644 --- a/rust/crates/truapi/src/api/account.rs +++ b/rust/crates/truapi/src/api/account.rs @@ -62,7 +62,7 @@ pub trait Account: Send + Sync { /// context: ["truapi-playground.dot", "0x00"], /// ringLocation: { /// chainId: PASEO_NEXT_V2_ASSET_HUB.genesis, - /// junctions: [{ palletInstance: 42 }], + /// junctions: [{ tag: "PalletInstance", value: 42 }], /// }, /// }); /// assert(result.isOk(), "getAccountAlias failed:", result); @@ -86,7 +86,7 @@ pub trait Account: Send + Sync { /// context: ["truapi-playground.dot", "0x00"], /// ringLocation: { /// chainId: PASEO_NEXT_V2_ASSET_HUB.genesis, - /// junctions: [{ palletInstance: 42 }], + /// junctions: [{ tag: "PalletInstance", value: 42 }], /// }, /// message: "0x", /// }); From 77b37e8fa9ba471c0376fc5ffea9503300daba1b Mon Sep 17 00:00:00 2001 From: valentunn Date: Tue, 30 Jun 2026 17:24:38 +0300 Subject: [PATCH 9/9] Include SSO companion --- docs/rfcs/0004-ringlocation-redesign.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/docs/rfcs/0004-ringlocation-redesign.md b/docs/rfcs/0004-ringlocation-redesign.md index 2463fee8..36b714fb 100644 --- a/docs/rfcs/0004-ringlocation-redesign.md +++ b/docs/rfcs/0004-ringlocation-redesign.md @@ -133,6 +133,27 @@ let result = host_account_create_proof( // result.proof / contextual_alias / ring_index / ring_revision ``` +### Accounts Protocol companion + +The methods above are the **TrUAPI** boundary (product ↔ Host). The same operations also cross the **Accounts Protocol** boundary (Host ↔ Account Holder, which custodies the member keys and does the selection and proof generation). The companion methods reuse the same types, with `calling_product_id: ProductId` prepended — at the TrUAPI boundary the Host already knows the calling product, but here it is the caller acting on a product's behalf, so the Account Holder needs it named to scope context derivation and permissioning: + +```rust +fn create_account_proof( + calling_product_id: ProductId, + context: ProductProofContext, + ring: RingLocation, + message: Vec, +) -> Result; + +fn get_account_alias( + calling_product_id: ProductId, + context: ProductProofContext, + ring: RingLocation, +) -> Result; +``` + +The two boundaries are kept as distinct method sets so they can evolve independently, even though they currently share request/response shapes. + ## Out of Scope: Product-SDK Helpers (Non-Normative) These live at the product-sdk level, not in truAPI; the host implements none of them. Documented only because they shape how products build a `ProductProofContext`.