Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,10 @@ SSO transport for local end-to-end work. See [Install the CLI](#install-the-cli)
to get it, and the [`truapi-host-cli` guide](rust/crates/truapi-host-cli/README.md)
for its commands and controls.

CLI reserved identities follow the selected network's dotNS suffix. Old account
and pairing stores are left unused as the CLI starts fresh under its
[versioned state directory](rust/crates/truapi-host-cli/README.md#state-directory).

`scripts/battery.sh` drives that CLI from source over every code-generated
example and writes both committed compatibility reports:
`explorer/diagnosis-reports/spa/signing-host-cli.md` from a direct signing-host
Expand Down
8 changes: 8 additions & 0 deletions android/truapi-host/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ The package is public, so any authenticated GitHub identity can read it. In GitH

The consuming app must declare `android.permission.INTERNET` — the localhost WebSocket bridge binds a `127.0.0.1` TCP socket, which requires it even for loopback.

`HostRuntimeConfig.networkSuffix` is required. Supply the bare TLD (`dot`,
`paseo`, or `testnet`) from the same network configuration used by onboarding
and the People/Bulletin genesis hashes. It must match the People chain's
`NetworkSuffix.NetworkSuffix`. Include this configuration update in the
embedding app's package upgrade.

### Compatibility

- **minSdk**: 29 (Android 10). Aligns with the polkadot-app-android-v2 floor.
Expand Down Expand Up @@ -96,6 +102,7 @@ val runtime = TrUAPIHostRuntime(
hostName = "My Chat Host",
peopleChainGenesisHash = peopleChainGenesisHash, // exactly 32 bytes
bulletinChainGenesisHash = bulletinChainGenesisHash,
networkSuffix = "dot",
),
)
// Chat needs an active session; without one every Chat call answers `Denied`.
Expand Down Expand Up @@ -332,6 +339,7 @@ val runtimeConfig = HostRuntimeConfig(
hostIcon = "https://host.example/icon.png",
peopleChainGenesisHash = ByteArray(32),
bulletinChainGenesisHash = ByteArray(32),
networkSuffix = "dot",
// Optional: activate a local signing session from host-held BIP-39 entropy
// (no SSO pairing). Omit for the QR pairing flow.
localSessionSecret = null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,10 @@ enum class ProductExecutionKind {
/**
* Immutable process-wide configuration shared by every product execution
* opened from one [TrUAPIHostRuntime]. [peopleChainGenesisHash] and
* [bulletinChainGenesisHash] must each be exactly 32 bytes.
* [bulletinChainGenesisHash] must each be exactly 32 bytes. [networkSuffix] is
* the network's dotNS TLD without the leading dot (`dot`, `paseo`, `testnet`);
* the core derives the wallet's reserved identities under it (`uid.<suffix>`,
* `peopl.<suffix>`), the same person the app's own onboarding derives there.
*/
data class HostRuntimeConfig(
val hostName: String,
Expand All @@ -106,6 +109,7 @@ data class HostRuntimeConfig(
val platformVersion: String? = null,
val peopleChainGenesisHash: ByteArray,
val bulletinChainGenesisHash: ByteArray,
val networkSuffix: String,
val localSessionSecret: ByteArray? = null,
val localSessionLiteUsername: String? = null,
) {
Expand All @@ -119,6 +123,7 @@ data class HostRuntimeConfig(
platformVersion = platformVersion,
peopleChainGenesisHash = peopleChainGenesisHash,
bulletinChainGenesisHash = bulletinChainGenesisHash,
networkSuffix = networkSuffix,
localSessionSecret = localSessionSecret,
localSessionLiteUsername = localSessionLiteUsername,
)
Expand All @@ -133,6 +138,7 @@ data class HostRuntimeConfig(
platformVersion == other.platformVersion &&
peopleChainGenesisHash.contentEquals(other.peopleChainGenesisHash) &&
bulletinChainGenesisHash.contentEquals(other.bulletinChainGenesisHash) &&
networkSuffix == other.networkSuffix &&
localSessionSecret.contentEquals(other.localSessionSecret) &&
localSessionLiteUsername == other.localSessionLiteUsername
}
Expand All @@ -145,6 +151,7 @@ data class HostRuntimeConfig(
result = 31 * result + (platformVersion?.hashCode() ?: 0)
result = 31 * result + peopleChainGenesisHash.contentHashCode()
result = 31 * result + bulletinChainGenesisHash.contentHashCode()
result = 31 * result + networkSuffix.hashCode()
result = 31 * result + (localSessionSecret?.contentHashCode() ?: 0)
result = 31 * result + (localSessionLiteUsername?.hashCode() ?: 0)
return result
Expand Down
26 changes: 20 additions & 6 deletions docs/rfcs/0022-account-derivations.md
Original file line number Diff line number Diff line change
Expand Up @@ -270,10 +270,18 @@ reserved product identities as their `productId`:
| Migrating to a product soon | Game (DIM2) | `dim2.dot` | Governance-reserved 3–5 char name |
| Migrating long-term / product-shaped | PoI (DIM1) | `poi.dot` | Governance-reserved 3–5 char name |
| Migrating long-term / product-shaped | Funding | `fund.dot` | Governance-reserved 3–5 char name |
| Migrating long-term / product-shaped | Public light person identity | `uid.dot` | Governance-reserved 3–5 char name |
| Migrating long-term / product-shaped | Personhood | `peopl.dot` | Governance-reserved 3–5 char name |
| Migrating long-term / product-shaped | Public light person identity | `uid.<tld>` | Governance-reserved 3–5 char name |
| Migrating long-term / product-shaped | Personhood | `peopl.<tld>`| Governance-reserved 3–5 char name |
| Not coercible to a product | Coinage | — | Deferred to a separate RFC (own layout today: `//pps//coin/{index}`, `//pps//ring-vrf/{index}`) |

A reserved `productId` is a dotNS name like any other, so it ends in the TLD of
the network the host runs against: `uid.dot` and `peopl.dot` on Polkadot,
`uid.paseo` and `peopl.paseo` on paseo-next-v2, `uid.testnet` and
`peopl.testnet` on previewnet. The TLD is the same network suffix the People
runtime scopes its product contexts with (`product/peopl.<tld>/…`), so accounts,
contexts and keys agree on which network a person belongs to, and one seed is one
person per network. A host learns the suffix from the network it is configured
for (`SigningHostConfig::network_suffix` in the Rust core) and never assumes it.
### Well-known alias accounts

The runtime defines well-known Account Contexts (`resources`, `score`,
Expand Down Expand Up @@ -322,16 +330,22 @@ reserved product identity from the table above. `DerivationIndex` is the same
32-byte index format as product accounts, so each domain gets its own index
space.

The personhood keys live under the `peopl.dot` domain:
The personhood keys live under the `peopl.<tld>` domain of the network:

```rust
// Full personhood ring-VRF key
full_personhood_key = //peopl.dot//index_bytes(0)
full_personhood_key = //peopl.<tld>//index_bytes(0)

// Light personhood ring-VRF key
light_personhood_key = //peopl.dot//index_bytes(1)
light_personhood_key = //peopl.<tld>//index_bytes(1)
```

On Polkadot these are `//peopl.dot//index_bytes(0)` and
`//peopl.dot//index_bytes(1)`; on paseo-next-v2 the same seed yields the
`peopl.paseo` keys, a different pair. The `peopl.<tld>` domain is also what the
personhood product on that network derives from when it registers its keys
under RFC-0024, so the reserved keys and the product's own registry entries are
the same bytes.
Existing keys migrate to these paths. Coinage's ring-VRF keys
(recyclers/vouchers) are deferred to the coinage RFC.

Expand Down Expand Up @@ -384,7 +398,7 @@ game_domain = "game"
There are no production deployments of secret-component derivations or of the
`u32`-index wire types; the selector change is wire-breaking for
`ProductAccountId`, `ProductProofContext`, `PaymentTopUpSource`, and
`AllocatableResource`, and is made freely, with no migration path. Existing ring-VRF keys move to their `peopl.dot`
`AllocatableResource`, and is made freely, with no migration path. Existing ring-VRF keys move to their `peopl.<tld>`
paths; deployed encryption keys are handled by the encryption RFC.

## Drawbacks
Expand Down
4 changes: 2 additions & 2 deletions docs/rfcs/0024-personhood-as-product.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ A proof is a bearer token for its context's alias and a signature is a bearer to

**Personhood is welded into the Host.** RFC-0004 §"Host member-key selection" requires every Host to define the PoP ring collection internally, choose a member key corresponding to the requested `RingLocation`, fall back to the PoP key when correspondence is undeterminable, and tiebreak stably. `truapi-server` implements exactly that with the ring identities compiled in (`rust/crates/truapi-server/src/runtime/signing_host/ring_vrf.rs`: `FULL_PERSON_COLLECTION`, `LITE_PERSON_COLLECTION`, `enum PersonKey { Full, Lite }`). So every change to how a person key is derived, registered, renewed, or recovered is a Host release.

A personhood product must instead own the full and light keys — under RFC-0022, the `peopl.dot` domain of the ring-VRF tree — while telling the Host and Account Holder enough to keep serving the app's own personhood-dependent features, and lending its keys and aliases to other products. The binding constraint across all of it: **no consumer may know which key is used**, not the app and not a calling product.
A personhood product must instead own the full and light keys — under RFC-0022, the `peopl.<tld>` domain of the ring-VRF tree, `peopl.dot` on Polkadot — while telling the Host and Account Holder enough to keep serving the app's own personhood-dependent features, and lending its keys and aliases to other products. The binding constraint across all of it: **no consumer may know which key is used**, not the app and not a calling product.

**The obstacle** is that the member keys serve three overlapping classes of work, and only one is not extractable:

Expand Down Expand Up @@ -108,7 +108,7 @@ fn list_ring_vrf_keys(
- **Registration declares intent, not membership.** It means "this is the key I will use for that ring", not "the user is a person"; membership is still discovered only by attempting a proof, which returns `NotMember` (RFC-0004). This keeps the registry from being a personhood oracle.
- **The public key is owner-visible by default, permissioned cross-product**, because a member public key is linkable across every ring it appears in.

RFC-0022 already pins `//peopl.dot//index_bytes(0)` as the full personhood key and `index_bytes(1)` as the light one. Under this RFC those constants are the personhood product's own implementation detail, expressed to everyone else as two registry entries.
RFC-0022 already pins `//peopl.<tld>//index_bytes(0)` as the full personhood key and `index_bytes(1)` as the light one, under the TLD of the network. Under this RFC those constants are the personhood product's own implementation detail, expressed to everyone else as two registry entries. The examples below are written for Polkadot, where the product is `peopl.dot`; on paseo-next-v2 read `peopl.paseo` throughout.

### Proofs, aliases, and signatures take an explicit key handle

Expand Down
12 changes: 10 additions & 2 deletions ios/truapi-host/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,12 @@ manifest PR to keep `main` current. SPM pins the resolved revision in the app's
`Package.resolved`; update it with File > Packages > Update in Xcode or
`xcodebuild -resolvePackageDependencies` after the tag is published.

`HostRuntimeConfig.networkSuffix` is required. Supply the bare TLD (`dot`,
`paseo`, or `testnet`) from the same network configuration used by onboarding
and the People/Bulletin genesis hashes. It must match the People chain's
`NetworkSuffix.NetworkSuffix`. Include this configuration update in the
embedding app's package upgrade.

Run the package tests against an iOS simulator (the xcframework has no macOS slice):

```bash
Expand Down Expand Up @@ -140,7 +146,8 @@ let runtime = try TrUAPIHostRuntime(
runtimeConfig: HostRuntimeConfig(
hostName: "My Chat Host",
peopleChainGenesisHash: peopleChainGenesisHash, // exactly 32 bytes
bulletinChainGenesisHash: bulletinChainGenesisHash
bulletinChainGenesisHash: bulletinChainGenesisHash,
networkSuffix: "dot"
)
)
// Chat needs an active session; without one every Chat call answers denied.
Expand Down Expand Up @@ -378,7 +385,8 @@ let runtimeConfig = HostRuntimeConfig(
hostName: "My Host",
hostIcon: "https://host.example/icon.png",
peopleChainGenesisHash: Data(repeating: 0, count: 32),
bulletinChainGenesisHash: Data(repeating: 0, count: 32)
bulletinChainGenesisHash: Data(repeating: 0, count: 32),
networkSuffix: "dot"
)
let runtime = try TrUAPIHostRuntime(bridge: bridge, runtimeConfig: runtimeConfig)
try runtime.activateLocalSession(secret: entropyBytes, liteUsername: nil)
Expand Down
9 changes: 9 additions & 0 deletions ios/truapi-host/Sources/TrUAPIHost/TrUAPIHost.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ public struct HostRuntimeConfig: Sendable, Equatable {
public let platformVersion: String?
public let peopleChainGenesisHash: Data
public let bulletinChainGenesisHash: Data
/// The network's dotNS TLD without the leading dot (`dot`, `paseo`,
/// `testnet`). The core derives the wallet's reserved identities under it:
/// `uid.<suffix>` for the identity account and `peopl.<suffix>` for the
/// person ring-VRF keys, the same person the app's own onboarding derives
/// on that network.
public let networkSuffix: String
public let localSessionSecret: Data?
public let localSessionLiteUsername: String?

Expand All @@ -40,6 +46,7 @@ public struct HostRuntimeConfig: Sendable, Equatable {
platformVersion: String? = nil,
peopleChainGenesisHash: Data,
bulletinChainGenesisHash: Data,
networkSuffix: String,
localSessionSecret: Data? = nil,
localSessionLiteUsername: String? = nil
) {
Expand All @@ -50,6 +57,7 @@ public struct HostRuntimeConfig: Sendable, Equatable {
self.platformVersion = platformVersion
self.peopleChainGenesisHash = peopleChainGenesisHash
self.bulletinChainGenesisHash = bulletinChainGenesisHash
self.networkSuffix = networkSuffix
self.localSessionSecret = localSessionSecret
self.localSessionLiteUsername = localSessionLiteUsername
}
Expand All @@ -64,6 +72,7 @@ public struct HostRuntimeConfig: Sendable, Equatable {
platformVersion: platformVersion,
peopleChainGenesisHash: peopleChainGenesisHash,
bulletinChainGenesisHash: bulletinChainGenesisHash,
networkSuffix: networkSuffix,
localSessionSecret: localSessionSecret,
localSessionLiteUsername: localSessionLiteUsername
)
Expand Down
3 changes: 2 additions & 1 deletion ios/truapi-host/Tests/TrUAPIWsBridgeTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,8 @@ private extension TrUAPIWsBridgeTests {
HostRuntimeConfig(
hostName: "truapi-host-tests",
peopleChainGenesisHash: Data(repeating: 0, count: 32),
bulletinChainGenesisHash: Data(repeating: 0, count: 32)
bulletinChainGenesisHash: Data(repeating: 0, count: 32),
networkSuffix: "paseo"
)
}

Expand Down
8 changes: 8 additions & 0 deletions js/packages/truapi-host/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,14 @@ The package exposes tree-shakeable subpath exports — import only what your env
| `@parity/truapi-host/worker-runtime` | Web Worker entrypoint (import with your bundler's `?worker` suffix) so the WASM core runs off the page main thread. |
| `@parity/truapi-host/wasm/web` | The raw browser `wasm-bindgen` glue, if you need to instantiate the core yourself. |

The shipped WASM is built by `scripts/build-wasm.mjs` with
`--no-default-features`, so it excludes `WasmSigningHostRuntime`.
`ProductRuntimeConfig` configures the pairing host and requires no network
suffix. A custom build enabling the Rust `wasm-signing-host` feature exposes
the signing constructor, whose configuration requires
`runtimeConfig.networkSuffix`: the bare TLD (`dot`, `paseo`, or `testnet`)
matching the People chain and the wallet's onboarding configuration.

## Bundler requirements

The worker imports the WASM glue by a literal specifier, so every bundler
Expand Down
Loading