From 7709867c24005c5d4337225135d99f96728d4977 Mon Sep 17 00:00:00 2001 From: Filippo Vecchiato Date: Fri, 17 Apr 2026 18:04:36 +0200 Subject: [PATCH 1/3] =?UTF-8?q?docs:=20RFC=200011=20=E2=80=94=20Contacts?= =?UTF-8?q?=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduce host_contacts_get and host_contacts_subscribe methods. Contacts are scoped by DotNS path (same as Ring VRF context) with two access tiers: own-context (default) and cross-context (privileged). --- docs/rfcs/0011-contacts-api.md | 118 +++++++++++++++++++++++++++++++++ docs/rfcs/_index.md | 1 + 2 files changed, 119 insertions(+) create mode 100644 docs/rfcs/0011-contacts-api.md diff --git a/docs/rfcs/0011-contacts-api.md b/docs/rfcs/0011-contacts-api.md new file mode 100644 index 00000000..96d8d4b8 --- /dev/null +++ b/docs/rfcs/0011-contacts-api.md @@ -0,0 +1,118 @@ +# RFC-0011: Contacts API + +| | | +| --------------- | --------------------------------------------------------------- | +| **Start Date** | 2026-04-17 | +| **Description** | Expose the user's contact list to products via Host API | +| **Authors** | Filippo Vecchiato | + +## Summary + +Products can read the user's contact list. Each contact pairs local metadata with a context-scoped map keyed by DotNS path (the same identifier used for Ring VRF alias derivation). By default a product only sees entries for its own context; cross-context access is a separate privilege. + +## Motivation + +Products need to resolve human-readable identities to accounts. Without a contacts API, users must paste raw keys or scan QR codes for every interaction. + +The host already manages a contact list. Exposing it: + +1. **Removes friction** — products can show names instead of raw addresses. +2. **Enables cross-product identity** — multiple products resolve the same contact within their respective contexts. +3. **Preserves user control** — the host gates access and filters responses to the requesting product's scope. +4. **Supports contextual accounts** — a contact has different aliases and accounts per DotNS context, preserving unlinkability. + +## Detailed Design + +### Data Model + +```rust +type ContactContext = ProductAccountId; // (DotNsIdentifier, DerivationIndex) + +struct ContextContactInfo { + alias: Option>, + account_id: Option +} + +struct LocalContactInfo { + display_name: Option +} + +struct Contact { + local: LocalContactInfo, + entries: Map +} +``` + +`ContactContext` is a DotNS path — the same as Ring VRF contexts. `ContextContactInfo` fields are optional; either or both may be present. + +### Access Tiers + +#### Tier 1: Own-context (default) + +The host filters `entries` to only the requesting product's DotNS path. `LocalContactInfo` is always included. + +#### Tier 2: Cross-context (privileged) + +Returns the full `entries` map. Required for products that aggregate identities across contexts (profile, honour). + +### API + +```rust +enum ContactsErr { + NotConnected, + Rejected, + Unknown(GenericErr) +} + +fn host_contacts_get() -> Result, ContactsErr>; + +fn host_contacts_subscribe( + callback: fn(Vec) +) -> Result; +``` + +Both require authentication (RFC-0009). The host prompts for permission before returning. `host_contacts_subscribe` delivers the full filtered list on each callback; hosts MAY debounce. + +### Permission Model + +Uses RFC-0002: + +| Permission | Tier | Grants | +|-----------|------|--------| +| `Contacts` | 1 | Own-context entries + local info | +| `ContactsCrossContext` | 2 | Full entries across all contexts | + +The tier 2 prompt SHOULD warn that the product can correlate contacts across contexts. + +### Privacy-Preserving Display + +The host can render a contact picker in a privileged overlay using full contact data, returning only the selected contact's own-context entry to the product. This lets users see rich details without the product receiving cross-context data. The overlay mechanism is host-specific and out of scope. + +## Drawbacks + +- **Privacy surface.** Even tier 1 reveals the user's social graph. The permission prompt mitigates but does not eliminate this. +- **Full-list delivery.** No per-contact queries. The overlay pattern partially addresses this for picker UIs. +- **Read-only.** Products cannot add contacts. Deferred intentionally. + +## Alternatives + +### A: Freeform context keys + +Loses alignment with Ring VRF contexts and makes scoping ambiguous. + +### B: Per-contact lookup by alias + +Requires knowing the alias upfront; does not support browsing. + +### C: No context scoping + +Breaks unlinkability — any product could correlate aliases across all contexts. + +## Unresolved Questions + +1. **Honour.** Needs a protected path so UAs can display honour without exposing the alias to the product. Whether honour is per-product or universal (or both) needs design. Likely a separate RFC. +2. **Common triage contexts.** Should well-known contexts (profile, honour) have a lighter permission model? +3. **Contact mutation.** Write access deferred to a follow-up RFC. +4. **Filtered subscriptions.** Should tier 2 `host_contacts_subscribe` accept a context filter? +5. **Overlay specification.** The exact overlay mechanism needs its own spec. +6. **Pagination.** May be needed for large contact lists. diff --git a/docs/rfcs/_index.md b/docs/rfcs/_index.md index 37275737..be0359c4 100644 --- a/docs/rfcs/_index.md +++ b/docs/rfcs/_index.md @@ -17,3 +17,4 @@ created: 2026-03-13 | 0008 | [Statement Store Host API v0.2](0008-statement-store.md) | accepted | @johnthecat | [#118](https://github.com/paritytech/triangle-js-sdks/pull/118) | [v0.7 → Statement Store Subscribe API](../migration/v0.7.md#statement-store-subscribe-api-rfc-0008) | | 0009 | [Unauthenticated Product Access](0009-unauthenticated-product-access.md) | accepted | @filvecchiato | [#128](https://github.com/paritytech/triangle-js-sdks/pull/128) | [v0.7 → Request Login](../migration/v0.7.md#new-host_request_login-rfc-0009) | | 0010 | [Root account access Host API](0010-get-root-account.md) | accepted | @johnthecat | [#126](https://github.com/paritytech/triangle-js-sdks/pull/126) | [v0.7 → Root Account](../migration/v0.7.md#requesting-root-account-from-product) | +| 0011 | [Contacts API](0011-contacts-api.md) | draft | @filvecchiato | - | - | From 47015985014f7128a1c745bd542236ec80bf9319 Mon Sep 17 00:00:00 2001 From: Filippo Vecchiato Date: Mon, 20 Apr 2026 16:30:54 +0200 Subject: [PATCH 2/3] docs: address review feedback on RFC-0011 - Change ContactContext from ProductAccountId to DotNsIdentifier - Clarify this is the user's address book, not a global resolver - Specify Contacts/ContactsCrossContext as DevicePermission variants - Add concrete tier 1 example - Address host-privileged product access (Browse) --- docs/rfcs/0011-contacts-api.md | 52 +++++++++++++++++++++++++++------- 1 file changed, 42 insertions(+), 10 deletions(-) diff --git a/docs/rfcs/0011-contacts-api.md b/docs/rfcs/0011-contacts-api.md index 96d8d4b8..e79b1bd2 100644 --- a/docs/rfcs/0011-contacts-api.md +++ b/docs/rfcs/0011-contacts-api.md @@ -8,15 +8,15 @@ ## Summary -Products can read the user's contact list. Each contact pairs local metadata with a context-scoped map keyed by DotNS path (the same identifier used for Ring VRF alias derivation). By default a product only sees entries for its own context; cross-context access is a separate privilege. +Products can read the user's host-managed address book. Each contact pairs local metadata with a context-scoped map keyed by `DotNsIdentifier` — the same namespace used for Ring VRF alias derivation. By default a product only sees entries for its own context; cross-context access is a separate privilege. ## Motivation -Products need to resolve human-readable identities to accounts. Without a contacts API, users must paste raw keys or scan QR codes for every interaction. +Products need to resolve human-readable identities to accounts. The host manages an address book but does not expose it. Without this API, users must paste raw keys or scan QR codes for every interaction. -The host already manages a contact list. Exposing it: +Exposing the contact list: -1. **Removes friction** — products can show names instead of raw addresses. +1. **Removes friction** — products show names instead of raw addresses. 2. **Enables cross-product identity** — multiple products resolve the same contact within their respective contexts. 3. **Preserves user control** — the host gates access and filters responses to the requesting product's scope. 4. **Supports contextual accounts** — a contact has different aliases and accounts per DotNS context, preserving unlinkability. @@ -26,7 +26,7 @@ The host already manages a contact list. Exposing it: ### Data Model ```rust -type ContactContext = ProductAccountId; // (DotNsIdentifier, DerivationIndex) +type ContactContext = DotNsIdentifier; // str struct ContextContactInfo { alias: Option>, @@ -43,17 +43,19 @@ struct Contact { } ``` -`ContactContext` is a DotNS path — the same as Ring VRF contexts. `ContextContactInfo` fields are optional; either or both may be present. +`ContactContext` is a `DotNsIdentifier` (a human-readable string). The host derives the `[u8; 32]` Ring VRF context by hashing this identifier — the same derivation used elsewhere in the protocol. The contacts map is keyed by the string form; the host performs the hash internally when resolving aliases. + +`ContextContactInfo` fields are optional; either or both may be present. ### Access Tiers #### Tier 1: Own-context (default) -The host filters `entries` to only the requesting product's DotNS path. `LocalContactInfo` is always included. +The host filters `entries` to only the requesting product's `DotNsIdentifier`. `LocalContactInfo` is always included. This is safe because the product could already derive this information through its own alias system. #### Tier 2: Cross-context (privileged) -Returns the full `entries` map. Required for products that aggregate identities across contexts (profile, honour). +Returns the full `entries` map. Required for host-privileged products that aggregate identities across contexts (e.g. Browse, profile, honour). The host MAY grant implicit tier 2 access to built-in host products that need it for their core function (e.g. a contact management UI). ### API @@ -73,16 +75,46 @@ fn host_contacts_subscribe( Both require authentication (RFC-0009). The host prompts for permission before returning. `host_contacts_subscribe` delivers the full filtered list on each callback; hosts MAY debounce. +This API returns only contacts the user has explicitly saved in their address book. It is not a global name resolution service — resolving arbitrary accounts to DotNS names is a separate concern (on-chain DotNS lookup). + ### Permission Model -Uses RFC-0002: +Extends `DevicePermission` from RFC-0002 with two new variants: + +```rust +enum DevicePermission { + // ... existing variants ... + Contacts, + ContactsCrossContext +} +``` | Permission | Tier | Grants | |-----------|------|--------| | `Contacts` | 1 | Own-context entries + local info | | `ContactsCrossContext` | 2 | Full entries across all contexts | -The tier 2 prompt SHOULD warn that the product can correlate contacts across contexts. +The tier 2 prompt SHOULD warn that the product can correlate contacts across contexts. `ContactsCrossContext` implies `Contacts`. + +### Example + +``` +Product "voting.dot" calls host_contacts_get(): + +→ Host checks DevicePermission::Contacts grant +→ Host filters each contact's entries to key "voting.dot" +→ Returns: + [ + Contact { + local: { display_name: "Alice" }, + entries: { "voting.dot": { alias: 0xab.., account_id: 0x12.. } } + }, + Contact { + local: { display_name: "Bob" }, + entries: {} // Bob has no entry in "voting.dot" context + } + ] +``` ### Privacy-Preserving Display From 858068464f6bdd4504f97f7ea012dd633b102d3c Mon Sep 17 00:00:00 2001 From: Filippo Vecchiato Date: Mon, 20 Apr 2026 17:00:18 +0200 Subject: [PATCH 3/3] docs: revert ContactContext back to ProductAccountId MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DerivationIndex is needed — a product may have multiple derived accounts under the same DotNsIdentifier. --- docs/rfcs/0011-contacts-api.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/rfcs/0011-contacts-api.md b/docs/rfcs/0011-contacts-api.md index e79b1bd2..9b3917bd 100644 --- a/docs/rfcs/0011-contacts-api.md +++ b/docs/rfcs/0011-contacts-api.md @@ -8,7 +8,7 @@ ## Summary -Products can read the user's host-managed address book. Each contact pairs local metadata with a context-scoped map keyed by `DotNsIdentifier` — the same namespace used for Ring VRF alias derivation. By default a product only sees entries for its own context; cross-context access is a separate privilege. +Products can read the user's host-managed address book. Each contact pairs local metadata with a context-scoped map keyed by `ProductAccountId` (`DotNsIdentifier` + `DerivationIndex`) — the same namespace used for Ring VRF alias derivation. By default a product only sees entries for its own context; cross-context access is a separate privilege. ## Motivation @@ -26,7 +26,7 @@ Exposing the contact list: ### Data Model ```rust -type ContactContext = DotNsIdentifier; // str +type ContactContext = ProductAccountId; // (DotNsIdentifier, DerivationIndex) struct ContextContactInfo { alias: Option>, @@ -43,7 +43,7 @@ struct Contact { } ``` -`ContactContext` is a `DotNsIdentifier` (a human-readable string). The host derives the `[u8; 32]` Ring VRF context by hashing this identifier — the same derivation used elsewhere in the protocol. The contacts map is keyed by the string form; the host performs the hash internally when resolving aliases. +`ContactContext` is a `ProductAccountId` (`DotNsIdentifier` + `DerivationIndex`) — the same tuple used for Ring VRF alias derivation. The host derives the `[u8; 32]` Ring VRF context by hashing this identifier internally. `ContextContactInfo` fields are optional; either or both may be present. @@ -51,7 +51,7 @@ struct Contact { #### Tier 1: Own-context (default) -The host filters `entries` to only the requesting product's `DotNsIdentifier`. `LocalContactInfo` is always included. This is safe because the product could already derive this information through its own alias system. +The host filters `entries` to only the requesting product's `ProductAccountId`. `LocalContactInfo` is always included. This is safe because the product could already derive this information through its own alias system. #### Tier 2: Cross-context (privileged) @@ -99,19 +99,19 @@ The tier 2 prompt SHOULD warn that the product can correlate contacts across con ### Example ``` -Product "voting.dot" calls host_contacts_get(): +Product ("voting.dot", 0) calls host_contacts_get(): → Host checks DevicePermission::Contacts grant -→ Host filters each contact's entries to key "voting.dot" +→ Host filters each contact's entries to key ("voting.dot", 0) → Returns: [ Contact { local: { display_name: "Alice" }, - entries: { "voting.dot": { alias: 0xab.., account_id: 0x12.. } } + entries: { ("voting.dot", 0): { alias: 0xab.., account_id: 0x12.. } } }, Contact { local: { display_name: "Bob" }, - entries: {} // Bob has no entry in "voting.dot" context + entries: {} // Bob has no entry in ("voting.dot", 0) context } ] ```