From 71ea3221f5e817c46959249029511866fc996d08 Mon Sep 17 00:00:00 2001 From: Zebedeusz Date: Thu, 6 Aug 2026 11:34:06 +0200 Subject: [PATCH] Person's usernames - read from AH dotNS instead of PC --- Cargo.lock | 1 + js/packages/truapi-host/src/runtime.ts | 5 + .../src/web/worker-provider.test.ts | 4 + playground/src/lib/example-helpers.ts | 13 +- rust/crates/truapi-host-cli/Cargo.toml | 1 + rust/crates/truapi-host-cli/README.md | 23 +- rust/crates/truapi-host-cli/SPEC.md | 38 +- rust/crates/truapi-host-cli/src/accounts.rs | 5 +- .../crates/truapi-host-cli/src/attestation.rs | 341 +++++-- rust/crates/truapi-host-cli/src/chain.rs | 6 +- rust/crates/truapi-host-cli/src/dotns_read.rs | 155 +++ rust/crates/truapi-host-cli/src/main.rs | 67 +- rust/crates/truapi-host-cli/src/network.rs | 157 +++- .../truapi-host-cli/src/register_name.rs | 185 ++++ rust/crates/truapi-platform/src/lib.rs | 8 + rust/crates/truapi-platform/tests/bounds.rs | 1 + .../crates/truapi-server/src/chain_runtime.rs | 40 + rust/crates/truapi-server/src/host_logic.rs | 2 +- .../src/host_logic/attestation.rs | 106 ++- .../src/host_logic/dotns_gateway.rs | 887 ++++++++++++++++++ .../truapi-server/src/host_logic/identity.rs | 148 --- .../src/host_logic/product_account.rs | 6 +- .../src/host_logic/sso/messages.rs | 1 + .../src/host_logic/sso/pairing.rs | 1 + rust/crates/truapi-server/src/native.rs | 16 + rust/crates/truapi-server/src/runtime.rs | 1 + .../truapi-server/src/runtime/identity.rs | 294 +++--- .../truapi-server/src/runtime/pairing_host.rs | 6 +- .../truapi-server/src/runtime/signing_host.rs | 1 + .../src/runtime/signing_host/sso_responder.rs | 1 + .../src/runtime/statement_allowance.rs | 34 +- .../runtime/statement_allowance/extension.rs | 178 +++- .../runtime/statement_allowance/extrinsic.rs | 31 +- .../src/runtime/statement_allowance/ring.rs | 182 +++- rust/crates/truapi-server/src/test_support.rs | 1 + rust/crates/truapi-server/src/wasm.rs | 7 + rust/crates/truapi-server/src/ws_bridge.rs | 1 + rust/crates/truapi-server/tests/common/mod.rs | 1 + .../paseo-next-v2-asset-hub-metadata.scale | Bin 0 -> 652465 bytes .../tests/wasm_crypto_vectors.rs | 1 + rust/crates/truapi/src/lib.rs | 2 + 41 files changed, 2442 insertions(+), 516 deletions(-) create mode 100644 rust/crates/truapi-host-cli/src/dotns_read.rs create mode 100644 rust/crates/truapi-host-cli/src/register_name.rs create mode 100644 rust/crates/truapi-server/src/host_logic/dotns_gateway.rs delete mode 100644 rust/crates/truapi-server/src/host_logic/identity.rs create mode 100644 rust/crates/truapi-server/tests/fixtures/paseo-next-v2-asset-hub-metadata.scale diff --git a/Cargo.lock b/Cargo.lock index 0e38dbf43..c78f6d006 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4998,6 +4998,7 @@ dependencies = [ "anyhow", "arboard", "async-trait", + "base64", "bip39", "clap", "crossterm", diff --git a/js/packages/truapi-host/src/runtime.ts b/js/packages/truapi-host/src/runtime.ts index 8c4b0fb93..43fa9c560 100644 --- a/js/packages/truapi-host/src/runtime.ts +++ b/js/packages/truapi-host/src/runtime.ts @@ -84,6 +84,11 @@ export interface ProductRuntimeConfig { /** Bulletin-chain genesis hash. */ genesisHash: string | Uint8Array; }; + /** Asset Hub configuration used to resolve session usernames from dotNS. */ + assetHub: { + /** Asset Hub genesis hash. */ + genesisHash: string | Uint8Array; + }; /** Wallet pairing configuration. */ pairing: { /** URI scheme used for wallet pairing deeplinks. */ diff --git a/js/packages/truapi-host/src/web/worker-provider.test.ts b/js/packages/truapi-host/src/web/worker-provider.test.ts index 3c75785d5..026c46eb7 100644 --- a/js/packages/truapi-host/src/web/worker-provider.test.ts +++ b/js/packages/truapi-host/src/web/worker-provider.test.ts @@ -94,6 +94,10 @@ function runtimeConfig( genesisHash: "0xbbcccc1cbe333151b8ed63b17e9e0dec61ee53b57296f1fbe2d161ae3e6fb4dc", }, + assetHub: { + genesisHash: + "0xcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc", + }, pairing: { deeplinkScheme: "polkadotapp", }, diff --git a/playground/src/lib/example-helpers.ts b/playground/src/lib/example-helpers.ts index 47e436ccd..f624522fc 100644 --- a/playground/src/lib/example-helpers.ts +++ b/playground/src/lib/example-helpers.ts @@ -1,6 +1,6 @@ import { Observable } from "rxjs"; import { err, ok, type Result } from "neverthrow"; -import { PASEO_NEXT_V2_INDIVIDUALITY } from "@parity/truapi"; +import { PASEO_NEXT_V2_ASSET_HUB } from "@parity/truapi"; import { AccountId, Blake2128Concat, @@ -51,7 +51,10 @@ export type BuildCreateTransactionPayload = (opts: { callData: HexString; }) => Promise>; -const usernameOwnerOfStorage = Storage("Resources")("UsernameOwnerOf", [ +// Lite usernames are keyed by their dotted label ("alice.01") on the Asset +// Hub gateway pallet. Full-person ownership lives contract-side as H160 only. +// Name→account resolution therefore covers lite usernames. +const liteLabelOwnerStorage = Storage("DotnsGateway")("LiteLabelOwner", [ Bytes(), Blake2128Concat, ]); @@ -113,7 +116,7 @@ export function createAccountIdForDotNsUsername( return err(new Error("DotNS username is empty")); } - const key = usernameOwnerOfStorage.enc( + const key = liteLabelOwnerStorage.enc( new TextEncoder().encode(dotNsUsername), ) as HexString; @@ -137,7 +140,7 @@ export function createAccountIdForDotNsUsername( switch (item.tag) { case "Initialized": { const result = await truapi.chain.getHeadStorage({ - genesisHash: PASEO_NEXT_V2_INDIVIDUALITY.genesis, + genesisHash: PASEO_NEXT_V2_ASSET_HUB.genesis, followSubscriptionId: sub.subscriptionId, hash: item.value.finalizedBlockHashes[0], items: [{ key, queryType: "Value" }], @@ -188,7 +191,7 @@ export function createAccountIdForDotNsUsername( const sub = truapi.chain .followHeadSubscribe({ request: { - genesisHash: PASEO_NEXT_V2_INDIVIDUALITY.genesis, + genesisHash: PASEO_NEXT_V2_ASSET_HUB.genesis, withRuntime: false, }, }) diff --git a/rust/crates/truapi-host-cli/Cargo.toml b/rust/crates/truapi-host-cli/Cargo.toml index 72ef36136..c6ebdbe3b 100644 --- a/rust/crates/truapi-host-cli/Cargo.toml +++ b/rust/crates/truapi-host-cli/Cargo.toml @@ -19,6 +19,7 @@ truapi-server = { path = "../truapi-server" } anyhow = "1" arboard = { version = "3.6.1", default-features = false } async-trait = "0.1" +base64 = "0.22" bip39 = { version = "2", features = ["rand"] } clap = { version = "4", features = ["derive", "env"] } crossterm = { version = "0.29", features = ["event-stream"] } diff --git a/rust/crates/truapi-host-cli/README.md b/rust/crates/truapi-host-cli/README.md index 337bc2449..2f6af47a1 100644 --- a/rust/crates/truapi-host-cli/README.md +++ b/rust/crates/truapi-host-cli/README.md @@ -391,21 +391,22 @@ cleans up a unique temporary Unix socket. ## Scope / gaps -- **Chain methods** route to real `wss://` nodes from the selected `--network` - when `E2E_LIVE_CHAIN=1`; off by default. A rustls crypto provider is - installed at startup for the TLS connections. +- **Chain methods** route to real `wss://` nodes from the selected `--network`. + People, Bulletin, and Asset Hub are host-required and always enabled. + `E2E_LIVE_CHAIN=1` additionally enables any preset route marked optional. A + rustls crypto provider is installed at startup for the TLS connections. - **Ring-VRF product-account aliases and proofs** are implemented by the signing host via the `verifiable` crate (`get_account_alias` and `create_account_proof`). -- **`get_user_id`** resolves the signing account's username from People-chain - `Resources.Consumers`. Auto-managed signing accounts register fresh lite - usernames via the identity backend (`src/attestation.rs`); first registration - is backend-async and can take minutes (ring onboarding). `truapi-host - identity-check --mnemonic ` probes which derivation carries a username. +- **`get_user_id`** resolves the signing account's username from the dotNS + contracts on Asset Hub. Auto-managed signing accounts register fresh lite + usernames via the identity backend (`src/attestation.rs`). First registration + is backend-async and can take minutes, waiting on ring onboarding. + `truapi-host identity-check --mnemonic ` probes which derivation carries a + username. - `set_statement_store_account` and Bulletin long-term-storage resource allocation are implemented over SSO on native headless hosts. - Everything else the browser host exercises passes: signing (raw, payload, create-transaction, and their legacy variants), statement store, entropy, - aliases, preimage, storage, permissions, notifications, theme, system, chain - (with `E2E_LIVE_CHAIN=1`), and user id, subject to live chain availability - and allowance-slot capacity. + aliases, preimage, storage, permissions, notifications, theme, system, chain, + and user id, subject to live chain availability and allowance-slot capacity. diff --git a/rust/crates/truapi-host-cli/SPEC.md b/rust/crates/truapi-host-cli/SPEC.md index 0ef6c77d4..17619f041 100644 --- a/rust/crates/truapi-host-cli/SPEC.md +++ b/rust/crates/truapi-host-cli/SPEC.md @@ -114,7 +114,7 @@ as the paired path. `truapi-host-cli` owns: - argument and slash-command parsing; -- the single supported network preset; +- the supported network presets (`paseo-next-v2`, `previewnet`); - local signer selection and onboarding; - local persistence and account-store locking; - approvals and `--auto-accept`; @@ -198,7 +198,8 @@ Commands: | --- | --- | | `pairing-host` | Run the seedless product-facing host. | | `signing-host` | Run the wallet-local signing host. | -| `identity-check` | Probe People-chain identity records for a mnemonic. | +| `identity-check` | Probe dotNS identity records on Asset Hub for a mnemonic. | +| `register-name` | Register a full-person username via `DotnsGateway.register_name`. | | `alloc-check` | Inspect or submit Statement Store allowance registration. | ### 4.1 Global logging option @@ -230,7 +231,7 @@ truapi-host pairing-host [options] | `--product-id ` | `headless-playground.dot` | Initial product scope. | | `--frame-listen ` | none | Opt into a TCP product WebSocket listener. When omitted, use a private per-process Unix socket. Port `0` selects an available TCP port. | | `--base-path ` | section 12.1 | Root for network, identity, core, script, and product state. | -| `--network ` | `paseo-next-v2` | Select the complete endpoint/genesis preset. | +| `--network ` | `paseo-next-v2` | Select the complete endpoint/genesis preset (`paseo-next-v2`, `previewnet`). | | `--auto-accept` | off | Approve platform confirmations automatically. | Without `--script`, both stdin and stdout must be terminals. The command enters @@ -268,7 +269,7 @@ truapi-host signing-host [options] [exec ''] | `--session ` | remembered session | Restore or create a managed session. | | `--lite-username-prefix ` | session-derived | Prefix for newly generated Lite username bases. | | `--base-path ` | section 12.1 | Root for account, session, core, script, and product state. | -| `--network ` | `paseo-next-v2` | Select the complete endpoint/genesis preset. | +| `--network ` | `paseo-next-v2` | Select the complete endpoint/genesis preset (`paseo-next-v2`, `previewnet`). | | `--frame-listen ` | none | Opt into a TCP product WebSocket listener. When omitted, use a private per-process Unix socket. Port `0` is allowed. | | `--auto-accept` | off | Approve platform confirmations automatically. | @@ -797,14 +798,27 @@ A new auto account: 4. chooses `auto-` as its local name; 5. tries up to eight available Lite username bases; 6. saves a pending account record; -7. builds and submits identity-backend registration proofs; -8. polls `Resources.Consumers` for the final `name.discriminator`; +7. builds and submits identity-backend registration proofs, including the dotNS + gateway reservation signature timestamped with Asset Hub chain time; +8. polls the dotNS contracts on Asset Hub for the final `name.discriminator`; 9. waits for inclusion in a LitePeople ring; and 10. marks and saves the account as attested. Identity and ring polling each allow 10 attempts with four seconds between attempts. Identity-backend HTTP clients use a 30-second timeout. +The backend's username routes are bearer-gated. Unless +`HOST_CLI_IDENTITY_BACKEND_TOKEN` supplies one, the CLI mints an access token +once per process. It takes a challenge from `auth/challenges`. It answers +`auth/token` with an sr25519 proof over +`SHA256(challenge || clientId || SHA256(body))`, signed by a throwaway keypair. + +The token's subject only identifies the calling app instance. The username claim +carries its own candidate account. A fresh subject per run therefore stays clear +of the backend's per-subject device gate and rate limit. Availability answers are +read from both wire shapes: the `{_tag, value: {base: {status}}}` record and the +flat `{base: "AVAILABLE"}` map. + The default Lite username prefix is `headless`. For a non-default session, the prefix is its lowercase letters with digits and separators removed; a name with no letters becomes `session`. `--lite-username-prefix` overrides this and @@ -1097,8 +1111,9 @@ v0.1 supports only `paseo-next-v2`. There are no public endpoint override flags. -People and Bulletin routes are always enabled because host internals require -them. Asset Hub routing is enabled only when `E2E_LIVE_CHAIN=1`. +People, Bulletin, and Asset Hub routes are always enabled because host +internals require them. `E2E_LIVE_CHAIN=1` additionally enables any preset +route marked optional. The all-zero SSO sentinel and every genesis hash not present in the active route map fall back to the People RPC. @@ -1148,7 +1163,7 @@ surface. | Service | Implemented behavior | | --- | --- | | Account | Connection status, product accounts, aliases, proofs, empty legacy-account list, user id, and login. | -| Chain | chainHead-v1 follow/header/body/storage/call/unpin/continue/stop, chain spec queries, transaction broadcast/stop. Asset Hub needs `E2E_LIVE_CHAIN=1`. | +| Chain | chainHead-v1 follow/header/body/storage/call/unpin/continue/stop, chain spec queries, transaction broadcast/stop. People, Bulletin, and Asset Hub all route to the preset's live nodes. | | Entropy | Product-scoped deterministic entropy from the active account/session. | | Local Storage | Persistent product-scoped read, write, and clear. | | Notifications | In-process immediate/scheduled delivery and cancellation with transcript events. | @@ -1490,12 +1505,15 @@ ended. This preserves the child status but bypasses later Rust destructors. | `RUST_LOG` | Full startup tracing filter. | | `TRUAPI_HOST_BASE_PATH` | Default `--base-path`. | | `HOST_CLI_SIGNER_MNEMONIC` | Signing, identity, and allowance mnemonic input. | +| `HOST_CLI_IDENTITY_BACKEND_BASE` | Identity backend base URL override, including `/api/v1`, for instance a local backend. Chain endpoints stay on the preset. | +| `HOST_CLI_IDENTITY_BACKEND_TOKEN` | Bearer token for the identity backend's username routes. Unset, the CLI mints one itself through the backend's `auth/challenges` → `auth/token` sr25519 handshake with a throwaway keypair. | +| `HOST_CLI_DOTNS_POP_CONTROLLER` | `DotnsPopController` H160 override, skipping on-chain discovery. Required on networks whose deployed dispatcher exposes no target getter. On paseo-next-v2 that address is `0x1c858C31497a7715C0D56A11208feB6b74FaB2aB`. | | `XDG_STATE_HOME` | Preferred default state parent. | | `HOME` | Fallback default state parent. | | `VISUAL` | Preferred script editor. | | `EDITOR` | Fallback script editor. | | `TRUAPI_HOST_RUNNER` | Override `js/runner.ts`. | -| `E2E_LIVE_CHAIN` | Value `1` enables optional Asset Hub routing. | +| `E2E_LIVE_CHAIN` | Value `1` enables optional live-chain routing. Every preset route is currently host-required, so this is a no-op. | | `NO_COLOR` | Disable CLI semantic colors and battery reporter color. | | `COLORFGBG` | Infer TUI background color. | | `COLORTERM` | Select true-color TUI rendering. | diff --git a/rust/crates/truapi-host-cli/src/accounts.rs b/rust/crates/truapi-host-cli/src/accounts.rs index dfd500c1f..d71953864 100644 --- a/rust/crates/truapi-host-cli/src/accounts.rs +++ b/rust/crates/truapi-host-cli/src/accounts.rs @@ -428,7 +428,7 @@ async fn ensure_record_ready( record.attested = true; } else { record.lite_username = - attestation::registered_lite_username(network.people_ws, &identity.entropy) + attestation::registered_lite_username(network.asset_hub_ws, &identity.entropy) .await .with_context(|| format!("resolve Lite username for account {}", record.name))?; } @@ -447,9 +447,10 @@ async fn attest_record(network: NetworkConfig, record: &AccountRecord) -> Result let entropy = mnemonic_entropy(&record.mnemonic)?; let lite_username = attestation::attest(&attestation::AttestConfig { backend_base: network.identity_backend_base.to_string(), - people_ws: network.people_ws.to_string(), + asset_hub_ws: network.asset_hub_ws.to_string(), entropy, username_base: record.lite_username.clone(), + reserved_username: None, }) .await .with_context(|| format!("attest account {}", record.name))?; diff --git a/rust/crates/truapi-host-cli/src/attestation.rs b/rust/crates/truapi-host-cli/src/attestation.rs index ad906e270..c89ddf6fb 100644 --- a/rust/crates/truapi-host-cli/src/attestation.rs +++ b/rust/crates/truapi-host-cli/src/attestation.rs @@ -1,35 +1,149 @@ //! Lite-username attestation against the People-chain identity backend. //! -//! Ports signing-bot `attestation.ts`: fetch the backend verifier, build the -//! client proofs (`truapi_server::host_logic::attestation`), POST them to -//! `/usernames`, then poll People-chain `Resources.Consumers` until the record -//! lands. Registers the signing host's RFC-0022 `uid.dot` identity account so -//! the paired host can resolve its username via `get_user_id`. +//! Fetches the backend verifier. Builds the client proofs +//! (`truapi_server::host_logic::attestation`), including the dotNS gateway +//! reservation signature timestamped with Asset Hub chain time. POSTs them to +//! `/usernames`. Polls the dotNS contracts on Asset Hub until the lite username +//! lands. +//! +//! Registers the signing host's RFC-0022 `uid.dot` identity account. The paired +//! host can then resolve its username via `get_user_id`. use std::time::Duration; use anyhow::{Context, Result, bail}; +use base64::Engine as _; +use base64::engine::general_purpose::STANDARD as BASE64; +use bip39::Mnemonic; use serde_json::{Value, json}; -use subxt_rpcs::client::{RpcClient, rpc_params}; +use sha2::{Digest as _, Sha256}; +use tokio::sync::OnceCell; use tracing::{debug, warn}; use truapi_server::host_logic::attestation::build_lite_registration; -use truapi_server::host_logic::identity::{ - decode_people_identity, resources_consumers_storage_key, -}; use truapi_server::host_logic::product_account::{ - derive_identity_keypair, derive_root_keypair_from_entropy, product_public_key_to_address, + SR25519_SIGNING_CONTEXT, derive_identity_keypair, derive_root_keypair_from_entropy, + product_public_key_to_address, }; +use crate::dotns_read::AssetHubReader; + +/// Env var carrying an optional bearer token for the identity backend. +/// Set it to reuse a token minted elsewhere. Unset, the CLI runs the sr25519 +/// auth handshake itself ([`backend_token`]). +pub const IDENTITY_BACKEND_TOKEN_ENV: &str = "HOST_CLI_IDENTITY_BACKEND_TOKEN"; + +/// Access token for the username routes, minted once per process. +static BACKEND_TOKEN: OnceCell = OnceCell::const_new(); + +/// Bearer token for the identity backend's username routes. +/// +/// The explicit env token wins. Otherwise the CLI completes the backend's +/// `challenges` → `token` sr25519 handshake with a throwaway keypair. The JWT +/// subject only identifies the calling app instance. The username claim carries +/// its own candidate account. A fresh subject each run keeps repeat +/// registrations clear of the per-subject device gate and rate limit. +async fn backend_token(client: &reqwest::Client, backend_base: &str) -> Result<&'static str> { + if let Ok(token) = std::env::var(IDENTITY_BACKEND_TOKEN_ENV) { + let token = token.trim(); + if !token.is_empty() { + return Ok(Box::leak(token.to_string().into_boxed_str())); + } + } + BACKEND_TOKEN + .get_or_try_init(|| mint_backend_token(client, backend_base)) + .await + .map(String::as_str) +} + +/// Runs the backend's auth handshake and returns its access JWT. +async fn mint_backend_token(client: &reqwest::Client, backend_base: &str) -> Result { + let url = format!("{backend_base}/auth/challenges"); + let body: Value = client + .post(&url) + .json(&json!({})) + .send() + .await + .with_context(|| format!("POST {url}"))? + .error_for_status()? + .json() + .await + .context("decoding challenge response")?; + let challenge = body + .get("challenge") + .and_then(Value::as_str) + .context("challenge response missing 'challenge' field")? + .to_string(); + let challenge_bytes = BASE64 + .decode(&challenge) + .context("challenge is not valid base64")?; + + let entropy = Mnemonic::generate(12) + .context("generate throwaway auth mnemonic")? + .to_entropy(); + let keypair = derive_root_keypair_from_entropy(&entropy) + .map_err(|err| anyhow::anyhow!("auth keypair derivation failed: {err}"))?; + let client_id = keypair.public.to_bytes(); + + // The proof signs SHA256(challenge || clientId || SHA256(body)). It must + // cover the exact bytes the request carries, so the body is serialized once. + let payload = b"{}"; + let mut hasher = Sha256::new(); + hasher.update(&challenge_bytes); + hasher.update(client_id); + hasher.update(Sha256::digest(payload)); + let message: [u8; 32] = hasher.finalize().into(); + let proof = keypair + .secret + .sign_simple(SR25519_SIGNING_CONTEXT, &message, &keypair.public) + .to_bytes(); + + let url = format!("{backend_base}/auth/token"); + let response = client + .post(&url) + .header("Auth-ClientId", BASE64.encode(client_id)) + .header("Auth-ClientProof", BASE64.encode(proof)) + .header("Auth-Challenge", &challenge) + .header(reqwest::header::CONTENT_TYPE, "application/json") + .body(payload.as_slice()) + .send() + .await + .with_context(|| format!("POST {url}"))?; + let status = response.status(); + let body = response.text().await.unwrap_or_default(); + if !status.is_success() { + bail!("identity backend auth handshake failed ({status}): {body}"); + } + let body: Value = serde_json::from_str(&body).context("decoding token response")?; + let token = body + .get("token") + .and_then(Value::as_str) + .context("token response missing 'token' field")?; + debug!("minted identity backend access token"); + Ok(token.to_string()) +} + +/// Attaches the identity backend bearer token to `request`. +async fn with_backend_auth( + client: &reqwest::Client, + backend_base: &str, + request: reqwest::RequestBuilder, +) -> Result { + Ok(request.bearer_auth(backend_token(client, backend_base).await?)) +} + /// Inputs for one attestation run. pub struct AttestConfig { /// Identity backend base URL including `/api/v1`. pub backend_base: String, - /// People-chain WebSocket URL for the `Resources.Consumers` poll. - pub people_ws: String, + /// Asset Hub WebSocket URL for the reservation timestamp and the dotNS + /// username poll. + pub asset_hub_ws: String, /// BIP-39 entropy of the signing host's root account. pub entropy: Vec, /// Requested lite username base (6+ lowercase letters, no digits). pub username_base: String, + /// Optional base name to queue on dotNS for a later full-person claim. + pub reserved_username: Option, } /// Check whether a lite username base is available through the identity @@ -40,35 +154,62 @@ pub async fn lite_username_available(backend_base: &str, username_base: &str) -> .build()?; let url = format!("{backend_base}/usernames/available"); let body = json!({ "usernames": [username_base] }); - let response = client - .post(&url) - .json(&body) + let response = with_backend_auth(&client, backend_base, client.post(&url).json(&body)) + .await? .send() .await - .with_context(|| format!("POST {url}"))? - .error_for_status() - .with_context(|| format!("username availability check failed for {username_base}"))?; + .with_context(|| format!("POST {url}"))?; + let status = response.status(); + if !status.is_success() { + let text = response.text().await.unwrap_or_default(); + bail!("username availability check failed for {username_base} ({status}): {text}"); + } let body: Value = response .json() .await .context("decoding availability response")?; - Ok(body - .get(username_base) - .and_then(Value::as_str) - .is_some_and(|status| status == "AVAILABLE")) + Ok(availability_status(&body, username_base) == Some("AVAILABLE")) +} + +/// Reads one base's availability status out of either wire shape. Those are the +/// v1 record `{_tag, value: {base: {status, …}}}` and the flat +/// `{base: "AVAILABLE"}` the preset backends serve. +fn availability_status<'a>(body: &'a Value, username_base: &str) -> Option<&'a str> { + let entry = body + .get("value") + .and_then(|value| value.get(username_base)) + .or_else(|| body.get(username_base))?; + match entry { + Value::String(status) => Some(status), + entry => entry.get("status").and_then(Value::as_str), + } } -/// Register (or confirm) the signing host's lite username and wait until the -/// People-chain `Resources.Consumers` record exists. Returns the Lite username -/// assigned on chain (including its discriminator). +/// Registers (or confirms) the signing host's lite username. Waits until the +/// dotNS contracts on Asset Hub record it. Returns the lite username assigned on +/// chain, including its discriminator. pub async fn attest(config: &AttestConfig) -> Result { let client = reqwest::Client::builder() .timeout(Duration::from_secs(30)) .build()?; + let mut reader = AssetHubReader::connect(&config.asset_hub_ws).await?; + // Timestamping the reservation signature with Asset Hub chain time. The + // gateway rejects values ahead of the chain. + let signed_at = reader + .timestamp_secs() + .await + .context("read Asset Hub Timestamp.Now")?; + let verifier = fetch_verifier(&client, &config.backend_base).await?; - let registration = build_lite_registration(&config.entropy, verifier, &config.username_base) - .map_err(|reason| anyhow::anyhow!("failed to build registration params: {reason}"))?; + let registration = build_lite_registration( + &config.entropy, + verifier, + &config.username_base, + config.reserved_username.as_deref(), + signed_at, + ) + .map_err(|reason| anyhow::anyhow!("failed to build registration params: {reason}"))?; debug!( candidate = %registration.candidate_account_id, "attesting lite username '{}'", @@ -79,51 +220,44 @@ pub async fn attest(config: &AttestConfig) -> Result { &client, &config.backend_base, &config.username_base, + config.reserved_username.as_deref(), + signed_at, ®istration, ) .await?; - let storage_key = format!( - "0x{}", - hex::encode(resources_consumers_storage_key( - ®istration.candidate_public_key - )) - ); - let identity = wait_for_consumer_record(&config.people_ws, &storage_key).await?; + let identity = wait_for_dotns_username(&mut reader, ®istration.candidate_public_key).await?; debug!("lite username registered and confirmed on-chain"); identity .lite_username - .context("registered People-chain identity has no Lite username") + .context("registered dotNS identity has no lite username") } -/// Resolve the on-chain Lite username for an already-attested signer. +/// Resolves the on-chain lite username for an already-attested signer. /// -/// Older CLI account records stored the requested username base rather than -/// the final `name.discriminator` assigned by the People chain. Reading the -/// consumer record repairs those records without re-attesting the account. -pub async fn registered_lite_username(people_ws: &str, entropy: &[u8]) -> Result { +/// Older CLI account records stored the requested username base rather than the +/// final `name.discriminator` assigned on chain. Reading the dotNS record repairs +/// those records without re-attesting the account. +pub async fn registered_lite_username(asset_hub_ws: &str, entropy: &[u8]) -> Result { let identity = derive_identity_keypair(entropy) .map_err(|err| anyhow::anyhow!("uid.dot identity derivation failed: {err}"))?; - let storage_key = format!( - "0x{}", - hex::encode(resources_consumers_storage_key(&identity.public.to_bytes())) - ); - let value = query_storage(people_ws, &storage_key) + let mut reader = AssetHubReader::connect(asset_hub_ws).await?; + reader + .dotns_identity(&identity.public.to_bytes()) .await? - .context("attested signer has no Resources.Consumers record")?; - decode_identity_hex(&value)? .lite_username - .context("registered People-chain identity has no Lite username") + .context("attested signer has no dotNS lite username") } -/// Probe the People chain for the bare root and canonical RFC-0022 `uid.dot` -/// identity account, printing any `Resources.Consumers` record. Used to -/// confirm a pre-onboarded account. -pub async fn check_identity(people_ws: &str, entropy: &[u8]) -> Result<()> { +/// Probes the dotNS contracts for the bare root and canonical RFC-0022 `uid.dot` +/// identity account. Prints any recorded usernames. Used to confirm a +/// pre-onboarded account. +pub async fn check_identity(asset_hub_ws: &str, entropy: &[u8]) -> Result<()> { let root = derive_root_keypair_from_entropy(entropy) .map_err(|err| anyhow::anyhow!("invalid entropy: {err}"))?; let identity = derive_identity_keypair(entropy) .map_err(|err| anyhow::anyhow!("uid.dot identity derivation failed: {err}"))?; + let mut reader = AssetHubReader::connect(asset_hub_ws).await?; for (label, public) in [ ("", root.public.to_bytes()), @@ -132,23 +266,15 @@ pub async fn check_identity(people_ws: &str, entropy: &[u8]) -> Result<()> { identity.public.to_bytes(), ), ] { - let key = format!( - "0x{}", - hex::encode(resources_consumers_storage_key(&public)) - ); let address = product_public_key_to_address(public); - match query_storage(people_ws, &key).await { - Ok(Some(value)) => { - let decoded = hex::decode(value.strip_prefix("0x").unwrap_or(&value)) - .ok() - .and_then(|bytes| decode_people_identity(&bytes).ok()); - let username = decoded - .and_then(|id| id.full_username.or(id.lite_username)) - .unwrap_or_else(|| "".to_string()); - println!("IDENTITY_FOUND path={label} account={address} username={username}"); - } - Ok(None) => println!("IDENTITY_NONE path={label} account={address}"), - Err(err) => println!("IDENTITY_ERROR path={label} account={address} error={err}"), + match reader.dotns_identity(&public).await { + Ok(identity) => match identity.full_username.or(identity.lite_username) { + Some(username) => { + println!("IDENTITY_FOUND path={label} account={address} username={username}") + } + None => println!("IDENTITY_NONE path={label} account={address}"), + }, + Err(err) => println!("IDENTITY_ERROR path={label} account={address} error={err:#}"), } } Ok(()) @@ -179,9 +305,18 @@ async fn submit_registration( client: &reqwest::Client, backend_base: &str, username_base: &str, + reserved_username: Option<&str>, + signed_at: u64, reg: &truapi_server::host_logic::attestation::LiteRegistration, ) -> Result<()> { let url = format!("{backend_base}/usernames"); + let mut dotns = json!({ + "signature": hex0x(®.dotns_signature), + "signedAt": signed_at, + }); + if let Some(reserved) = reserved_username { + dotns["reservedUsername"] = json!(reserved); + } let body = json!({ "username": username_base, "candidateAccountId": reg.candidate_account_id, @@ -190,10 +325,10 @@ async fn submit_registration( "proofOfOwnership": hex0x(®.proof_of_ownership), "identifierKey": hex0x(®.identifier_key), "consumerRegistrationSignature": hex0x(®.consumer_registration_signature), + "dotns": dotns, }); - let response = client - .post(&url) - .json(&body) + let response = with_backend_auth(client, backend_base, client.post(&url).json(&body)) + .await? .send() .await .with_context(|| format!("POST {url}"))?; @@ -217,60 +352,62 @@ fn hex0x(bytes: &[u8]) -> String { format!("0x{}", hex::encode(bytes)) } -async fn wait_for_consumer_record( - people_ws: &str, - storage_key: &str, -) -> Result { +async fn wait_for_dotns_username( + reader: &mut AssetHubReader, + candidate: &[u8; 32], +) -> Result { // First-time lite registration is backend-async and can lag the HTTP - // response. The record is permanent once written, so later runs resolve on - // the first poll. + // response. The record is permanent once written. Later runs therefore + // resolve on the first poll. const MAX_ATTEMPTS: usize = 10; for attempt in 1..=MAX_ATTEMPTS { - match query_storage(people_ws, storage_key).await { - Ok(Some(value)) => { + match reader.dotns_identity(candidate).await { + Ok(identity) if identity.lite_username.is_some() => { crate::terminal_ui::update_activity( "signer", "Setting up signer", - Some("People-chain identity ready".to_string()), + Some("dotNS identity ready".to_string()), crate::terminal_ui::ActivityState::Running, ); - return decode_identity_hex(&value); + return Ok(identity); } - Ok(None) => { + Ok(_) => { crate::terminal_ui::update_activity( "signer", "Setting up signer", Some(format!( - "Waiting for People-chain identity · attempt {attempt}/{MAX_ATTEMPTS}" + "Waiting for dotNS username · attempt {attempt}/{MAX_ATTEMPTS}" )), crate::terminal_ui::ActivityState::Running, ); - debug!("Resources.Consumers poll {attempt}/{MAX_ATTEMPTS}: empty"); + debug!("dotNS username poll {attempt}/{MAX_ATTEMPTS}: empty"); } - Err(err) => warn!(%err, "Resources.Consumers poll attempt {attempt} failed"), + Err(err) => warn!(%err, "dotNS username poll attempt {attempt} failed"), } if attempt < MAX_ATTEMPTS { tokio::time::sleep(Duration::from_secs(4)).await; } } - bail!("Resources.Consumers record did not appear after attestation") + bail!("dotNS username did not appear on Asset Hub after attestation") } -fn decode_identity_hex(value: &str) -> Result { - let bytes = hex::decode(value.strip_prefix("0x").unwrap_or(value)) - .context("Resources.Consumers value is not valid hex")?; - decode_people_identity(&bytes).map_err(anyhow::Error::msg) -} +#[cfg(test)] +mod tests { + use super::*; -/// One `state_getStorage` request over a fresh RPC connection; returns the value -/// hex when present. -async fn query_storage(people_ws: &str, storage_key: &str) -> Result> { - let rpc = RpcClient::from_insecure_url(people_ws) - .await - .with_context(|| format!("connect {people_ws}"))?; - let value = rpc - .request::("state_getStorage", rpc_params![storage_key]) - .await - .context("rpc state_getStorage")?; - Ok(value.as_str().map(str::to_string)) + /// Both backends the CLI talks to are understood. Those are the identity + /// backend's v1 record and the flat map the preset backends serve. + #[test] + fn availability_reads_both_wire_shapes() { + let v1 = json!({ + "_tag": "v1", + "value": { "pntest": { "status": "AVAILABLE", "availableDigits": [1, 2] } }, + }); + assert_eq!(availability_status(&v1, "pntest"), Some("AVAILABLE")); + + let flat = json!({ "pntest": "EXHAUSTED" }); + assert_eq!(availability_status(&flat, "pntest"), Some("EXHAUSTED")); + + assert_eq!(availability_status(&v1, "other"), None); + } } diff --git a/rust/crates/truapi-host-cli/src/chain.rs b/rust/crates/truapi-host-cli/src/chain.rs index 5fbf94ebf..77fcc4dc9 100644 --- a/rust/crates/truapi-host-cli/src/chain.rs +++ b/rust/crates/truapi-host-cli/src/chain.rs @@ -217,9 +217,11 @@ mod tests { network.bulletin_ws ); assert_eq!(provider.url_for(&network.people_genesis), network.people_ws); + // Asset Hub is a required host route. Session usernames resolve from + // the dotNS contracts there. assert_eq!( - provider.url_for(&network.live_chain_endpoints[0].genesis), - network.people_ws + provider.url_for(&network.asset_hub_genesis), + network.asset_hub_ws ); } diff --git a/rust/crates/truapi-host-cli/src/dotns_read.rs b/rust/crates/truapi-host-cli/src/dotns_read.rs new file mode 100644 index 000000000..bb2c6c8ee --- /dev/null +++ b/rust/crates/truapi-host-cli/src/dotns_read.rs @@ -0,0 +1,155 @@ +//! Asset Hub dotNS reads over plain RPC (`state_getStorage` / `state_call`). +//! +//! The transport half of username resolution. This module supplies the two RPC +//! primitives. `truapi_server::host_logic::dotns_gateway` walks the contract +//! chain over them. The CLI and the in-core `chainHead_v1` lookup therefore +//! resolve identically. + +use anyhow::{Context, Result, bail}; +use serde_json::Value; +use subxt_rpcs::client::{RpcClient, rpc_params}; +use truapi_platform::async_trait; +use truapi_server::host_logic::dotns_gateway::{ + DotnsIdentity, DotnsTransport, VIEW_CALL_ORIGIN, account_alias_key, classify_labels, + decode_revive_call_output, discover_pop_controller, encode_revive_call, resolve_labels, + timestamp_now_key, +}; + +/// Env var overriding the `DotnsPopController` H160 (hex), skipping on-chain +/// discovery. +/// +/// Needed on networks whose deployed dispatcher exposes no target getter. On +/// paseo-next-v2 that address is `0x1c858C31497a7715C0D56A11208feB6b74FaB2aB`. +pub const DOTNS_POP_CONTROLLER_ENV: &str = "HOST_CLI_DOTNS_POP_CONTROLLER"; + +/// One Asset Hub RPC connection for a batch of dotNS reads. +pub struct AssetHubReader { + rpc: RpcClient, +} + +impl AssetHubReader { + /// Connects to the Asset Hub WebSocket endpoint. + pub async fn connect(asset_hub_ws: &str) -> Result { + let rpc = RpcClient::from_insecure_url(asset_hub_ws) + .await + .with_context(|| format!("connect {asset_hub_ws}"))?; + Ok(Self { rpc }) + } + + /// Asset Hub chain time in Unix seconds. `Timestamp.Now` itself is + /// milliseconds. + pub async fn timestamp_secs(&self) -> Result { + let value = self + .raw_storage(×tamp_now_key()) + .await? + .context("Timestamp.Now is unset")?; + let millis: [u8; 8] = value + .as_slice() + .try_into() + .map_err(|_| anyhow::anyhow!("Timestamp.Now is not a u64"))?; + Ok(u64::from_le_bytes(millis) / 1000) + } + + /// Alias `account` registered with, per `DotnsGateway.AccountAlias`. + pub async fn account_alias(&self, account: &[u8; 32]) -> Result> { + let value = self.raw_storage(&account_alias_key(account)).await?; + Ok(value.and_then(|bytes| bytes.try_into().ok())) + } + + /// Usernames of `account` as recorded by the dotNS contracts. + pub async fn dotns_identity(&mut self, account: &[u8; 32]) -> Result { + let controller = self.pop_controller().await?; + let labels = resolve_labels(self, &controller, account) + .await + .map_err(anyhow::Error::msg)?; + Ok(classify_labels(labels)) + } + + /// `DotnsPopController` address. The env override wins, otherwise on-chain + /// discovery. + /// + /// The currently deployed paseo dispatcher exposes no target getter at all. + /// Its fallback reverts every unknown selector. On-chain discovery cannot + /// reach the controller there, hence the override. + async fn pop_controller(&mut self) -> Result<[u8; 20]> { + if let Ok(value) = std::env::var(DOTNS_POP_CONTROLLER_ENV) { + let trimmed = value.trim(); + if !trimmed.is_empty() { + let bytes = hex::decode(trimmed.strip_prefix("0x").unwrap_or(trimmed)) + .with_context(|| format!("{DOTNS_POP_CONTROLLER_ENV} is not valid hex"))?; + return bytes.try_into().map_err(|bytes: Vec| { + anyhow::anyhow!( + "{DOTNS_POP_CONTROLLER_ENV} must be 20 bytes, got {}", + bytes.len() + ) + }); + } + } + discover_pop_controller(self) + .await + .map_err(anyhow::Error::msg)? + .with_context(|| { + format!( + "dotNS gateway has no reachable controller; set \ + {DOTNS_POP_CONTROLLER_ENV} to the DotnsPopController H160" + ) + }) + } + + /// Dry-runs a contract view via the `ReviveApi_call` runtime API and returns + /// its data. + /// + /// Views originate from the synthetic always-mapped account. They work + /// regardless of the queried account's revive mapping. + async fn raw_view(&self, dest: &[u8; 20], input: Vec) -> Result> { + let args = encode_revive_call(&VIEW_CALL_ORIGIN, dest, &input); + let output: Value = self + .rpc + .request( + "state_call", + rpc_params!["ReviveApi_call", format!("0x{}", hex::encode(args))], + ) + .await + .context("rpc state_call ReviveApi_call")?; + let Some(output) = output.as_str() else { + bail!("state_call returned a non-string response"); + }; + let bytes = hex::decode(output.strip_prefix("0x").unwrap_or(output)) + .context("state_call output is not valid hex")?; + Ok(decode_revive_call_output(&bytes)?) + } + + /// One `state_getStorage` read at the best block. + async fn raw_storage(&self, key: &[u8]) -> Result>> { + let value: Value = self + .rpc + .request( + "state_getStorage", + rpc_params![format!("0x{}", hex::encode(key))], + ) + .await + .context("rpc state_getStorage")?; + value + .as_str() + .map(|hex_value| { + hex::decode(hex_value.strip_prefix("0x").unwrap_or(hex_value)) + .context("storage value is not valid hex") + }) + .transpose() + } +} + +#[async_trait] +impl DotnsTransport for AssetHubReader { + async fn storage(&mut self, key: Vec) -> Result>, String> { + self.raw_storage(&key) + .await + .map_err(|err| format!("{err:#}")) + } + + async fn view(&mut self, dest: &[u8; 20], input: Vec) -> Result, String> { + self.raw_view(dest, input) + .await + .map_err(|err| format!("{err:#}")) + } +} diff --git a/rust/crates/truapi-host-cli/src/main.rs b/rust/crates/truapi-host-cli/src/main.rs index e9eb53ad1..56181dc12 100644 --- a/rust/crates/truapi-host-cli/src/main.rs +++ b/rust/crates/truapi-host-cli/src/main.rs @@ -12,9 +12,11 @@ mod accounts; mod attestation; mod chain; +mod dotns_read; mod frame_server; mod network; mod platform; +mod register_name; mod script_runner; mod sessions; mod signing_shell; @@ -147,7 +149,8 @@ enum Command { /// specified, and can accept pairing deeplinks. With `--script`, exits with /// the script's status; otherwise stays interactive. SigningHost(SigningHostArgs), - /// Probe the People chain for a mnemonic's registered identity/username. + /// Probe the dotNS contracts on Asset Hub for a mnemonic's registered + /// identity/username. IdentityCheck { /// BIP-39 mnemonic to probe. #[arg(long, env = "HOST_CLI_SIGNER_MNEMONIC")] @@ -156,6 +159,28 @@ enum Command { #[arg(long, value_enum, default_value = "paseo-next-v2")] network: Network, }, + /// Register a full-person username on dotNS via + /// `DotnsGateway.register_name` on Asset Hub. Requires a recognized full + /// person. That person's People ring must have propagated to Asset Hub. + RegisterName { + /// BIP-39 mnemonic of the recognized full person. + #[arg(long, env = "HOST_CLI_SIGNER_MNEMONIC")] + mnemonic: String, + /// Network preset to use. + #[arg(long, value_enum, default_value = "paseo-next-v2")] + network: Network, + /// Base label to register (no digits). + #[arg(long)] + label: String, + /// Dotted lite username to link (`name.NN`). Defaults to the + /// account's own lite username. + #[arg(long, conflicts_with = "chat_key")] + link_lite: Option, + /// 65-byte ECDH chat key (hex). Use it for a standalone registration + /// with no lite-username link. + #[arg(long)] + chat_key: Option, + }, /// Check (and optionally submit) a statement-store allowance registration /// against the real People chain: ring membership, the chosen slot, and /// (with `--submit`) the `set_statement_store_account` extrinsic. @@ -295,7 +320,35 @@ async fn main() -> Result<()> { let entropy = bip39::Mnemonic::parse(mnemonic.trim()) .context("invalid BIP-39 mnemonic")? .to_entropy(); - attestation::check_identity(network.config().people_ws, &entropy).await + attestation::check_identity(network.config().asset_hub_ws, &entropy).await + } + Command::RegisterName { + mnemonic, + network, + label, + link_lite, + chat_key, + } => { + let entropy = bip39::Mnemonic::parse(mnemonic.trim()) + .context("invalid BIP-39 mnemonic")? + .to_entropy(); + let chat_key = chat_key + .map(|value| -> Result<[u8; 65]> { + let bytes = hex::decode(value.strip_prefix("0x").unwrap_or(&value)) + .context("chat key is not valid hex")?; + bytes.try_into().map_err(|bytes: Vec| { + anyhow::anyhow!("chat key must be 65 bytes, got {}", bytes.len()) + }) + }) + .transpose()?; + register_name::register_name(®ister_name::RegisterNameConfig { + network: network.config(), + entropy, + label, + link_lite, + chat_key, + }) + .await } Command::AllocCheck { mnemonic, @@ -499,13 +552,14 @@ async fn run_pairing_host( approval_policy(args.auto_accept), ui_handle, ); - // SSO and identity both run over the real People chain, so usernames always - // resolve from `Resources.Consumers` (host-spec G). + // SSO runs over the real People chain. Usernames resolve from the dotNS + // contracts on Asset Hub. let config = PairingHostConfig::new( host_info("Headless Pairing Host"), platform_info(), network.people_genesis, network.bulletin_genesis, + network.asset_hub_genesis, DEEPLINK_SCHEME.to_string(), ) .context("invalid pairing host config")?; @@ -801,12 +855,12 @@ async fn start_signing_host( lite_username_prefix: None, }) .await?; - match attestation::registered_lite_username(network.people_ws, &explicit_signer.entropy) + match attestation::registered_lite_username(network.asset_hub_ws, &explicit_signer.entropy) .await { Ok(user_id) => explicit_signer.lite_username = Some(user_id), Err(error) => { - tracing::warn!(%error, "explicit signer has no resolvable People-chain username") + tracing::warn!(%error, "explicit signer has no resolvable dotNS username") } } signer = Some(explicit_signer); @@ -896,6 +950,7 @@ fn build_signing_runtime( platform_info(), network.people_genesis, network.bulletin_genesis, + network.asset_hub_genesis, ) .context("invalid signing host config")?; Ok(Arc::new(SigningHostRuntime::new( diff --git a/rust/crates/truapi-host-cli/src/network.rs b/rust/crates/truapi-host-cli/src/network.rs index c1f909202..571034bc2 100644 --- a/rust/crates/truapi-host-cli/src/network.rs +++ b/rust/crates/truapi-host-cli/src/network.rs @@ -11,51 +11,118 @@ pub enum Network { #[value(name = "paseo-next-v2")] #[default] PaseoNextV2, + /// Previewnet, the testnet carrying the newer dotNS gateway. Its reservation + /// signature scheme has `MaxValiditySeconds`. + #[value(name = "previewnet")] + Previewnet, } +/// Env var overriding the identity backend base URL for every command. The URL +/// includes `/api/v1`, for instance a local backend at +/// `http://localhost:8080/api/v1`. Chain endpoints stay on the preset. +pub const IDENTITY_BACKEND_BASE_ENV: &str = "HOST_CLI_IDENTITY_BACKEND_BASE"; + impl Network { + /// Preset resolved with any environment overrides applied. pub fn config(self) -> NetworkConfig { + apply_backend_override(self.preset(), std::env::var(IDENTITY_BACKEND_BASE_ENV).ok()) + } + + /// The unmodified preset values. + fn preset(self) -> NetworkConfig { match self { Self::PaseoNextV2 => NetworkConfig { id: "paseo-next-v2", identity_backend_base: "https://identity-backend-next.parity-testnet.parity.io/api/v1", - people_ws: "wss://paseo-people-next-system-rpc.polkadot.io", - bulletin_ws: "wss://paseo-bulletin-next-rpc.polkadot.io", - people_genesis: hex_literal_genesis( - "c5af1826b31493f08b7e2a823842f98575b806a784126f28da9608c68665afa5", - ), - bulletin_genesis: hex_literal_genesis( - "8cfe6717dc4becfda2e13c488a1e2061ff2dfee96e7d031157f72d36716c0a22", - ), + people_ws: PASEO_PEOPLE.ws, + bulletin_ws: PASEO_BULLETIN.ws, + asset_hub_ws: PASEO_ASSET_HUB.ws, + people_genesis: PASEO_PEOPLE.genesis, + bulletin_genesis: PASEO_BULLETIN.genesis, + asset_hub_genesis: PASEO_ASSET_HUB.genesis, live_chain_endpoints: PASEO_NEXT_V2_CHAIN_ENDPOINTS, }, + Self::Previewnet => NetworkConfig { + id: "previewnet", + identity_backend_base: "https://polkadot-app-stg.parity.io/api/v1", + people_ws: PREVIEWNET_PEOPLE.ws, + // Previewnet has no bulletin chain. Preimage submission keeps + // using the paseo testnet bulletin. + bulletin_ws: PASEO_BULLETIN.ws, + asset_hub_ws: PREVIEWNET_ASSET_HUB.ws, + people_genesis: PREVIEWNET_PEOPLE.genesis, + bulletin_genesis: PASEO_BULLETIN.genesis, + asset_hub_genesis: PREVIEWNET_ASSET_HUB.genesis, + live_chain_endpoints: PREVIEWNET_CHAIN_ENDPOINTS, + }, + } + } +} + +/// Replaces the preset's identity backend base with `base` when it carries a +/// non-empty URL. Trailing slashes are stripped so path joins stay clean. The +/// override is leaked once per process, which is fine for a CLI. +fn apply_backend_override(mut config: NetworkConfig, base: Option) -> NetworkConfig { + if let Some(base) = base { + let trimmed = base.trim().trim_end_matches('/'); + if !trimmed.is_empty() { + config.identity_backend_base = Box::leak(trimmed.to_string().into_boxed_str()); } } + config } -const PASEO_NEXT_V2_CHAIN_ENDPOINTS: &[ChainEndpoint] = &[ - ChainEndpoint { - genesis: hex_literal_genesis( - "bf0488dbe9daa1de1c08c5f743e26fdc2a4ecd74cf87dd1b4b1eeb99ae4ef19f", - ), - ws: "wss://paseo-asset-hub-next-rpc.polkadot.io", - required_for_host: false, - }, - ChainEndpoint { - genesis: hex_literal_genesis( - "c5af1826b31493f08b7e2a823842f98575b806a784126f28da9608c68665afa5", - ), - ws: "wss://paseo-people-next-system-rpc.polkadot.io", - required_for_host: true, - }, - ChainEndpoint { - genesis: hex_literal_genesis( - "8cfe6717dc4becfda2e13c488a1e2061ff2dfee96e7d031157f72d36716c0a22", - ), - ws: "wss://paseo-bulletin-next-rpc.polkadot.io", - required_for_host: true, - }, -]; +// Each chain is declared once and reused by both the preset fields and the +// endpoint table. A genesis hash can therefore never drift from the URL it +// routes to. Every route below is host-required: session identity (dotNS +// usernames) resolves through Asset Hub, SSO through People, preimages through +// Bulletin. + +const PASEO_ASSET_HUB: ChainEndpoint = ChainEndpoint { + genesis: hex_literal_genesis( + "bf0488dbe9daa1de1c08c5f743e26fdc2a4ecd74cf87dd1b4b1eeb99ae4ef19f", + ), + ws: "wss://paseo-asset-hub-next-rpc.polkadot.io", + required_for_host: true, +}; + +const PASEO_PEOPLE: ChainEndpoint = ChainEndpoint { + genesis: hex_literal_genesis( + "c5af1826b31493f08b7e2a823842f98575b806a784126f28da9608c68665afa5", + ), + ws: "wss://paseo-people-next-system-rpc.polkadot.io", + required_for_host: true, +}; + +const PASEO_BULLETIN: ChainEndpoint = ChainEndpoint { + genesis: hex_literal_genesis( + "8cfe6717dc4becfda2e13c488a1e2061ff2dfee96e7d031157f72d36716c0a22", + ), + ws: "wss://paseo-bulletin-next-rpc.polkadot.io", + required_for_host: true, +}; + +const PREVIEWNET_ASSET_HUB: ChainEndpoint = ChainEndpoint { + genesis: hex_literal_genesis( + "4d11c803cc6921429e3876638977ad006ea1bba8cd3976a0bca2f164e7026210", + ), + ws: "wss://previewnet.substrate.dev/asset-hub", + required_for_host: true, +}; + +const PREVIEWNET_PEOPLE: ChainEndpoint = ChainEndpoint { + genesis: hex_literal_genesis( + "3138c6d4ce58c760047a413c2a930e919b4673a841ab4890de59aac3bd037f3d", + ), + ws: "wss://previewnet.substrate.dev/people", + required_for_host: true, +}; + +const PASEO_NEXT_V2_CHAIN_ENDPOINTS: &[ChainEndpoint] = + &[PASEO_ASSET_HUB, PASEO_PEOPLE, PASEO_BULLETIN]; + +const PREVIEWNET_CHAIN_ENDPOINTS: &[ChainEndpoint] = + &[PREVIEWNET_ASSET_HUB, PREVIEWNET_PEOPLE, PASEO_BULLETIN]; /// Resolved RPC/backend/genesis values for one network preset. #[derive(Debug, Clone, Copy)] @@ -65,8 +132,10 @@ pub struct NetworkConfig { pub people_ws: &'static str, #[allow(dead_code)] pub bulletin_ws: &'static str, + pub asset_hub_ws: &'static str, pub people_genesis: [u8; 32], pub bulletin_genesis: [u8; 32], + pub asset_hub_genesis: [u8; 32], pub live_chain_endpoints: &'static [ChainEndpoint], } @@ -110,17 +179,21 @@ mod tests { #[test] fn every_preset_is_a_test_network() { for network in Network::value_variants() { - let config = network.config(); + // Reading the raw preset. An exported backend override cannot leak in. + let config = network.preset(); let mut routes = vec![ config.identity_backend_base, config.people_ws, config.bulletin_ws, + config.asset_hub_ws, ]; routes.extend(config.live_chain_endpoints.iter().map(|chain| chain.ws)); for route in routes { assert!( - route.contains("paseo") || route.contains("testnet"), + ["paseo", "testnet", "previewnet", "-stg."] + .iter() + .any(|marker| route.contains(marker)), "preset `{}` routes to a host that is not a recognised test \ network: {route}", config.id, @@ -128,4 +201,22 @@ mod tests { } } } + + #[test] + fn backend_override_replaces_only_the_backend_base() { + let preset = Network::PaseoNextV2.preset(); + let overridden = + apply_backend_override(preset, Some("http://localhost:8080/api/v1/".to_string())); + + assert_eq!( + overridden.identity_backend_base, "http://localhost:8080/api/v1", + "trailing slash is stripped" + ); + assert_eq!(overridden.asset_hub_ws, preset.asset_hub_ws); + assert_eq!( + apply_backend_override(preset, Some(" ".to_string())).identity_backend_base, + preset.identity_backend_base, + "a blank override keeps the preset" + ); + } } diff --git a/rust/crates/truapi-host-cli/src/register_name.rs b/rust/crates/truapi-host-cli/src/register_name.rs new file mode 100644 index 000000000..48713ec44 --- /dev/null +++ b/rust/crates/truapi-host-cli/src/register_name.rs @@ -0,0 +1,185 @@ +//! Full-person username registration through `DotnsGateway.register_name`. +//! +//! Builds and submits the v5 general transaction on Asset Hub. The signer's +//! RFC-0022 `uid.dot` account is the call's `who`. The full-person bandersnatch +//! key proves People-ring membership bound to the dotNS gateway context. A fresh +//! sr25519 signature over the inherited-implication digest travels in the +//! `AsDotnsGateway` extension. +//! +//! Requires a recognized full person. The account's full-person key must be +//! `Included` in a People ring already propagated to Asset Hub. + +use std::time::Duration; + +use anyhow::{Context, Result, bail}; +use tracing::debug; +use truapi_server::host_logic::dotns_gateway::{ + DOTNS_GATEWAY_CONTEXT, Link, build_register_proof_message, encode_register_full_name_extra, + encode_register_name_call, +}; +use truapi_server::host_logic::product_account::{ + SR25519_SIGNING_CONTEXT, derive_full_person_ring_vrf_entropy, derive_identity_keypair, +}; +use truapi_server::statement_allowance::extension::AS_DOTNS_GATEWAY; +use truapi_server::statement_allowance::{ + self as alloc, extension, extrinsic, proof, ring, rpc::RpcClient, +}; + +use crate::dotns_read::AssetHubReader; +use crate::network::NetworkConfig; + +/// Inputs for one full-person registration run. +pub struct RegisterNameConfig { + /// Network preset providing the People and Asset Hub endpoints. + pub network: NetworkConfig, + /// BIP-39 entropy of the signing host's root account. + pub entropy: Vec, + /// Base label to register (no digits, 6+ characters). + pub label: String, + /// Dotted lite username to link (`name.NN`). Without this and without + /// `chat_key`, the account's own lite username is looked up and linked. + pub link_lite: Option, + /// 65-byte ECDH chat key for a standalone (unlinked) registration. + pub chat_key: Option<[u8; 65]>, +} + +/// Registers (or claims) `label` as a full-person username. Waits until the +/// gateway records the account's alias on Asset Hub. +pub async fn register_name(config: &RegisterNameConfig) -> Result<()> { + let who = derive_identity_keypair(&config.entropy) + .map_err(|err| anyhow::anyhow!("uid.dot identity derivation failed: {err}"))?; + let who_public = who.public.to_bytes(); + + let mut reader = AssetHubReader::connect(config.network.asset_hub_ws).await?; + let link = resolve_link(config, &mut reader, &who_public).await?; + + // Reading the full-person member key's ring and its members from the People + // chain, pinned to one finalized block. + let people_rpc = RpcClient::connect(config.network.people_ws).await?; + let people_metadata = alloc::fetch_metadata(&people_rpc).await?; + let at = people_rpc.finalized_head().await?; + let full_entropy = derive_full_person_ring_vrf_entropy(&config.entropy); + let member = proof::member_key(full_entropy); + let ring_index = ring::read_member_ring_index( + &people_rpc, + &people_metadata, + ring::PEOPLE_IDENTIFIER, + &member, + &at, + ) + .await + .context("resolve full-person ring membership (is the account a recognized person?)")?; + let members = ring::read_collection_ring_members_at( + &people_rpc, + ring::PEOPLE_IDENTIFIER, + ring_index, + &at, + ) + .await?; + debug!( + ring_index, + members = members.len(), + "full-person ring resolved" + ); + + // Reading Asset Hub metadata, which asserts the RegisterFullName shape. + // Then the chain state and the exponent the subscriber verifies proofs + // against. + let ah_rpc = RpcClient::connect(config.network.asset_hub_ws).await?; + let ah_metadata = alloc::fetch_metadata(&ah_rpc).await?; + let chain_state = extension::ChainState { + // A restricted-origin general transaction. RestrictOrigins carries true. + restrict_origins: true, + ..alloc::fetch_chain_state(&ah_rpc).await? + }; + let info_variant = ah_metadata.dotns_register_full_name_variant()?; + let exponent = + ring::read_subscriber_ring_exponent(&ah_rpc, &ah_metadata, ring::PEOPLE_IDENTIFIER).await?; + let domain = proof::domain_for_ring_exponent(exponent)?; + + let call_indices = ah_metadata.call_indices("DotnsGateway", "register_name")?; + let call = encode_register_name_call(call_indices, &who_public, config.label.as_bytes(), &link); + + // Signing the inherited-implication digest with sr25519. The signature is + // carried in the AsDotnsGateway extension. + let digest = + extension::build_proof_message(&ah_metadata, &call, &chain_state, AS_DOTNS_GATEWAY)?; + let signature = who + .secret + .sign_simple(SR25519_SIGNING_CONTEXT, &digest, &who.public) + .to_bytes(); + + // Building the ring-VRF membership proof. It is bound to the gateway context + // and the registration intent. + let proof_message = build_register_proof_message(&who_public, config.label.as_bytes(), &link); + let ring_proof = proof::ring_vrf_proof( + domain, + full_entropy, + &members, + &DOTNS_GATEWAY_CONTEXT, + &proof_message, + )?; + + let extra = encode_register_full_name_extra(info_variant, &ring_proof, ring_index, &signature); + let extrinsic = extrinsic::build_unsigned_extrinsic( + &ah_metadata, + &chain_state, + &call, + AS_DOTNS_GATEWAY, + &extra, + )?; + + let block = ah_rpc.submit_and_watch(&extrinsic).await?; + println!("REGISTER_SUBMITTED label={} block={block}", config.label); + + wait_for_alias(&reader, &who_public).await?; + let identity = reader.dotns_identity(&who_public).await?; + println!( + "REGISTER_CONFIRMED label={} full_username={}", + config.label, + identity.full_username.as_deref().unwrap_or("") + ); + Ok(()) +} + +/// Resolves the `Link` argument. An explicit chat key wins, then an explicit +/// lite label, then the account's own on-chain lite username. +async fn resolve_link( + config: &RegisterNameConfig, + reader: &mut AssetHubReader, + who: &[u8; 32], +) -> Result { + if let Some(chat_key) = config.chat_key { + return Ok(Link::None(chat_key)); + } + if let Some(lite) = &config.link_lite { + return Ok(Link::LiteUsername(lite.as_bytes().to_vec())); + } + let identity = reader.dotns_identity(who).await?; + match identity.lite_username { + Some(lite) => { + debug!(%lite, "linking the account's own lite username"); + Ok(Link::LiteUsername(lite.into_bytes())) + } + None => bail!( + "account has no lite username to link; pass --link-lite or \ + --chat-key for a standalone registration" + ), + } +} + +/// Polls `DotnsGateway.AccountAlias[who]` until the registration lands. +async fn wait_for_alias(reader: &AssetHubReader, who: &[u8; 32]) -> Result<()> { + const MAX_ATTEMPTS: usize = 10; + for attempt in 1..=MAX_ATTEMPTS { + if let Some(alias) = reader.account_alias(who).await? { + println!("REGISTER_ALIAS alias=0x{}", hex::encode(alias)); + return Ok(()); + } + debug!("DotnsGateway.AccountAlias poll {attempt}/{MAX_ATTEMPTS}: empty"); + if attempt < MAX_ATTEMPTS { + tokio::time::sleep(Duration::from_secs(4)).await; + } + } + bail!("DotnsGateway.AccountAlias did not appear after registration") +} diff --git a/rust/crates/truapi-platform/src/lib.rs b/rust/crates/truapi-platform/src/lib.rs index 25e85f948..6fbd23d3b 100644 --- a/rust/crates/truapi-platform/src/lib.rs +++ b/rust/crates/truapi-platform/src/lib.rs @@ -50,6 +50,8 @@ pub struct PairingHostConfig { pub people_chain_genesis_hash: [u8; 32], /// Bulletin-chain genesis hash used for in-core preimage submission. pub bulletin_chain_genesis_hash: [u8; 32], + /// Asset Hub genesis hash used to resolve session usernames from dotNS. + pub asset_hub_chain_genesis_hash: [u8; 32], /// Deeplink URI scheme used in pairing QR payloads, without `://`. /// /// Host-spec L.2-L.3 define the `polkadotapp://pair` route and construction @@ -70,6 +72,8 @@ pub struct SigningHostConfig { pub people_chain_genesis_hash: [u8; 32], /// Bulletin-chain genesis hash used for in-core preimage submission. pub bulletin_chain_genesis_hash: [u8; 32], + /// Asset Hub genesis hash used to resolve session usernames from dotNS. + pub asset_hub_chain_genesis_hash: [u8; 32], } /// Product identity attached to one product-facing TrUAPI connection. @@ -139,6 +143,7 @@ impl PairingHostConfig { platform_info: PlatformInfo, people_chain_genesis_hash: [u8; 32], bulletin_chain_genesis_hash: [u8; 32], + asset_hub_chain_genesis_hash: [u8; 32], pairing_deeplink_scheme: String, ) -> Result { require_non_empty("pairing_deeplink_scheme", &pairing_deeplink_scheme)?; @@ -151,6 +156,7 @@ impl PairingHostConfig { host: HostRuntimeConfig::new(host_info, platform_info)?, people_chain_genesis_hash, bulletin_chain_genesis_hash, + asset_hub_chain_genesis_hash, pairing_deeplink_scheme, }; Ok(config) @@ -165,11 +171,13 @@ impl SigningHostConfig { platform_info: PlatformInfo, people_chain_genesis_hash: [u8; 32], bulletin_chain_genesis_hash: [u8; 32], + asset_hub_chain_genesis_hash: [u8; 32], ) -> Result { Ok(Self { host: HostRuntimeConfig::new(host_info, platform_info)?, people_chain_genesis_hash, bulletin_chain_genesis_hash, + asset_hub_chain_genesis_hash, }) } } diff --git a/rust/crates/truapi-platform/tests/bounds.rs b/rust/crates/truapi-platform/tests/bounds.rs index 29ce74543..6b8d2b9dd 100644 --- a/rust/crates/truapi-platform/tests/bounds.rs +++ b/rust/crates/truapi-platform/tests/bounds.rs @@ -97,6 +97,7 @@ fn pairing_config_validation_cases() { PlatformInfo::default(), [0xa2; 32], [0xbb; 32], + [0xcc; 32], case.pairing_deeplink_scheme.to_string(), ) .map(|_| ()); diff --git a/rust/crates/truapi-server/src/chain_runtime.rs b/rust/crates/truapi-server/src/chain_runtime.rs index 28aa4e8bd..50ac1d11f 100644 --- a/rust/crates/truapi-server/src/chain_runtime.rs +++ b/rust/crates/truapi-server/src/chain_runtime.rs @@ -1535,6 +1535,46 @@ pub(crate) async fn wait_for_chain_head_storage_value( } } +/// Waits for one runtime-API call operation's output from a +/// `chainHead_v1_follow` stream. +pub(crate) async fn wait_for_chain_head_call_output( + follow: &mut BoxStream<'static, RemoteChainHeadFollowItem>, + operation_id: &str, + label: &'static str, + timeout: Duration, +) -> Result, String> { + let timeout = futures_timer::Delay::new(timeout).fuse(); + pin_mut!(timeout); + loop { + let next = follow.next().fuse(); + pin_mut!(next); + futures::select! { + item = next => match item { + Some(RemoteChainHeadFollowItem::OperationCallDone { operation_id: item_operation_id, output }) + if item_operation_id == operation_id => + { + return Ok(output); + } + Some(RemoteChainHeadFollowItem::OperationInaccessible { operation_id: item_operation_id }) + if item_operation_id == operation_id => + { + return Err(format!("{label} runtime call was inaccessible")); + } + Some(RemoteChainHeadFollowItem::OperationError { operation_id: item_operation_id, error }) + if item_operation_id == operation_id => + { + return Err(error); + } + Some(RemoteChainHeadFollowItem::Stop) | None => { + return Err(format!("{label} follow stopped during runtime call")); + } + _ => {} + }, + () = timeout => return Err(format!("{label} runtime call timed out")), + } + } +} + #[cfg(test)] fn decode_hex(value: &str) -> Result, String> { hex::decode(value.strip_prefix("0x").unwrap_or(value)).map_err(|_| "invalid hex".to_string()) diff --git a/rust/crates/truapi-server/src/host_logic.rs b/rust/crates/truapi-server/src/host_logic.rs index e687fafa1..eae0da4d8 100644 --- a/rust/crates/truapi-server/src/host_logic.rs +++ b/rust/crates/truapi-server/src/host_logic.rs @@ -7,10 +7,10 @@ pub mod attestation; pub mod bulletin; pub mod dotns; +pub mod dotns_gateway; pub mod entropy; pub mod extrinsic; pub mod features; -pub mod identity; pub mod permissions; pub mod product_account; pub mod session; diff --git a/rust/crates/truapi-server/src/host_logic/attestation.rs b/rust/crates/truapi-server/src/host_logic/attestation.rs index 098edec4f..006d2d562 100644 --- a/rust/crates/truapi-server/src/host_logic/attestation.rs +++ b/rust/crates/truapi-server/src/host_logic/attestation.rs @@ -16,6 +16,7 @@ use verifiable::Error as VerifiableError; use verifiable::GenerateVerifiable; use verifiable::ring::bandersnatch::BandersnatchVrfVerifiable; +use crate::host_logic::dotns_gateway::build_reservation_message; use crate::host_logic::product_account::{ ProductAccountError, SR25519_SIGNING_CONTEXT, derive_identity_keypair, derive_lite_person_ring_vrf_entropy, product_public_key_to_address, @@ -56,10 +57,14 @@ pub struct LiteRegistration { pub ring_vrf_key: [u8; 32], /// Plain bandersnatch VRF proof over the same proof message. pub proof_of_ownership: [u8; 64], - /// 65-byte uncompressed P-256 identifier key. + /// 65-byte uncompressed P-256 identifier key. It doubles as the dotNS chat + /// key. pub identifier_key: [u8; 65], /// sr25519 signature over the SCALE consumer-registration tuple. pub consumer_registration_signature: [u8; 64], + /// sr25519 signature over the dotNS gateway reservation message. It + /// authorizes `pallet_dotns_gateway::reserve_name` on Asset Hub. + pub dotns_signature: [u8; 64], } /// Error while building lite-person registration parameters. @@ -78,10 +83,17 @@ pub enum LiteRegistrationError { /// Build the lite-person registration parameters for `username_base` /// (6+ lowercase letters, no digit suffix) against the backend `verifier`. +/// +/// `reserved_username` optionally queues a base name for a later full-person +/// claim on dotNS. `dotns_signed_at_secs` must be Asset Hub chain time, meaning +/// `Timestamp.Now` in seconds. The local wall clock will not do: the gateway +/// rejects signatures more than 30 seconds in the chain's future. pub fn build_lite_registration( entropy: &[u8], verifier_account_id: [u8; 32], username_base: &str, + reserved_username: Option<&str>, + dotns_signed_at_secs: u64, ) -> Result { // Registration, local activation, and the SSO responder all use the // RFC-0022 `uid.dot` default product account. @@ -111,7 +123,7 @@ pub fn build_lite_registration( verifier: verifier_account_id, identifier_key, username: username_base.as_bytes().to_vec(), - reserved_username: None, + reserved_username: reserved_username.map(|name| name.as_bytes().to_vec()), } .encode(); let consumer_registration_signature = candidate @@ -123,6 +135,23 @@ pub fn build_lite_registration( ) .to_bytes(); + let reservation_message = build_reservation_message( + &candidate_public_key, + &verifier_account_id, + username_base.as_bytes(), + &identifier_key, + reserved_username.map(str::as_bytes), + dotns_signed_at_secs, + ); + let dotns_signature = candidate + .secret + .sign_simple( + SR25519_SIGNING_CONTEXT, + &reservation_message, + &candidate.public, + ) + .to_bytes(); + Ok(LiteRegistration { candidate_account_id: product_public_key_to_address(candidate_public_key), candidate_public_key, @@ -131,6 +160,7 @@ pub fn build_lite_registration( proof_of_ownership, identifier_key, consumer_registration_signature, + dotns_signature, }) } @@ -172,7 +202,9 @@ mod tests { #[test] fn registration_params_have_expected_shapes_and_verify() { let verifier = [0x11u8; 32]; - let reg = build_lite_registration(&ENTROPY, verifier, "headlesstester").unwrap(); + let reg = + build_lite_registration(&ENTROPY, verifier, "headlesstester", None, 1_749_573_123) + .unwrap(); assert_eq!( reg.candidate_public_key, derive_identity_keypair(&ENTROPY).unwrap().public.to_bytes(), @@ -235,6 +267,70 @@ mod tests { .is_ok(), "consumer registration signature verifies against the runtime tuple" ); + + // dotnsSignature verifies over the gateway reservation message. The + // identifier key doubles as the chat key. + let reservation_message = build_reservation_message( + ®.candidate_public_key, + &verifier, + b"headlesstester", + ®.identifier_key, + None, + 1_749_573_123, + ); + let sig = Signature::from_bytes(®.dotns_signature).unwrap(); + assert!( + public + .verify_simple(SR25519_SIGNING_CONTEXT, &reservation_message, &sig) + .is_ok(), + "dotns reservation signature verifies against the gateway message" + ); + } + + #[test] + fn reserved_username_threads_into_both_signed_payloads() { + let verifier = [0x33u8; 32]; + let reg = build_lite_registration( + &ENTROPY, + verifier, + "headlesstester", + Some("reservedbase"), + 77, + ) + .unwrap(); + let public = PublicKey::from_bytes(®.candidate_public_key).unwrap(); + + let consumer_message = ( + reg.candidate_public_key, + verifier, + reg.identifier_key, + b"headlesstester".as_slice(), + Some(b"reservedbase".to_vec()), + ) + .encode(); + let sig = Signature::from_bytes(®.consumer_registration_signature).unwrap(); + assert!( + public + .verify_simple(SR25519_SIGNING_CONTEXT, &consumer_message, &sig) + .is_ok(), + "consumer registration signature commits to the reserved username" + ); + + let reservation_message = build_reservation_message( + ®.candidate_public_key, + &verifier, + b"headlesstester", + ®.identifier_key, + Some(b"reservedbase"), + 77, + ); + let sig = Signature::from_bytes(®.dotns_signature).unwrap(); + assert!( + public + .verify_simple(SR25519_SIGNING_CONTEXT, &reservation_message, &sig) + .is_ok(), + "dotns signature commits to the reserved username and signed_at" + ); } #[test] @@ -266,8 +362,8 @@ mod tests { #[test] fn registration_is_deterministic_per_entropy_and_username() { let verifier = [0x22u8; 32]; - let first = build_lite_registration(&ENTROPY, verifier, "aliceheadless").unwrap(); - let again = build_lite_registration(&ENTROPY, verifier, "aliceheadless").unwrap(); + let first = build_lite_registration(&ENTROPY, verifier, "aliceheadless", None, 1).unwrap(); + let again = build_lite_registration(&ENTROPY, verifier, "aliceheadless", None, 1).unwrap(); assert_eq!(first.candidate_public_key, again.candidate_public_key); assert_eq!(first.ring_vrf_key, again.ring_vrf_key); assert_eq!(first.candidate_account_id, again.candidate_account_id); diff --git a/rust/crates/truapi-server/src/host_logic/dotns_gateway.rs b/rust/crates/truapi-server/src/host_logic/dotns_gateway.rs new file mode 100644 index 000000000..5cb53af61 --- /dev/null +++ b/rust/crates/truapi-server/src/host_logic/dotns_gateway.rs @@ -0,0 +1,887 @@ +//! dotNS gateway (Asset Hub) payload building and contract reads. +//! +//! Every host shares these helpers. They cover the candidate-signed +//! reservation message for `pallet_dotns_gateway::reserve_name`, the ring-VRF +//! proof message for `register_name`, the minimal Solidity ABI surface for +//! reading usernames back through `ReviveApi_call`, and the raw storage keys of +//! the gateway pallet. +//! +//! Username resolution ([`discover_pop_controller`], [`resolve_labels`]) is +//! written once against the [`DotnsTransport`] trait. The headless CLI's +//! plain-RPC reader and the in-core `chainHead_v1` lookup therefore walk the +//! same contract chain. +//! +//! Byte layouts mirror `pallets/dotns-gateway` in `paritytech/individuality` +//! and the dotNS contracts (`paritytech/dotns`). + +use parity_scale_codec::{Compact, Decode, Encode}; +use sp_crypto_hashing::{blake2_128, blake2_256, keccak_256, twox_128}; +use thiserror::Error; + +/// Ring-VRF context for `register_name` proofs. +/// +/// The ASCII label, space-padded to 32 bytes. Matches `DOTNS_GATEWAY_CONTEXT` +/// in the gateway pallet. +pub const DOTNS_GATEWAY_CONTEXT: [u8; 32] = *b"pop:polkadot.network/dotns "; + +/// Reservation message prefix, matching `RESERVE_MSG_PREFIX` in the gateway +/// pallet. +pub const RESERVE_MSG_PREFIX: &[u8] = b"pop:dotns-gateway:reserve"; + +/// Origin for read-only `ReviveApi_call` dry-runs. +/// +/// `pallet_revive` rejects unmapped signed origins with `AccountUnmapped`. +/// Eth-derived accounts — trailing 12 bytes `0xEE` — are implicitly mapped. +/// They need no on-chain state. Views therefore work from this synthetic +/// account on any network. +pub const VIEW_CALL_ORIGIN: [u8; 32] = { + let mut origin = [0xEEu8; 32]; + let mut i = 0; + while i < 20 { + origin[i] = 0; + i += 1; + } + origin +}; + +/// Builds the payload the candidate signs to authorize a lite-name +/// reservation. +/// +/// Commits to the *base* of the lite label only. The digit suffix is excluded, +/// so the candidate can sign before the gateway assigns digits. Mirrors +/// `Pallet::construct_reservation_message`: a SCALE tuple of +/// `(prefix, candidate, attester, username_base, chat_key, reserved, signed_at)`. +pub fn build_reservation_message( + candidate: &[u8; 32], + attester: &[u8; 32], + username_base: &[u8], + chat_key: &[u8; 65], + reserved_base_label: Option<&[u8]>, + signed_at_secs: u64, +) -> Vec { + ( + RESERVE_MSG_PREFIX, + candidate, + attester, + username_base, + chat_key.as_slice(), + reserved_base_label, + signed_at_secs, + ) + .encode() +} + +/// The `Link` argument of `register_name`: how the new full-person username +/// relates to the account's previous lite identity. +#[derive(Debug, Clone, PartialEq, Eq, Encode)] +pub enum Link { + /// Links the full username to an existing lite username, in dotted form + /// such as `alice.01`. The chat key is inherited from the lite entry. + LiteUsername(Vec), + /// Standalone registration carrying a fresh 65-byte uncompressed ECDH + /// chat key. + None([u8; 65]), +} + +/// Hashes the registration intent into the 32-byte ring-VRF proof message. +/// Matches the pallet's `(who, label, link).using_encoded(blake2_256)`. +pub fn build_register_proof_message(who: &[u8; 32], label: &[u8], link: &Link) -> [u8; 32] { + blake2_256(&(who, label, link).encode()) +} + +/// Encodes the `DotnsGateway.register_name { who, label, link }` call. +/// Layout: dispatch indices, the raw account, the compact-prefixed label, then +/// the link. +pub fn encode_register_name_call( + call_indices: [u8; 2], + who: &[u8; 32], + label: &[u8], + link: &Link, +) -> Vec { + let mut call = call_indices.to_vec(); + (who, label, link).encode_to(&mut call); + call +} + +/// Encodes the `AsDotnsGateway` extension extra. +/// +/// The value is `Some(RegisterFullName { proof, ring_index, signature })`. The +/// signature is wrapped as `MultiSignature::Sr25519`. +pub fn encode_register_full_name_extra( + info_variant: u8, + proof: &[u8], + ring_index: u32, + signature: &[u8; 64], +) -> Vec { + // Option::Some ‖ info variant ‖ Vec(proof) ‖ ring_index ‖ Sr25519 ‖ sig. + let mut extra = vec![0x01, info_variant]; + proof.encode_to(&mut extra); + ring_index.encode_to(&mut extra); + extra.push(0x01); + extra.extend_from_slice(signature); + extra +} + +/// Maps an AccountId32 to the H160 the contracts see. +/// +/// Mirrors `pallet_revive::AccountId32Mapper::to_address`. Eth-derived +/// accounts — trailing 12 bytes all `0xEE` — truncate. Everything else hashes. +pub fn account_to_h160(public_key: &[u8; 32]) -> [u8; 20] { + if public_key[20..].iter().all(|byte| *byte == 0xEE) { + public_key[..20] + .try_into() + .expect("slice of first 20 bytes from a 32-byte array; qed") + } else { + keccak_256(public_key)[12..] + .try_into() + .expect("keccak_256 output is 32 bytes, tail slice is 20; qed") + } +} + +/// Solidity `bytes32("name")` literal: ASCII left-aligned in a 32-byte word. +/// The dotNS protocol registry keys (`storeFactory`, `registrar`, ...) use this +/// form. They are not hashes. +pub fn registry_key(name: &str) -> [u8; 32] { + let bytes = name.as_bytes(); + assert!(bytes.len() <= 32, "registry key exceeds 32 bytes"); + let mut out = [0u8; 32]; + out[..bytes.len()].copy_from_slice(bytes); + out +} + +/// First 4 bytes of `keccak256(signature)` — the Solidity function selector. +pub fn selector(signature: &str) -> [u8; 4] { + keccak_256(signature.as_bytes())[..4] + .try_into() + .expect("keccak_256 output is 32 bytes, head slice is 4; qed") +} + +/// Calldata for a view function without arguments. +pub fn call_no_args(signature: &str) -> Vec { + selector(signature).to_vec() +} + +/// Calldata for a view function taking one `address` argument. +pub fn call_address(signature: &str, address: &[u8; 20]) -> Vec { + let mut data = call_no_args(signature); + data.extend_from_slice(&[0u8; 12]); + data.extend_from_slice(address); + data +} + +/// Calldata for a view function taking one `bytes32` argument. +pub fn call_bytes32(signature: &str, word: &[u8; 32]) -> Vec { + let mut data = call_no_args(signature); + data.extend_from_slice(word); + data +} + +/// Calldata for a view function taking two `uint256` arguments. +pub fn call_u256_pair(signature: &str, first: u64, second: u64) -> Vec { + let mut data = call_no_args(signature); + for value in [first, second] { + let mut word = [0u8; 32]; + word[24..].copy_from_slice(&value.to_be_bytes()); + data.extend_from_slice(&word); + } + data +} + +/// Error decoding a dotNS contract round-trip. +#[derive(Debug, Error)] +pub enum DotnsContractError { + /// `ContractResult` bytes ended before the expected field. + #[error("ContractResult truncated at {context}")] + Truncated { + /// Field being decoded when the input ended. + context: &'static str, + }, + /// The runtime rejected the call before reaching the contract. + #[error("ReviveApi_call dispatch failed: {detail}")] + Dispatch { + /// Raw hex of the `DispatchError` payload. + detail: String, + }, + /// The contract executed but reverted. + #[error("contract reverted: {detail}")] + Reverted { + /// Rendered revert reason. + detail: String, + }, + /// ABI return data did not match the expected shape. + #[error("ABI decode failed at {context}")] + Abi { + /// Value being decoded when the shape mismatched. + context: &'static str, + }, + /// ABI return data was not valid UTF-8 where a string was expected. + #[error("ABI string is not UTF-8")] + AbiUtf8, +} + +/// SCALE arguments for the `ReviveApi_call` runtime API. +/// A zero-value, unlimited-gas dry-run of `input` against `dest` from `origin`. +pub fn encode_revive_call(origin: &[u8; 32], dest: &[u8; 20], input: &[u8]) -> Vec { + let mut args = Vec::with_capacity(32 + 20 + 16 + 2 + 5 + input.len()); + args.extend_from_slice(origin); + args.extend_from_slice(dest); + args.extend_from_slice(&0u128.to_le_bytes()); + // gas_limit: Option and storage_deposit_limit: Option. + // Both absent, so the node estimates. + args.push(0x00); + args.push(0x00); + input.encode_to(&mut args); + args +} + +/// Extracts the contract return data from a `ReviveApi_call` output. +/// The output is a `pallet_revive::ContractResult`. Dispatch errors and reverts +/// are rejected. +pub fn decode_revive_call_output(output: &[u8]) -> Result, DotnsContractError> { + let truncated = |context| DotnsContractError::Truncated { context }; + let input = &mut &output[..]; + // weight_consumed + weight_required. Weight is a compact ref_time plus a + // compact proof_size. + for _ in 0..4 { + Compact::::decode(input).map_err(|_| truncated("weight"))?; + } + // storage_deposit + max_storage_deposit. StorageDeposit is variant ‖ u128. + for _ in 0..2 { + <(u8, u128)>::decode(input).map_err(|_| truncated("storage_deposit"))?; + } + u128::decode(input).map_err(|_| truncated("gas_consumed"))?; + match u8::decode(input).map_err(|_| truncated("result"))? { + 0 => { + let flags = u32::decode(input).map_err(|_| truncated("flags"))?; + let data = Vec::::decode(input).map_err(|_| truncated("data"))?; + // ReturnFlags::REVERT. + if flags & 1 == 1 { + return Err(DotnsContractError::Reverted { + detail: describe_revert(&data), + }); + } + Ok(data) + } + _ => Err(DotnsContractError::Dispatch { + detail: format!("0x{}", hex::encode(*input)), + }), + } +} + +/// Renders a Solidity revert payload. Recognizes `Error(string)` and +/// `Panic(uint256)`. +fn describe_revert(data: &[u8]) -> String { + if data.is_empty() { + return "(empty)".to_string(); + } + if data.starts_with(&[0x08, 0xc3, 0x79, 0xa0]) && data.len() >= 68 { + let len = u64::from_be_bytes( + data[60..68] + .try_into() + .expect("8-byte slice from a length-checked buffer; qed"), + ) as usize; + if let Some(message) = data + .get(68..68 + len) + .and_then(|bytes| core::str::from_utf8(bytes).ok()) + { + return format!("Error({message:?})"); + } + } + if data.starts_with(&[0x4e, 0x48, 0x7b, 0x71]) && data.len() >= 36 { + return format!("Panic(0x{})", hex::encode(&data[32..36])); + } + format!("0x{}", hex::encode(data)) +} + +/// Reads the 32-byte ABI word at index `index`. +fn word(data: &[u8], index: usize) -> Result<&[u8], DotnsContractError> { + data.get(index * 32..(index + 1) * 32) + .ok_or(DotnsContractError::Abi { context: "word" }) +} + +/// Interprets an ABI word as a right-aligned usize offset or length. +fn word_usize(data: &[u8], index: usize) -> Result { + let bytes = word(data, index)?; + if bytes[..24].iter().any(|b| *b != 0) { + return Err(DotnsContractError::Abi { + context: "oversized word", + }); + } + Ok(u64::from_be_bytes( + bytes[24..] + .try_into() + .expect("8-byte tail of a 32-byte word; qed"), + ) as usize) +} + +/// Decodes an ABI `string` at byte offset `at`. +/// The layout is a length word followed by padded UTF-8 bytes. +fn decode_string_at(data: &[u8], at: usize) -> Result { + let region = data.get(at..).ok_or(DotnsContractError::Abi { + context: "string offset", + })?; + let len = word_usize(region, 0)?; + let bytes = region.get(32..32 + len).ok_or(DotnsContractError::Abi { + context: "string bytes", + })?; + String::from_utf8(bytes.to_vec()).map_err(|_| DotnsContractError::AbiUtf8) +} + +/// Decodes a single `address` return value. +pub fn decode_address(data: &[u8]) -> Result<[u8; 20], DotnsContractError> { + let bytes = word(data, 0)?; + if bytes[..12].iter().any(|b| *b != 0) { + return Err(DotnsContractError::Abi { context: "address" }); + } + Ok(bytes[12..] + .try_into() + .expect("20-byte tail of a 32-byte word; qed")) +} + +/// Decodes a `string[]` return value (`LabelStore.getLabels`). +pub fn decode_string_array(data: &[u8]) -> Result, DotnsContractError> { + let array_at = word_usize(data, 0)?; + let array = data.get(array_at..).ok_or(DotnsContractError::Abi { + context: "array offset", + })?; + let len = word_usize(array, 0)?; + let elements = array.get(32..).ok_or(DotnsContractError::Abi { + context: "array elements", + })?; + (0..len) + .map(|i| decode_string_at(elements, word_usize(elements, i)?)) + .collect() +} + +/// Decodes `DotnsPopController.pendingClaim(address)`, an ABI +/// `(string label, uint64 mintedAt)`. An empty label means no pending claim. +pub fn decode_pending_claim(data: &[u8]) -> Result<(String, u64), DotnsContractError> { + let struct_at = word_usize(data, 0)?; + let claim = data.get(struct_at..).ok_or(DotnsContractError::Abi { + context: "struct offset", + })?; + let label = decode_string_at(claim, word_usize(claim, 0)?)?; + let minted_at = word_usize(claim, 1)? as u64; + Ok((label, minted_at)) +} + +/// Decodes the deployed-era `pendingClaims(address)`, an ABI +/// `(string label, uint64 mintedAt)[]`. Returns the labels. +pub fn decode_pending_claims_array(data: &[u8]) -> Result, DotnsContractError> { + let array_at = word_usize(data, 0)?; + let array = data.get(array_at..).ok_or(DotnsContractError::Abi { + context: "claims offset", + })?; + let len = word_usize(array, 0)?; + let elements = array.get(32..).ok_or(DotnsContractError::Abi { + context: "claims elements", + })?; + (0..len) + .map(|i| { + let claim = + elements + .get(word_usize(elements, i)?..) + .ok_or(DotnsContractError::Abi { + context: "claim offset", + })?; + decode_string_at(claim, word_usize(claim, 0)?) + }) + .collect() +} + +/// Usernames of one account as recorded by the dotNS contracts. +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub struct DotnsIdentity { + /// Dotted lite username (`alice.01`), if any. + pub lite_username: Option, + /// Full-person username, if any. + pub full_username: Option, +} + +/// Classifies flat contract labels into lite and full usernames. +/// +/// A label with an all-alphabetic stem and exactly two trailing digits is a lite +/// username, the PopRules lite format. Those are re-dotted (`alice01` → +/// `alice.01`). Everything else is a full username. First hit per slot wins. +pub fn classify_labels(labels: I) -> DotnsIdentity +where + I: IntoIterator, + I::Item: AsRef, +{ + let mut identity = DotnsIdentity::default(); + for label in labels { + let label = label.as_ref().trim_end_matches(".dot"); + let (stem, digits) = label.split_at(label.len().saturating_sub(2)); + let is_lite = !stem.is_empty() + && stem.chars().all(|c| c.is_ascii_lowercase()) + && digits.len() == 2 + && digits.chars().all(|c| c.is_ascii_digit()); + if is_lite { + identity + .lite_username + .get_or_insert_with(|| format!("{stem}.{digits}")); + } else if !label.is_empty() { + identity + .full_username + .get_or_insert_with(|| label.to_string()); + } + } + identity +} + +/// Page size for `LabelStore.getLabels`. Gateway users hold at most a lite and +/// a full name. +const LABEL_PAGE_LIMIT: u64 = 16; + +/// How a caller reaches Asset Hub for dotNS reads. +/// +/// The surface is one storage read plus one contract view. The headless CLI +/// implements it over plain RPC. The in-core runtime implements it over a +/// `chainHead_v1` follow. The resolution steps below are written once. +#[truapi_platform::async_trait] +pub trait DotnsTransport { + /// Reads one storage value. `None` when the entry is absent. + async fn storage(&mut self, key: Vec) -> Result>, String>; + + /// Dry-runs a contract view against `dest` and returns its return data. + async fn view(&mut self, dest: &[u8; 20], input: Vec) -> Result, String>; +} + +/// Resolves the `DotnsPopController` address. +/// +/// The pallet's stored controller wins when present. Otherwise the dispatcher's +/// target getter answers: `target()` on deployed-era contracts, `TARGET()` on +/// current dotNS `master`. `None` when the gateway is not deployed on the chain +/// at all. +pub async fn discover_pop_controller( + transport: &mut T, +) -> Result, String> { + if let Some(controller) = transport + .storage(pop_controller_address_key()) + .await? + .and_then(|value| <[u8; 20]>::try_from(value).ok()) + { + return Ok(Some(controller)); + } + let Some(dispatcher) = transport + .storage(dispatcher_address_key()) + .await? + .and_then(|value| <[u8; 20]>::try_from(value).ok()) + else { + return Ok(None); + }; + let output = match transport.view(&dispatcher, call_no_args("target()")).await { + Ok(output) => output, + Err(_) => { + transport + .view(&dispatcher, call_no_args("TARGET()")) + .await? + } + }; + decode_address(&output) + .map(Some) + .map_err(|err| format!("RootGatewayDispatcher target getter: {err}")) +} + +/// Resolves the flat contract labels `account` holds. +/// +/// The warm path reads the account's `LabelStore`. The cold path reads its +/// pending claim on the controller, covering gateway-minted names before the +/// user settles their store. +pub async fn resolve_labels( + transport: &mut T, + controller: &[u8; 20], + account: &[u8; 32], +) -> Result, String> { + let user = account_to_h160(account); + + let registry_output = transport + .view(controller, call_no_args("protocolRegistry()")) + .await?; + let registry = decode_address(®istry_output) + .map_err(|err| format!("DotnsPopController.protocolRegistry(): {err}"))?; + + let factory_output = transport + .view( + ®istry, + call_bytes32("get(bytes32)", ®istry_key("storeFactory")), + ) + .await?; + let factory = decode_address(&factory_output) + .map_err(|err| format!("ProtocolRegistry.get(storeFactory): {err}"))?; + + let store_output = transport + .view(&factory, call_address("getLabelStore(address)", &user)) + .await?; + let store = decode_address(&store_output) + .map_err(|err| format!("StoreFactory.getLabelStore: {err}"))?; + + if store != [0u8; 20] { + let labels_output = transport + .view( + &store, + call_u256_pair("getLabels(uint256,uint256)", 0, LABEL_PAGE_LIMIT), + ) + .await?; + return decode_string_array(&labels_output) + .map_err(|err| format!("LabelStore.getLabels: {err}")); + } + + // The current controller exposes `pendingClaim`, one struct. The + // deployed-era one exposes `pendingClaims`, an array. + match transport + .view(controller, call_address("pendingClaim(address)", &user)) + .await + { + Ok(claim_output) => { + let (label, minted_at) = decode_pending_claim(&claim_output) + .map_err(|err| format!("DotnsPopController.pendingClaim: {err}"))?; + Ok(if minted_at == 0 { vec![] } else { vec![label] }) + } + Err(_) => { + let claims_output = transport + .view(controller, call_address("pendingClaims(address)", &user)) + .await?; + decode_pending_claims_array(&claims_output) + .map_err(|err| format!("DotnsPopController.pendingClaims: {err}")) + } + } +} + +/// `DotnsGateway.PopControllerAddress` storage key. +/// Older runtimes store the controller directly. It is absent on runtimes that +/// wire a dispatcher instead. +pub fn pop_controller_address_key() -> Vec { + plain_key(b"DotnsGateway", b"PopControllerAddress") +} + +/// `DotnsGateway.DispatcherAddress` storage key. +/// The entry holds the `RootGatewayDispatcher`. Its `target()` is the +/// `DotnsPopController`. +pub fn dispatcher_address_key() -> Vec { + plain_key(b"DotnsGateway", b"DispatcherAddress") +} + +/// `DotnsGateway.AccountAlias[account]` storage key. +/// The value is the 32-byte alias the account registered with. +pub fn account_alias_key(account: &[u8; 32]) -> Vec { + let mut key = plain_key(b"DotnsGateway", b"AccountAlias"); + key.extend_from_slice(&blake2_128(account)); + key.extend_from_slice(account); + key +} + +/// `Timestamp.Now` storage key. The value is a u64 of Unix milliseconds. +pub fn timestamp_now_key() -> Vec { + plain_key(b"Timestamp", b"Now") +} + +/// `twox128(pallet) ‖ twox128(entry)`. +fn plain_key(pallet: &[u8], entry: &[u8]) -> Vec { + [twox_128(pallet).as_slice(), twox_128(entry).as_slice()].concat() +} + +#[cfg(test)] +mod tests { + use super::*; + + // Golden vectors from the validated reference implementation in + // pop-dotns-testing: reservation.ts, proof.ts, verify.ts, abi.ts. + const RESERVATION_WITH_RESERVED: &str = "64706f703a646f746e732d676174657761793a72657365727665111111111111111111111111111111111111111111111111111111111111111122222222222222222222222222222222222222222222222222222222222222221c616c696365626305013333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333013072657365727665646e616d65035e486800000000"; + const RESERVATION_NO_RESERVED: &str = "64706f703a646f746e732d676174657761793a72657365727665111111111111111111111111111111111111111111111111111111111111111122222222222222222222222222222222222222222222222222222222222222221c616c69636562630501333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333300035e486800000000"; + + #[test] + fn reservation_message_matches_reference_vectors() { + let with_reserved = build_reservation_message( + &[0x11; 32], + &[0x22; 32], + b"alicebc", + &[0x33; 65], + Some(b"reservedname"), + 1749573123, + ); + assert_eq!(hex::encode(&with_reserved), RESERVATION_WITH_RESERVED); + + let without = build_reservation_message( + &[0x11; 32], + &[0x22; 32], + b"alicebc", + &[0x33; 65], + None, + 1749573123, + ); + assert_eq!(hex::encode(&without), RESERVATION_NO_RESERVED); + } + + #[test] + fn link_and_proof_message_match_reference_vectors() { + let lite = Link::LiteUsername(b"alice.01".to_vec()); + assert_eq!(hex::encode(lite.encode()), "0020616c6963652e3031"); + assert_eq!( + hex::encode(build_register_proof_message(&[0x44; 32], b"alicebc", &lite)), + "506fe3afcb13b2e4fd182d49ff165bc9c834f9dd21d0d545eccb925993b68675" + ); + + let standalone = Link::None([0x55; 65]); + assert_eq!( + hex::encode(standalone.encode()), + format!("01{}", hex::encode([0x55; 65])) + ); + assert_eq!( + hex::encode(build_register_proof_message( + &[0x44; 32], + b"alicebc", + &standalone + )), + "255da65ea6687123d5ac035a438a5c6fa18f7d04f23433611095a0fe58c479ab" + ); + } + + #[test] + fn register_name_call_and_extension_extra_have_the_reference_layout() { + let link = Link::LiteUsername(b"alice.01".to_vec()); + let call = encode_register_name_call([0x6a, 0x01], &[0x44; 32], b"alicebc", &link); + assert_eq!(&call[..2], &[0x6a, 0x01]); + assert_eq!(&call[2..34], &[0x44; 32]); + assert_eq!( + &call[34..], + &[b"alicebc".to_vec().encode(), link.encode()].concat()[..] + ); + + // Layout: 0x01 (Some) ‖ variant ‖ compact-len proof ‖ ring_index LE ‖ + // 0x01 (Sr25519) ‖ signature. Matches registerTx.ts + // encodeAsDotnsGatewayValue. + let extra = encode_register_full_name_extra(0, &[0xEE; 785], 3, &[0xAB; 64]); + assert_eq!( + extra, + [ + vec![0x01, 0x00], + vec![0xEEu8; 785].encode(), + 3u32.to_le_bytes().to_vec(), + vec![0x01], + vec![0xAB; 64], + ] + .concat() + ); + } + + #[test] + fn selectors_match_reference_vectors() { + assert_eq!(hex::encode(selector("getLabelStore(address)")), "4fc5dce9"); + assert_eq!( + hex::encode(selector("getLabels(uint256,uint256)")), + "2d7b5794" + ); + assert_eq!(hex::encode(selector("pendingClaim(address)")), "0ef4b248"); + assert_eq!(hex::encode(selector("protocolRegistry()")), "7656419f"); + assert_eq!(hex::encode(selector("get(bytes32)")), "8eaa6ac0"); + assert_eq!(hex::encode(selector("target()")), "d4b83992"); + } + + #[test] + fn calldata_encoders_place_arguments_in_padded_words() { + let address_call = call_address("getLabelStore(address)", &[0xAA; 20]); + assert_eq!(address_call.len(), 4 + 32); + assert_eq!(&address_call[..4], &selector("getLabelStore(address)")); + assert!(address_call[4..16].iter().all(|b| *b == 0)); + assert_eq!(&address_call[16..], &[0xAA; 20]); + + let pair_call = call_u256_pair("getLabels(uint256,uint256)", 0, 16); + assert_eq!(pair_call.len(), 4 + 64); + assert_eq!(pair_call[35], 0); + assert_eq!(pair_call[67], 16); + + let key_call = call_bytes32("get(bytes32)", ®istry_key("storeFactory")); + assert_eq!(&key_call[4..16], b"storeFactory"); + assert!(key_call[16..36].iter().all(|b| *b == 0)); + } + + #[test] + fn account_to_h160_follows_the_revive_mapper() { + let mut eth_derived = [0x0Fu8; 32]; + eth_derived[20..].fill(0xEE); + assert_eq!(account_to_h160(ð_derived), [0x0F; 20]); + + let hashed = account_to_h160(&[0x11; 32]); + assert_eq!(hashed, keccak_256(&[0x11; 32])[12..]); + } + + /// Encodes a `ContractResult` prefix followed by `result`. + fn contract_result(result: &[u8]) -> Vec { + let mut out = Vec::new(); + for _ in 0..4 { + Compact(7u64).encode_to(&mut out); + } + for _ in 0..2 { + (1u8, 0u128).encode_to(&mut out); + } + 0u128.encode_to(&mut out); + out.extend_from_slice(result); + out + } + + #[test] + fn revive_call_output_surfaces_data_reverts_and_dispatch_errors() { + let mut ok = vec![0u8]; + 0u32.encode_to(&mut ok); + vec![0xABu8, 0xCD].encode_to(&mut ok); + assert_eq!( + decode_revive_call_output(&contract_result(&ok)).unwrap(), + vec![0xAB, 0xCD] + ); + + // Error("nope") revert payload under ReturnFlags::REVERT. + let mut revert_data = vec![0x08, 0xc3, 0x79, 0xa0]; + revert_data.extend_from_slice(&{ + let mut word = [0u8; 32]; + word[31] = 0x20; + word + }); + revert_data.extend_from_slice(&{ + let mut word = [0u8; 32]; + word[31] = 4; + word + }); + revert_data.extend_from_slice(b"nope"); + revert_data.extend_from_slice(&[0u8; 28]); + let mut reverted = vec![0u8]; + 1u32.encode_to(&mut reverted); + revert_data.encode_to(&mut reverted); + let err = decode_revive_call_output(&contract_result(&reverted)).unwrap_err(); + assert!( + matches!(&err, DotnsContractError::Reverted { detail } if detail == "Error(\"nope\")"), + "unexpected error: {err}" + ); + + let dispatch = contract_result(&[0x01, 0x02, 0x03]); + assert!(matches!( + decode_revive_call_output(&dispatch).unwrap_err(), + DotnsContractError::Dispatch { .. } + )); + } + + /// ABI word with `value` right-aligned. + fn abi_word(value: u64) -> [u8; 32] { + let mut word = [0u8; 32]; + word[24..].copy_from_slice(&value.to_be_bytes()); + word + } + + /// ABI string tail: a length word plus padded bytes. + fn abi_string(value: &str) -> Vec { + let mut out = abi_word(value.len() as u64).to_vec(); + out.extend_from_slice(value.as_bytes()); + out.resize(out.len().div_ceil(32) * 32, 0); + out + } + + #[test] + fn abi_decoders_handle_addresses_string_arrays_and_pending_claims() { + let mut address = [0u8; 32]; + address[12..].fill(0xBC); + assert_eq!(decode_address(&address).unwrap(), [0xBC; 20]); + assert!(decode_address(&[0xFF; 32]).is_err()); + + // string[] = ["alice01", "bob"]. + let mut array = abi_word(0x20).to_vec(); + array.extend_from_slice(&abi_word(2)); + array.extend_from_slice(&abi_word(0x40)); + array.extend_from_slice(&abi_word(0x40 + 0x40)); + array.extend_from_slice(&abi_string("alice01")); + array.extend_from_slice(&abi_string("bob")); + assert_eq!( + decode_string_array(&array).unwrap(), + vec!["alice01".to_string(), "bob".to_string()] + ); + assert_eq!( + decode_string_array(&[abi_word(0x20), abi_word(0)].concat()).unwrap(), + Vec::::new() + ); + + // pendingClaim = ("alice", 42). + let mut claim = abi_word(0x20).to_vec(); + claim.extend_from_slice(&abi_word(0x40)); + claim.extend_from_slice(&abi_word(42)); + claim.extend_from_slice(&abi_string("alice")); + assert_eq!( + decode_pending_claim(&claim).unwrap(), + ("alice".to_string(), 42) + ); + + // pendingClaims = [("alice01", 42), ("bob", 7)]. Deployed-era plural. + let struct_a = [ + abi_word(0x40).to_vec(), + abi_word(42).to_vec(), + abi_string("alice01"), + ] + .concat(); + let struct_b = [ + abi_word(0x40).to_vec(), + abi_word(7).to_vec(), + abi_string("bob"), + ] + .concat(); + let mut claims = abi_word(0x20).to_vec(); + claims.extend_from_slice(&abi_word(2)); + claims.extend_from_slice(&abi_word(0x40)); + claims.extend_from_slice(&abi_word(0x40 + struct_a.len() as u64)); + claims.extend_from_slice(&struct_a); + claims.extend_from_slice(&struct_b); + assert_eq!( + decode_pending_claims_array(&claims).unwrap(), + vec!["alice01".to_string(), "bob".to_string()] + ); + assert_eq!( + decode_pending_claims_array(&[abi_word(0x20), abi_word(0)].concat()).unwrap(), + Vec::::new() + ); + } + + #[test] + fn labels_classify_into_lite_and_full_usernames() { + let identity = classify_labels(["alice01", "myproject"]); + assert_eq!(identity.lite_username.as_deref(), Some("alice.01")); + assert_eq!(identity.full_username.as_deref(), Some("myproject")); + + // `.dot` suffixes are tolerated. Digit-bearing stems are full names. + let identity = classify_labels(["bobby42.dot", "web3app"]); + assert_eq!(identity.lite_username.as_deref(), Some("bobby.42")); + assert_eq!(identity.full_username.as_deref(), Some("web3app")); + + assert_eq!( + classify_labels(Vec::::new()), + DotnsIdentity::default() + ); + // One trailing digit or a digit inside the stem is not lite format. + let identity = classify_labels(["alice1", "a2b34"]); + assert_eq!(identity.lite_username, None); + assert_eq!(identity.full_username.as_deref(), Some("alice1")); + } + + #[test] + fn storage_keys_have_the_expected_layout() { + let alias_key = account_alias_key(&[0x11; 32]); + let prefix = [ + twox_128(b"DotnsGateway").as_slice(), + twox_128(b"AccountAlias").as_slice(), + ] + .concat(); + assert_eq!(&alias_key[..32], prefix.as_slice()); + // Blake2_128Concat over the raw account bytes. + assert_eq!(&alias_key[48..], &[0x11; 32]); + + assert_eq!(pop_controller_address_key().len(), 32); + assert_eq!(dispatcher_address_key().len(), 32); + assert_eq!(timestamp_now_key().len(), 32); + } + + #[test] + fn revive_call_args_encode_origin_dest_and_input() { + let args = encode_revive_call(&[0x11; 32], &[0x22; 20], &[0xAB, 0xCD]); + assert_eq!(&args[..32], &[0x11; 32]); + assert_eq!(&args[32..52], &[0x22; 20]); + assert_eq!(&args[52..68], &[0u8; 16], "value is zero"); + assert_eq!(&args[68..70], &[0x00, 0x00], "no gas or deposit limit"); + assert_eq!(&args[70..], vec![0xABu8, 0xCD].encode().as_slice()); + } +} diff --git a/rust/crates/truapi-server/src/host_logic/identity.rs b/rust/crates/truapi-server/src/host_logic/identity.rs deleted file mode 100644 index bcf0d834a..000000000 --- a/rust/crates/truapi-server/src/host_logic/identity.rs +++ /dev/null @@ -1,148 +0,0 @@ -//! People-chain identity lookup for paired SSO sessions. -//! -//! The previous host-papp path read `Resources.Consumers[account]` from -//! the People chain and used only the username fields. Keep this module narrow: -//! it builds that storage key and decodes the leading username fields from the -//! SCALE value. The record begins with a fixed identifier public key; credibility -//! and statement-store slots are intentionally ignored. -//! Host-spec G defines the cross-host identity model and lookup behavior: -//! - -use parity_scale_codec::{Decode, Encode}; -use sp_crypto_hashing::{blake2_128, twox_128}; - -/// Username fields read from a People-chain `Resources.Consumers` record. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct PeopleIdentity { - /// Lite username; `None` when the record stores an empty string. - pub lite_username: Option, - /// Full username, when the account has registered one. - pub full_username: Option, -} - -#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode)] -struct ConsumerUsernamePrefix { - identifier_key: [u8; 65], - full_username: Option>, - lite_username: Vec, -} - -/// Build the People-chain `Resources.Consumers` storage key for `account_id`. -pub fn resources_consumers_storage_key(account_id: &[u8; 32]) -> Vec { - let mut key = Vec::with_capacity(32 + 16 + account_id.len()); - key.extend_from_slice(&twox_128(b"Resources")); - key.extend_from_slice(&twox_128(b"Consumers")); - key.extend_from_slice(&blake2_128(account_id)); - key.extend_from_slice(account_id); - key -} - -/// Decode the username fields from a `Resources.Consumers` storage value. -pub fn decode_people_identity(value: &[u8]) -> Result { - if value.len() < 65 { - return Err(format!( - "invalid Resources.Consumers record: expected 65-byte identifier key, got {} bytes", - value.len() - )); - } - - let mut input = value; - let decoded = ConsumerUsernamePrefix::decode(&mut input) - .map_err(|err| format!("invalid Resources.Consumers record: {err}"))?; - let lite_username = non_empty_string(decoded.lite_username)?; - let full_username = decoded - .full_username - .map(non_empty_string) - .transpose()? - .flatten(); - Ok(PeopleIdentity { - lite_username, - full_username, - }) -} - -fn non_empty_string(bytes: Vec) -> Result, String> { - if bytes.is_empty() { - return Ok(None); - } - let value = String::from_utf8(bytes) - .map_err(|err| format!("Resources.Consumers username is not UTF-8: {err}"))?; - Ok(Some(value)) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn resources_consumers_key_uses_expected_prefix() { - let key = resources_consumers_storage_key(&[0x42; 32]); - - assert_eq!(key.len(), 80); - assert_eq!(&key[..16], &twox_128(b"Resources")); - assert_eq!(&key[16..32], &twox_128(b"Consumers")); - assert_eq!(&key[48..], &[0x42; 32]); - } - - #[test] - fn twox128_matches_substrate_storage_prefix_vector() { - assert_eq!( - hex::encode(twox_128(b"System")), - "26aa394eea5630e07c48ae0c9558cef7" - ); - } - - #[test] - fn decodes_username_prefix_and_ignores_trailing_fields() { - let mut value = vec![0x04; 65]; - value.extend((Some(b"Alice Smith".to_vec()), b"alice.01".to_vec()).encode()); - value.extend_from_slice(&[0xff; 8]); - - let decoded = decode_people_identity(&value).expect("identity should decode"); - - assert_eq!(decoded.full_username.as_deref(), Some("Alice Smith")); - assert_eq!(decoded.lite_username.as_deref(), Some("alice.01")); - } - - #[test] - fn consumer_username_prefix_matches_runtime_field_codec() { - let prefix = ConsumerUsernamePrefix { - identifier_key: [0x04; 65], - full_username: Some(b"Alice Smith".to_vec()), - lite_username: b"alice.01".to_vec(), - }; - let encoded = prefix.encode(); - let runtime_fields = ( - prefix.identifier_key, - prefix.full_username.as_ref(), - prefix.lite_username.as_slice(), - ) - .encode(); - - assert_eq!(encoded, runtime_fields); - assert_eq!( - ConsumerUsernamePrefix::decode(&mut encoded.as_slice()).unwrap(), - prefix - ); - } - - #[test] - fn empty_full_username_is_none() { - let mut value = vec![0x04; 65]; - value.extend((Some(Vec::::new()), b"alice.01".to_vec()).encode()); - - let decoded = decode_people_identity(&value).expect("identity should decode"); - - assert_eq!(decoded.full_username, None); - assert_eq!(decoded.lite_username.as_deref(), Some("alice.01")); - } - - #[test] - fn rejects_missing_identifier_key() { - let value = (None::>, b"alice.01".to_vec()).encode(); - - let error = decode_people_identity(&value).expect_err("identity should reject"); - - assert!(error.contains("65-byte identifier key")); - } -} diff --git a/rust/crates/truapi-server/src/host_logic/product_account.rs b/rust/crates/truapi-server/src/host_logic/product_account.rs index 94e2da354..d3aa099a7 100644 --- a/rust/crates/truapi-server/src/host_logic/product_account.rs +++ b/rust/crates/truapi-server/src/host_logic/product_account.rs @@ -24,9 +24,9 @@ pub const IDENTITY_PRODUCT_ID: &str = "uid.dot"; pub const PERSONHOOD_PRODUCT_ID: &str = "peopl.dot"; const RING_VRF_ROOT_KEY: &[u8] = b"ring-vrf"; -/// Substrate sr25519 signing-context string, shared by every sr25519 signature -/// the core produces (statement store, product raw signing). -pub(crate) const SR25519_SIGNING_CONTEXT: &[u8] = b"substrate"; +/// Substrate sr25519 signing-context string. Shared by every sr25519 signature +/// the core produces: statement store, product raw signing, dotNS gateway. +pub const SR25519_SIGNING_CONTEXT: &[u8] = b"substrate"; /// Error deriving product accounts or keys. #[derive(Debug, Error, PartialEq, Eq)] diff --git a/rust/crates/truapi-server/src/host_logic/sso/messages.rs b/rust/crates/truapi-server/src/host_logic/sso/messages.rs index e37e9d5cd..df37fac09 100644 --- a/rust/crates/truapi-server/src/host_logic/sso/messages.rs +++ b/rust/crates/truapi-server/src/host_logic/sso/messages.rs @@ -1568,6 +1568,7 @@ mod tests { PlatformInfo::default(), [0; 32], [0xbb; 32], + [0xcc; 32], "polkadotapp".to_string(), ) .expect("test pairing config is valid"); diff --git a/rust/crates/truapi-server/src/host_logic/sso/pairing.rs b/rust/crates/truapi-server/src/host_logic/sso/pairing.rs index 974a490f5..4c9a1b4d8 100644 --- a/rust/crates/truapi-server/src/host_logic/sso/pairing.rs +++ b/rust/crates/truapi-server/src/host_logic/sso/pairing.rs @@ -634,6 +634,7 @@ mod tests { }, [0; 32], [0xbb; 32], + [0xcc; 32], "polkadotapp".to_string(), ) .expect("test runtime config is valid") diff --git a/rust/crates/truapi-server/src/native.rs b/rust/crates/truapi-server/src/native.rs index eab2d7bb1..64e8ba44f 100644 --- a/rust/crates/truapi-server/src/native.rs +++ b/rust/crates/truapi-server/src/native.rs @@ -239,6 +239,8 @@ pub struct NativeRuntimeConfig { pub people_chain_genesis_hash: Vec, /// Bulletin-chain genesis hash. Must be exactly 32 bytes. pub bulletin_chain_genesis_hash: Vec, + /// Asset Hub genesis hash. Must be exactly 32 bytes. + pub asset_hub_chain_genesis_hash: Vec, /// Optional local signing-host secret material (raw BIP-39 entropy). pub local_session_secret: Option>, /// Optional lite username attached to the local signing-host session. @@ -276,6 +278,12 @@ pub enum NativeRuntimeConfigError { /// Supplied byte length. actual: u64, }, + /// Asset Hub genesis hash was not exactly 32 bytes. + #[error("asset_hub_chain_genesis_hash must be exactly 32 bytes, got {actual}")] + InvalidAssetHubChainGenesisHash { + /// Supplied byte length. + actual: u64, + }, /// Host icon URL could not be parsed. #[error("host_icon must be an absolute HTTPS URL: {reason}")] InvalidHostIcon { @@ -324,6 +332,12 @@ impl TryFrom for NativeResolvedRuntimeConfig { actual: config.bulletin_chain_genesis_hash.len() as u64, } })?; + let asset_hub_chain_genesis_hash = + <[u8; 32]>::try_from(config.asset_hub_chain_genesis_hash.as_slice()).map_err(|_| { + NativeRuntimeConfigError::InvalidAssetHubChainGenesisHash { + actual: config.asset_hub_chain_genesis_hash.len() as u64, + } + })?; let product = ProductContext::new(config.product_id).map_err(NativeRuntimeConfigError::from)?; let signing = SigningHostConfig::new( @@ -338,6 +352,7 @@ impl TryFrom for NativeResolvedRuntimeConfig { }, people_chain_genesis_hash, bulletin_chain_genesis_hash, + asset_hub_chain_genesis_hash, )?; Ok(Self { signing, @@ -1264,6 +1279,7 @@ mod tests { platform_version: None, people_chain_genesis_hash: vec![0xa2; 32], bulletin_chain_genesis_hash: vec![0xbb; 32], + asset_hub_chain_genesis_hash: vec![0xcc; 32], local_session_secret: None, local_session_lite_username: None, pairing_deeplink_scheme: NativePairingDeeplinkScheme::PolkadotApp, diff --git a/rust/crates/truapi-server/src/runtime.rs b/rust/crates/truapi-server/src/runtime.rs index 06e08fe2b..5b15a143e 100644 --- a/rust/crates/truapi-server/src/runtime.rs +++ b/rust/crates/truapi-server/src/runtime.rs @@ -349,6 +349,7 @@ impl ProductRuntimeHost { truapi_platform::PlatformInfo::default(), [0; 32], [0xbb; 32], + [0xcc; 32], "polkadotapp".to_string(), ) .expect("compat runtime config is valid") diff --git a/rust/crates/truapi-server/src/runtime/identity.rs b/rust/crates/truapi-server/src/runtime/identity.rs index c783c603e..61edc8d86 100644 --- a/rust/crates/truapi-server/src/runtime/identity.rs +++ b/rust/crates/truapi-server/src/runtime/identity.rs @@ -1,4 +1,13 @@ -//! People-chain identity lookup used to resolve usernames for a paired session. +//! dotNS identity lookup used to resolve usernames for a paired session. +//! +//! Usernames live in the dotNS contracts on Asset Hub. The gateway pallet +//! storage anchors the `DotnsPopController`. The protocol registry locates the +//! `StoreFactory`. The account's labels come from its `LabelStore` on the warm +//! path. On the cold path they come from its pending claim on the controller, +//! covering gateway-minted names before the user settles their store. +//! +//! All reads run over one `chainHead_v1` follow via `ReviveApi_call` dry-runs. +//! No chain metadata is needed. use std::sync::atomic::{AtomicU64, Ordering}; #[cfg(not(target_arch = "wasm32"))] @@ -8,52 +17,49 @@ use web_time::Duration; use crate::chain_runtime::{ ChainHeadStorageValue, ChainHeadStorageValueLookup, ChainRuntime, - wait_for_chain_head_best_hash, wait_for_chain_head_storage_value, + wait_for_chain_head_best_hash, wait_for_chain_head_call_output, + wait_for_chain_head_storage_value, }; -use crate::host_logic::identity::{ - PeopleIdentity, decode_people_identity, resources_consumers_storage_key, +use crate::host_logic::dotns_gateway::{ + DotnsIdentity, DotnsTransport, VIEW_CALL_ORIGIN, classify_labels, decode_revive_call_output, + discover_pop_controller, encode_revive_call, resolve_labels, }; use crate::host_logic::session::SessionInfo; +use futures::stream::BoxStream; use futures::{FutureExt, pin_mut}; use tracing::{debug, instrument, warn}; use truapi::latest::{ - OperationStartedResult, RemoteChainHeadFollowRequest, RemoteChainHeadStorageRequest, - StorageQueryItem, StorageQueryType, + OperationStartedResult, RemoteChainHeadCallRequest, RemoteChainHeadFollowItem, + RemoteChainHeadFollowRequest, RemoteChainHeadStorageRequest, StorageQueryItem, + StorageQueryType, }; -/// Budget for the whole People-chain lookup (best block + storage read). +/// Budget for the whole Asset Hub lookup: best block, storage, contract views. const LOOKUP_TIMEOUT: Duration = Duration::from_secs(10); -const LOOKUP_RETRY_INTERVAL: Duration = Duration::from_secs(2); const BEST_BLOCK_TIMEOUT: Duration = Duration::from_secs(2); -/// Monotonic salt for local identity lookup follow ids, avoiding collisions -/// between concurrent People-chain identity lookups. +/// Monotonic salt for local identity lookup follow ids. It keeps concurrent +/// dotNS identity lookups from colliding. static IDENTITY_LOOKUP_COUNTER: AtomicU64 = AtomicU64::new(1); -enum ConsumerRecordLookup { - Found(Vec), - Missing, - Inaccessible, -} - -/// Fill in missing usernames by querying the people chain; returns the -/// session unchanged when it already carries a username or no people chain -/// is configured. +/// Fills in missing usernames by querying the dotNS contracts on Asset Hub. +/// Returns the session unchanged when it already carries a username. Also +/// returns it unchanged when no Asset Hub is configured. #[instrument(skip_all, fields(runtime.method = "session.identity.resolve_with_chain"))] pub(super) async fn resolve_session_identity_with_chain( chain: &ChainRuntime, - people_chain_genesis_hash: [u8; 32], + asset_hub_chain_genesis_hash: [u8; 32], mut session: SessionInfo, ) -> SessionInfo { - if session.has_username() || people_chain_genesis_hash == [0; 32] { + if session.has_username() || asset_hub_chain_genesis_hash == [0; 32] { return session; } let preferred_account = session.identity_account_id.unwrap_or(session.public_key); if lookup_and_apply( chain, - people_chain_genesis_hash, + asset_hub_chain_genesis_hash, preferred_account, &mut session, "identity", @@ -65,7 +71,7 @@ pub(super) async fn resolve_session_identity_with_chain( let public_key = session.public_key; lookup_and_apply( chain, - people_chain_genesis_hash, + asset_hub_chain_genesis_hash, public_key, &mut session, "root identity", @@ -77,37 +83,37 @@ pub(super) async fn resolve_session_identity_with_chain( } /// Maximum lookup attempts per account on transient failure. The first attempt -/// warms the People-chain connection (cached per genesis), so a retry after a -/// cold-start timeout usually resolves immediately. +/// warms the Asset Hub connection, cached per genesis. A retry after a cold-start +/// timeout therefore usually resolves immediately. const IDENTITY_LOOKUP_MAX_ATTEMPTS: usize = 3; #[derive(Debug, Clone, Copy, PartialEq, Eq)] enum LookupOutcome { /// A username record was found and applied. Applied, - /// The account has no consumer record (definitive; do not retry). + /// The account has no dotNS labels. Definitive, not worth a retry. NoRecord, /// The lookup failed transiently after exhausting retries. Failed, } -/// Look up `account`'s people-chain identity and apply any usernames to -/// `session`, retrying transient failures against the warmed connection. +/// Looks up `account`'s dotNS identity and applies any usernames to `session`. +/// Transient failures are retried against the warmed connection. async fn lookup_and_apply( chain: &ChainRuntime, - people_chain_genesis_hash: [u8; 32], + asset_hub_chain_genesis_hash: [u8; 32], account: [u8; 32], session: &mut SessionInfo, label: &str, ) -> LookupOutcome { for attempt in 1..=IDENTITY_LOOKUP_MAX_ATTEMPTS { - match lookup_people_identity(chain, people_chain_genesis_hash, account).await { + match lookup_dotns_identity(chain, asset_hub_chain_genesis_hash, account).await { Ok(Some(identity)) => { debug!( account = %hex::encode(account), lite_username = identity.lite_username.as_deref().unwrap_or(""), full_username = identity.full_username.as_deref().unwrap_or(""), - "People-chain {label} lookup found username" + "dotNS {label} lookup found username" ); session.apply_usernames(identity.lite_username, identity.full_username); return LookupOutcome::Applied; @@ -115,7 +121,7 @@ async fn lookup_and_apply( Ok(None) => { debug!( account = %hex::encode(account), - "People-chain {label} lookup found no consumer record" + "dotNS {label} lookup found no labels" ); return LookupOutcome::NoRecord; } @@ -124,7 +130,7 @@ async fn lookup_and_apply( account = %hex::encode(account), attempt, %reason, - "People-chain {label} lookup failed" + "dotNS {label} lookup failed" ); } } @@ -132,100 +138,158 @@ async fn lookup_and_apply( LookupOutcome::Failed } +/// Resolves `account_id`'s usernames from the dotNS contracts at a fresh Asset +/// Hub head, under one overall time budget. Returns `None` when the gateway is not +/// deployed. Also returns `None` when the account holds no labels. #[instrument(skip_all, fields(runtime.method = "session.identity.lookup"))] -async fn lookup_people_identity( +async fn lookup_dotns_identity( chain: &ChainRuntime, - people_chain_genesis_hash: [u8; 32], + asset_hub_chain_genesis_hash: [u8; 32], account_id: [u8; 32], -) -> Result, String> { +) -> Result, String> { let timeout = futures_timer::Delay::new(LOOKUP_TIMEOUT).fuse(); pin_mut!(timeout); - loop { - let lookup = fetch_consumer_record(chain, people_chain_genesis_hash, account_id).fuse(); - pin_mut!(lookup); - let lookup = futures::select! { - value = lookup => value?, - () = timeout => return Err("People-chain identity lookup timed out".to_string()), + let lookup = async { + let mut lookup = + DotnsLookup::pinned_to_best_block(chain, asset_hub_chain_genesis_hash, account_id) + .await?; + let Some(controller) = discover_pop_controller(&mut lookup).await? else { + return Ok(None); }; - match lookup { - ConsumerRecordLookup::Found(value) => { - return decode_people_identity(&value).map(Some); - } - ConsumerRecordLookup::Missing => return Ok(None), - ConsumerRecordLookup::Inaccessible => {} - } - - let retry = futures_timer::Delay::new(LOOKUP_RETRY_INTERVAL).fuse(); - pin_mut!(retry); - futures::select! { - () = retry => {}, - () = timeout => return Err("People-chain identity lookup timed out".to_string()), + let labels = resolve_labels(&mut lookup, &controller, &account_id).await?; + if labels.is_empty() { + return Ok(None); } + Ok(Some(classify_labels(labels))) + } + .fuse(); + pin_mut!(lookup); + futures::select! { + value = lookup => value, + () = timeout => Err("dotNS identity lookup timed out".to_string()), } } -/// Read the raw `Resources.Consumers` record for `account_id` at a fresh -/// People-chain head. The key is built locally, so the read never needs the -/// People-chain metadata. -async fn fetch_consumer_record( - chain: &ChainRuntime, - people_chain_genesis_hash: [u8; 32], - account_id: [u8; 32], -) -> Result { - let genesis_hash = people_chain_genesis_hash.to_vec(); - let key = resources_consumers_storage_key(&account_id); - let lookup_id = IDENTITY_LOOKUP_COUNTER.fetch_add(1, Ordering::Relaxed); - let follow_id = format!("truapi:identity:{lookup_id}:{}", hex::encode(account_id)); - let mut follow = chain.remote_chain_head_follow( - follow_id.clone(), - RemoteChainHeadFollowRequest { - genesis_hash: genesis_hash.clone(), - with_runtime: false, - }, - ); - - let hash = wait_for_chain_head_best_hash( - &mut follow, - "People-chain", - LOOKUP_TIMEOUT, - BEST_BLOCK_TIMEOUT, - ) - .await?; - let response = chain - .remote_chain_head_storage(RemoteChainHeadStorageRequest { - genesis_hash: genesis_hash.clone(), - follow_subscription_id: follow_id.clone(), +/// One pinned-block context for the dotNS lookup steps. It owns the +/// `chainHead_v1` follow every read and view runs over. +struct DotnsLookup<'a> { + chain: &'a ChainRuntime, + follow: BoxStream<'static, RemoteChainHeadFollowItem>, + genesis_hash: Vec, + follow_id: String, + hash: Vec, +} + +impl<'a> DotnsLookup<'a> { + /// Opens a follow on Asset Hub and pins it to the current best block. + async fn pinned_to_best_block( + chain: &'a ChainRuntime, + asset_hub_chain_genesis_hash: [u8; 32], + account_id: [u8; 32], + ) -> Result { + let genesis_hash = asset_hub_chain_genesis_hash.to_vec(); + let lookup_id = IDENTITY_LOOKUP_COUNTER.fetch_add(1, Ordering::Relaxed); + let follow_id = format!("truapi:identity:{lookup_id}:{}", hex::encode(account_id)); + let mut follow = chain.remote_chain_head_follow( + follow_id.clone(), + RemoteChainHeadFollowRequest { + genesis_hash: genesis_hash.clone(), + with_runtime: false, + }, + ); + let hash = wait_for_chain_head_best_hash( + &mut follow, + "Asset Hub", + LOOKUP_TIMEOUT, + BEST_BLOCK_TIMEOUT, + ) + .await?; + Ok(Self { + chain, + follow, + genesis_hash, + follow_id, hash, - items: vec![StorageQueryItem { - key: key.clone(), - query_type: StorageQueryType::Value, - }], - child_trie: None, }) - .await - .map_err(|failure| failure.reason())?; - let operation_id = match response.operation { - OperationStartedResult::Started { operation_id } => operation_id, + } +} + +#[truapi_platform::async_trait] +impl DotnsTransport for DotnsLookup<'_> { + /// Reads one storage value at the pinned block. `Ok(None)` when absent. + async fn storage(&mut self, key: Vec) -> Result>, String> { + let response = self + .chain + .remote_chain_head_storage(RemoteChainHeadStorageRequest { + genesis_hash: self.genesis_hash.clone(), + follow_subscription_id: self.follow_id.clone(), + hash: self.hash.clone(), + items: vec![StorageQueryItem { + key: key.clone(), + query_type: StorageQueryType::Value, + }], + child_trie: None, + }) + .await + .map_err(|failure| failure.reason())?; + let operation_id = started_operation_id(response.operation)?; + let value = wait_for_chain_head_storage_value( + &mut self.follow, + ChainHeadStorageValueLookup { + chain: self.chain, + genesis_hash: &self.genesis_hash, + follow_subscription_id: &self.follow_id, + operation_id: &operation_id, + key: &key, + label: "Asset Hub", + timeout: LOOKUP_TIMEOUT, + }, + ) + .await?; + match value { + ChainHeadStorageValue::Found(value) => Ok(Some(value)), + ChainHeadStorageValue::Missing => Ok(None), + ChainHeadStorageValue::Inaccessible => { + Err("Asset Hub storage was inaccessible".to_string()) + } + } + } + + /// Dry-runs a contract view via `ReviveApi_call` at the pinned block and + /// returns its data. + /// + /// Views originate from the synthetic always-mapped account. They work + /// regardless of the queried account's revive mapping. + async fn view(&mut self, dest: &[u8; 20], input: Vec) -> Result, String> { + let response = self + .chain + .remote_chain_head_call(RemoteChainHeadCallRequest { + genesis_hash: self.genesis_hash.clone(), + follow_subscription_id: self.follow_id.clone(), + hash: self.hash.clone(), + function: "ReviveApi_call".to_string(), + call_parameters: encode_revive_call(&VIEW_CALL_ORIGIN, dest, &input), + }) + .await + .map_err(|failure| failure.reason())?; + let operation_id = started_operation_id(response.operation)?; + let output = wait_for_chain_head_call_output( + &mut self.follow, + &operation_id, + "Asset Hub", + LOOKUP_TIMEOUT, + ) + .await?; + decode_revive_call_output(&output).map_err(|err| err.to_string()) + } +} + +/// Unwraps a started operation id. `LimitReached` maps to an error. +fn started_operation_id(operation: OperationStartedResult) -> Result { + match operation { + OperationStartedResult::Started { operation_id } => Ok(operation_id), OperationStartedResult::LimitReached => { - return Err("People-chain storage lookup limit reached".to_string()); + Err("Asset Hub operation limit reached".to_string()) } - }; - let value = wait_for_chain_head_storage_value( - &mut follow, - ChainHeadStorageValueLookup { - chain, - genesis_hash: &genesis_hash, - follow_subscription_id: &follow_id, - operation_id: &operation_id, - key: &key, - label: "People-chain", - timeout: LOOKUP_TIMEOUT, - }, - ) - .await?; - Ok(match value { - ChainHeadStorageValue::Found(value) => ConsumerRecordLookup::Found(value), - ChainHeadStorageValue::Missing => ConsumerRecordLookup::Missing, - ChainHeadStorageValue::Inaccessible => ConsumerRecordLookup::Inaccessible, - }) + } } diff --git a/rust/crates/truapi-server/src/runtime/pairing_host.rs b/rust/crates/truapi-server/src/runtime/pairing_host.rs index 518af8809..b615fe2d3 100644 --- a/rust/crates/truapi-server/src/runtime/pairing_host.rs +++ b/rust/crates/truapi-server/src/runtime/pairing_host.rs @@ -246,7 +246,7 @@ impl PairingHost { Ok(session) => { let resolved = resolve_session_identity_with_chain( &pairing_host.chain, - pairing_host.host_config.people_chain_genesis_hash, + pairing_host.host_config.asset_hub_chain_genesis_hash, session, ) .await; @@ -519,13 +519,13 @@ impl PairingHost { async fn refresh_current_session_identity(&self) -> Option { let current = self.session_state.current()?; - if current.has_username() || self.host_config.people_chain_genesis_hash == [0; 32] { + if current.has_username() || self.host_config.asset_hub_chain_genesis_hash == [0; 32] { return Some(authority_session(¤t)); } let resolved = resolve_session_identity_with_chain( &self.chain, - self.host_config.people_chain_genesis_hash, + self.host_config.asset_hub_chain_genesis_hash, current.clone(), ) .await; diff --git a/rust/crates/truapi-server/src/runtime/signing_host.rs b/rust/crates/truapi-server/src/runtime/signing_host.rs index b1f3a2bd1..83d3895ca 100644 --- a/rust/crates/truapi-server/src/runtime/signing_host.rs +++ b/rust/crates/truapi-server/src/runtime/signing_host.rs @@ -803,6 +803,7 @@ mod tests { PlatformInfo::default(), [0; 32], [0xbb; 32], + [0xcc; 32], ) .expect("signing host config is valid"); let services = RuntimeServices::new( diff --git a/rust/crates/truapi-server/src/runtime/signing_host/sso_responder.rs b/rust/crates/truapi-server/src/runtime/signing_host/sso_responder.rs index f1b822ad8..d16d2ef43 100644 --- a/rust/crates/truapi-server/src/runtime/signing_host/sso_responder.rs +++ b/rust/crates/truapi-server/src/runtime/signing_host/sso_responder.rs @@ -1273,6 +1273,7 @@ mod tests { PlatformInfo::default(), [0; 32], [0xbb; 32], + [0xcc; 32], ) .expect("signing host config is valid"); let services = RuntimeServices::new( diff --git a/rust/crates/truapi-server/src/runtime/statement_allowance.rs b/rust/crates/truapi-server/src/runtime/statement_allowance.rs index ba9f71e8e..81fcb129e 100644 --- a/rust/crates/truapi-server/src/runtime/statement_allowance.rs +++ b/rust/crates/truapi-server/src/runtime/statement_allowance.rs @@ -135,6 +135,7 @@ pub async fn fetch_chain_state(rpc: &RpcClient) -> Result { @@ -347,7 +360,8 @@ pub async fn claim_long_term_storage( let context = slot::derive_long_term_storage_context(period, counter); let call = extrinsic::build_claim_long_term_storage_call(metadata, period, counter, target)?; - let message = extension::build_proof_message(metadata, &call, chain_state)?; + let message = + extension::build_proof_message(metadata, &call, chain_state, extension::AS_RESOURCES)?; let domain = proof::domain_for_ring_exponent(ring.exponent)?; let ring_proof = proof::ring_vrf_proof(domain, entropy, &ring.members, &context, &message)?; let as_resources_extra = extrinsic::build_long_term_storage_extra( @@ -356,8 +370,13 @@ pub async fn claim_long_term_storage( ring.ring_index, revision, )?; - let extrinsic = - extrinsic::build_unsigned_extrinsic(metadata, chain_state, &call, &as_resources_extra)?; + let extrinsic = extrinsic::build_unsigned_extrinsic( + metadata, + chain_state, + &call, + extension::AS_RESOURCES, + &as_resources_extra, + )?; debug!( period, counter, @@ -626,6 +645,7 @@ mod tests { transaction_version: 1, genesis_hash: [0xab; 32], nonce: 0, + restrict_origins: false, }; let entropy = [0x11; 32]; let ring = RingParams { diff --git a/rust/crates/truapi-server/src/runtime/statement_allowance/extension.rs b/rust/crates/truapi-server/src/runtime/statement_allowance/extension.rs index 9deccdc46..bf2f2166a 100644 --- a/rust/crates/truapi-server/src/runtime/statement_allowance/extension.rs +++ b/rust/crates/truapi-server/src/runtime/statement_allowance/extension.rs @@ -27,6 +27,10 @@ use super::StatementAllowanceError; /// Signed-extension identifier that carries the `AsResources` authorization. pub const AS_RESOURCES: &str = "AsResources"; +/// Signed-extension identifier that carries the `AsDotnsGateway` +/// authorization on Asset Hub. +pub const AS_DOTNS_GATEWAY: &str = "AsDotnsGateway"; + /// Error while decoding runtime metadata or resolving allowance-specific /// metadata entries. #[derive(Debug, Error)] @@ -63,9 +67,21 @@ pub enum MetadataError { /// Call name. call: String, }, - /// `AsResources` extension is absent from metadata. - #[error("{AS_RESOURCES} extension not found in metadata")] - MissingAsResourcesExtension, + /// A named transaction extension is absent from metadata. + #[error("{identifier} extension not found in metadata")] + MissingExtension { + /// Extension identifier. + identifier: &'static str, + }, + /// `RegisterFullName` did not have the expected field shape. + #[error( + "AsDotnsGatewayInfo::RegisterFullName fields are [{actual}], expected \ + [proof, ring_index, signature]; the runtime shape drifted" + )] + RegisterFullNameShapeDrift { + /// Actual comma-separated field names. + actual: String, + }, /// `AsResources` extra type did not contain the expected `Option`. #[error("{AS_RESOURCES} extra is not an Option")] AsResourcesExtraNotOption, @@ -139,6 +155,11 @@ pub struct ChainState { pub genesis_hash: [u8; 32], /// Account nonce (CheckNonce extra); ignored by the unsigned path. pub nonce: u32, + /// `RestrictOrigins` extra value. `false` for statement-store allowance + /// calls on People. `true` for restricted-origin dotNS gateway registrations + /// on Asset Hub. It is part of both the signed digest and the extrinsic + /// body, so it lives here to keep the two in lockstep. + pub restrict_origins: bool, } /// A signed extension's identifier plus the type ids of its `extra` and @@ -334,26 +355,22 @@ impl Metadata { Ok([pallet_index, variant.index]) } - /// Resolve `AsResourcesInfo::` and the - /// `MembershipCollection::LitePeople` index it carries, by name, from the - /// `AsResources` extension type. - pub fn as_resources_variant_indices( + /// Resolves the info-enum type carried by extension `identifier`. Its extra + /// is `Option`, with or without a struct wrapper. + fn extension_info_type( &self, - info_variant: &str, - ) -> Result<(u8, u8), StatementAllowanceError> { + identifier: &'static str, + ) -> Result { let ext = self .extensions .iter() - .find(|e| e.identifier == AS_RESOURCES) - .ok_or(MetadataError::MissingAsResourcesExtension)?; - // extra = `AsResources(Option)`, with or without the - // struct wrapper. + .find(|e| e.identifier == identifier) + .ok_or(MetadataError::MissingExtension { identifier })?; let option_type = match &self.resolve_type(ext.extra_type)?.type_def { TypeDef::Composite(_) => self.single_field_type(ext.extra_type)?, _ => ext.extra_type, }; - let info_type = self - .resolve_variant(option_type)? + self.resolve_variant(option_type)? .variants .iter() .find(|v| v.name == "Some") @@ -361,7 +378,46 @@ impl Metadata { [field] => Some(field.ty.id), _ => None, }) - .ok_or(MetadataError::AsResourcesExtraNotOption)?; + .ok_or(MetadataError::AsResourcesExtraNotOption.into()) + } + + /// Resolves `AsDotnsGatewayInfo::RegisterFullName` to its variant index. + /// + /// Asserts the exact `{proof, ring_index, signature}` field shape. A runtime + /// that grows the variant, say with a `revision` field, then fails loudly + /// instead of mis-encoding. + pub fn dotns_register_full_name_variant(&self) -> Result { + let info_type = self.extension_info_type(AS_DOTNS_GATEWAY)?; + let variant = self + .resolve_variant(info_type)? + .variants + .iter() + .find(|v| v.name == "RegisterFullName") + .ok_or_else(|| MetadataError::MissingAsResourcesInfoVariant { + variant: "RegisterFullName".to_string(), + })?; + let fields: Vec<&str> = variant + .fields + .iter() + .map(|field| field.name.as_deref().unwrap_or("")) + .collect(); + if fields != ["proof", "ring_index", "signature"] { + return Err(MetadataError::RegisterFullNameShapeDrift { + actual: fields.join(", "), + } + .into()); + } + Ok(variant.index) + } + + /// Resolves `AsResourcesInfo::` and the + /// `MembershipCollection::LitePeople` index it carries. Both are looked up by + /// name from the `AsResources` extension type. + pub fn as_resources_variant_indices( + &self, + info_variant: &str, + ) -> Result<(u8, u8), StatementAllowanceError> { + let info_type = self.extension_info_type(AS_RESOURCES)?; let variant = self .resolve_variant(info_type)? .variants @@ -466,8 +522,8 @@ impl Metadata { "VerifyMultiSignature" => (vec![0x00], Vec::new()), // extra = { tip: compact(0), asset_id: None } = 0x00 0x00. "ChargeAssetTxPayment" => (vec![0x00, 0x00], Vec::new()), - // extra = bool false = 0x00. - "RestrictOrigins" => (vec![0x00], Vec::new()), + // extra = bool. See `ChainState::restrict_origins`. + "RestrictOrigins" => (vec![state.restrict_origins as u8], Vec::new()), _ => ( self.encode_default(ext.extra_type), self.encode_default(ext.additional_signed_type), @@ -531,28 +587,33 @@ impl Metadata { } } - /// Index of `AsResources` in the extension list, if present. - pub fn as_resources_index(&self) -> Option { + /// Index of the named extension in the extension list, if present. + pub fn extension_index(&self, identifier: &str) -> Option { self.extensions .iter() - .position(|e| e.identifier == AS_RESOURCES) + .position(|e| e.identifier == identifier) } } -/// Build the ring-VRF proof message for an `AsResources`-authorized call: -/// `blake2b256(0x00 ‖ call ‖ Σ tail.extra ‖ Σ tail.additional_signed)`, where -/// the tail is the extensions ordered strictly after `AsResources`. The leading -/// `0x00` is the General-transaction extension-version byte. +/// Builds the inherited-implication digest for a call authorized by the `pivot` +/// extension. +/// +/// The digest is `blake2b256(0x00 ‖ call ‖ Σ tail.extra ‖ Σ +/// tail.additional_signed)`. The tail is the extensions ordered strictly after +/// `pivot`. The leading `0x00` is the General-transaction extension-version byte. +/// This digest is the ring-VRF message for `AsResources` calls and the +/// sr25519-signed message for `AsDotnsGateway` registrations. pub fn build_proof_message( metadata: &Metadata, call_data: &[u8], state: &ChainState, + pivot: &'static str, ) -> Result<[u8; 32], StatementAllowanceError> { let all = metadata.encode_signed_extensions(state); let tail_start = metadata - .as_resources_index() + .extension_index(pivot) .map(|i| i + 1) - .ok_or(MetadataError::MissingAsResourcesExtension)?; + .ok_or(MetadataError::MissingExtension { identifier: pivot })?; let tail = &all[tail_start..]; let mut payload = Vec::with_capacity(1 + call_data.len()); @@ -591,6 +652,7 @@ mod tests { transaction_version: 1, genesis_hash: [0xab; 32], nonce: 0, + restrict_origins: false, } } @@ -606,7 +668,8 @@ mod tests { #[test] fn proof_message_matches_frozen_known_answer() { let metadata = Metadata::decode(FIXTURE).unwrap(); - let msg = build_proof_message(&metadata, &fixture_call(), &fixture_state()).unwrap(); + let msg = build_proof_message(&metadata, &fixture_call(), &fixture_state(), AS_RESOURCES) + .unwrap(); assert_eq!( hex::encode(msg), "1d2e6d8d8f421b0857097c6076115507432d66fea47ebe0c3be282a369f6743c", @@ -616,7 +679,7 @@ mod tests { #[test] fn as_resources_tail_is_indices_10_through_20() { let metadata = Metadata::decode(FIXTURE).unwrap(); - let idx = metadata.as_resources_index().unwrap(); + let idx = metadata.extension_index(AS_RESOURCES).unwrap(); // AsResources sits at index 9; the proof tail is everything after it. assert_eq!(idx, 9); let ids = metadata.extension_ids(); @@ -677,13 +740,66 @@ mod tests { ); } + /// Asset Hub fixture metadata captured from paseo-next-v2. That is the + /// runtime the dotNS gateway flows were validated against. + const AH_FIXTURE: &[u8] = + include_bytes!("../../../tests/fixtures/paseo-next-v2-asset-hub-metadata.scale"); + + #[test] + fn dotns_gateway_pipeline_matches_the_reference_transaction_layout() { + let metadata = Metadata::decode(AH_FIXTURE).unwrap(); + let state = ChainState { + restrict_origins: true, + ..fixture_state() + }; + + // RegisterFullName resolves with the asserted 3-field shape. + assert_eq!(metadata.dotns_register_full_name_variant().unwrap(), 0); + assert!( + metadata + .call_indices("DotnsGateway", "register_name") + .is_ok() + ); + + // The extension tail after AsDotnsGateway encodes exactly the bytes the + // reference implementation registerTx.ts hand-builds. Extras are + // RestrictOrigin(true) ‖ CheckEra(Immortal) ‖ CheckNonce(0) ‖ + // ChargePGAS(tip 0, asset None) ‖ CheckMetadataHash(Disabled). The + // implicit half is specVersion ‖ txVersion ‖ genesis ‖ genesis ‖ + // metadata-hash None. + let all = metadata.encode_signed_extensions(&state); + let tail_start = metadata.extension_index(AS_DOTNS_GATEWAY).unwrap() + 1; + let tail = &all[tail_start..]; + let extras: Vec = tail.iter().flat_map(|e| e.extra.clone()).collect(); + assert_eq!( + hex::encode(extras), + "010000000000", + "tail extras: RestrictOrigin true, era, nonce, tip, asset, metadata hash" + ); + let implicit: Vec = tail + .iter() + .flat_map(|e| e.additional_signed.clone()) + .collect(); + assert_eq!( + implicit, + [ + state.spec_version.to_le_bytes().to_vec(), + state.transaction_version.to_le_bytes().to_vec(), + state.genesis_hash.to_vec(), + state.genesis_hash.to_vec(), + vec![0x00], + ] + .concat() + ); + } + #[test] fn dropping_the_version_byte_changes_the_hash() { let metadata = Metadata::decode(FIXTURE).unwrap(); let state = fixture_state(); let call = fixture_call(); let all = metadata.encode_signed_extensions(&state); - let tail = &all[metadata.as_resources_index().unwrap() + 1..]; + let tail = &all[metadata.extension_index(AS_RESOURCES).unwrap() + 1..]; let mut without = call.clone(); for e in tail { without.extend_from_slice(&e.extra); @@ -692,7 +808,7 @@ mod tests { without.extend_from_slice(&e.additional_signed); } assert_ne!( - build_proof_message(&metadata, &call, &state).unwrap(), + build_proof_message(&metadata, &call, &state, AS_RESOURCES).unwrap(), blake2b256(&without), ); } diff --git a/rust/crates/truapi-server/src/runtime/statement_allowance/extrinsic.rs b/rust/crates/truapi-server/src/runtime/statement_allowance/extrinsic.rs index c6e03dd39..bf692b270 100644 --- a/rust/crates/truapi-server/src/runtime/statement_allowance/extrinsic.rs +++ b/rust/crates/truapi-server/src/runtime/statement_allowance/extrinsic.rs @@ -133,23 +133,28 @@ pub fn build_long_term_storage_extra( Ok(extra) } -/// Assemble the unsigned General (v5) extrinsic: -/// `compact(len) ‖ 0x45 ‖ 0x00 ‖ Σ(all extra, AsResources = Some(info)) ‖ call`. +/// Assembles the unsigned General (v5) extrinsic. +/// +/// The layout is +/// `compact(len) ‖ 0x45 ‖ 0x00 ‖ Σ(all extra, pivot = Some(info)) ‖ call`. +/// `pivot` names the extension whose `extra` is replaced by `pivot_extra`: +/// `AsResources` on People, `AsDotnsGateway` on Asset Hub. pub fn build_unsigned_extrinsic( metadata: &Metadata, state: &ChainState, call_data: &[u8], - as_resources_extra: &[u8], + pivot: &'static str, + pivot_extra: &[u8], ) -> Result, StatementAllowanceError> { let all = metadata.encode_signed_extensions(state); - let as_resources_index = metadata - .as_resources_index() - .ok_or(MetadataError::MissingAsResourcesExtension)?; + let pivot_index = metadata + .extension_index(pivot) + .ok_or(MetadataError::MissingExtension { identifier: pivot })?; let mut body = vec![GENERAL_V5_PREAMBLE, EXTENSION_VERSION]; for (i, ext) in all.iter().enumerate() { - if i == as_resources_index { - body.extend_from_slice(as_resources_extra); + if i == pivot_index { + body.extend_from_slice(pivot_extra); } else { body.extend_from_slice(&ext.extra); } @@ -172,6 +177,7 @@ mod tests { transaction_version: 1, genesis_hash: [0xab; 32], nonce: 0, + restrict_origins: false, } } @@ -285,7 +291,14 @@ mod tests { let metadata = Metadata::decode(FIXTURE).unwrap(); let call = build_set_statement_store_account_call(&metadata, 7, 0, &[0u8; 32]).unwrap(); let extra = build_as_resources_extra(&metadata, &[0xEE; 785], 0).unwrap(); - let xt = build_unsigned_extrinsic(&metadata, &fixture_state(), &call, &extra).unwrap(); + let xt = build_unsigned_extrinsic( + &metadata, + &fixture_state(), + &call, + crate::runtime::statement_allowance::extension::AS_RESOURCES, + &extra, + ) + .unwrap(); // Strip the compact length prefix and check the body head + tail. let body = &xt[compact_prefix_len(&xt)..]; diff --git a/rust/crates/truapi-server/src/runtime/statement_allowance/ring.rs b/rust/crates/truapi-server/src/runtime/statement_allowance/ring.rs index aabd53fa5..b128c2a33 100644 --- a/rust/crates/truapi-server/src/runtime/statement_allowance/ring.rs +++ b/rust/crates/truapi-server/src/runtime/statement_allowance/ring.rs @@ -43,10 +43,24 @@ pub enum RingError { /// Ring status included field failed to decode. #[error("ring status: {0}")] RingStatus(#[source] parity_scale_codec::Error), + /// Member has no `Members.Members` record for the collection. + #[error("member has no Members.Members record for the collection")] + MemberRecordMissing, + /// Member is not included in a ring yet. + #[error("member is not included in a ring (status: {status})")] + MemberNotIncluded { + /// Onboarding or suspended. + status: &'static str, + }, + /// Subscriber ring exponent was absent for the collection. + #[error("MembersSubscriber.RingCollectionExponents missing for the collection")] + SubscriberExponentMissing, } /// LitePeople collection identifier: ASCII, exactly 32 bytes. -const LITE_PEOPLE_IDENTIFIER: &[u8; 32] = b"pop:polkadot.network/people-lite"; +pub const LITE_PEOPLE_IDENTIFIER: &[u8; 32] = b"pop:polkadot.network/people-lite"; +/// Full-person People collection identifier. ASCII, space-padded to 32 bytes. +pub const PEOPLE_IDENTIFIER: &[u8; 32] = b"pop:polkadot.network/people "; /// Ring member public key length. const MEMBER_LEN: usize = 32; @@ -113,36 +127,59 @@ fn collections_key() -> Vec { .concat() } -/// `Members.RingKeysStatus[(id, ring_index)]` storage key. -fn ring_keys_status_key(ring_index: u32) -> Vec { +/// `Members.Root[(id, ring_index)]` storage key. +fn ring_root_key(ring_index: u32) -> Vec { [ twox_128(b"Members").as_slice(), - twox_128(b"RingKeysStatus").as_slice(), + twox_128(b"Root").as_slice(), LITE_PEOPLE_IDENTIFIER.as_slice(), &blake2_128_concat(&ring_index.to_le_bytes()), ] .concat() } -/// `Members.Root[(id, ring_index)]` storage key. -fn ring_root_key(ring_index: u32) -> Vec { +/// `Members.RingKeys[(identifier, ring_index, page)]` storage key. +fn collection_ring_keys_key(identifier: &[u8; 32], ring_index: u32, page: u32) -> Vec { [ twox_128(b"Members").as_slice(), - twox_128(b"Root").as_slice(), - LITE_PEOPLE_IDENTIFIER.as_slice(), + twox_128(b"RingKeys").as_slice(), + identifier.as_slice(), &blake2_128_concat(&ring_index.to_le_bytes()), + &twox_64_concat(&page.to_le_bytes()), ] .concat() } -/// `Members.RingKeys[(id, ring_index, page)]` storage key. -fn ring_keys_key(ring_index: u32, page: u32) -> Vec { +/// `Members.RingKeysStatus[(identifier, ring_index)]` storage key. +fn collection_ring_keys_status_key(identifier: &[u8; 32], ring_index: u32) -> Vec { [ twox_128(b"Members").as_slice(), - twox_128(b"RingKeys").as_slice(), - LITE_PEOPLE_IDENTIFIER.as_slice(), + twox_128(b"RingKeysStatus").as_slice(), + identifier.as_slice(), &blake2_128_concat(&ring_index.to_le_bytes()), - &twox_64_concat(&page.to_le_bytes()), + ] + .concat() +} + +/// `Members.Members[(identifier, member)]` storage key. +/// The hashers are `Identity` then `Blake2_128Concat`. +fn member_record_key(identifier: &[u8; 32], member: &[u8; 32]) -> Vec { + [ + twox_128(b"Members").as_slice(), + twox_128(b"Members").as_slice(), + identifier.as_slice(), + &blake2_128_concat(member), + ] + .concat() +} + +/// `MembersSubscriber.RingCollectionExponents[identifier]` storage key. +/// It lives on the subscriber chain, Asset Hub. +fn subscriber_exponent_key(identifier: &[u8; 32]) -> Vec { + [ + twox_128(b"MembersSubscriber").as_slice(), + twox_128(b"RingCollectionExponents").as_slice(), + &blake2_128_concat(identifier), ] .concat() } @@ -209,11 +246,12 @@ pub async fn read_ring_exponent( }) } -/// Read the members of `ring_index`, sliced to the baked-in `included` -/// prefix, with every read pinned to block `at` so pages and status come from -/// one consistent snapshot. -pub async fn read_ring_members_at( +/// Reads the members of collection `identifier`'s `ring_index`, sliced to the +/// baked-in `included` prefix. Every read is pinned to block `at`, so pages and +/// status come from one consistent snapshot. +pub async fn read_collection_ring_members_at( rpc: &RpcClient, + identifier: &[u8; 32], ring_index: u32, at: &str, ) -> Result, StatementAllowanceError> { @@ -221,7 +259,7 @@ pub async fn read_ring_members_at( let mut members = Vec::new(); for page in 0.. { let Some(bytes) = rpc - .get_storage_at(&ring_keys_key(ring_index, page), at) + .get_storage_at(&collection_ring_keys_key(identifier, ring_index, page), at) .await? else { break; @@ -244,7 +282,7 @@ pub async fn read_ring_members_at( // 2. Slice to the baked-in `included` prefix (absent status => all included). if let Some(status) = rpc - .get_storage_at(&ring_keys_status_key(ring_index), at) + .get_storage_at(&collection_ring_keys_status_key(identifier, ring_index), at) .await? { // RingStatus = { total: u32 LE, included: u32 LE, .. }. @@ -256,6 +294,98 @@ pub async fn read_ring_members_at( Ok(members) } +/// Ring coordinates of one member. Projected from the runtime's `RingPosition` +/// enum. +#[derive(Debug, PartialEq, Eq, DecodeAsType)] +enum MemberRingPosition { + /// Waiting in the onboarding queue. + Onboarding { + #[allow(dead_code)] + queue_page: u32, + #[allow(dead_code)] + queued_at: u64, + }, + /// Included in a built ring. + Included { + ring_index: u32, + #[allow(dead_code)] + ring_page: u32, + #[allow(dead_code)] + ring_position: u32, + }, + /// Suspended from all rings. + Suspended, +} + +/// Reads the ring index `member` is included in for collection `identifier`, +/// from `Members.Members`, pinned to block `at`. Errors when the member has no +/// record. Errors too when the member is not `Included` yet. +pub async fn read_member_ring_index( + rpc: &RpcClient, + metadata: &Metadata, + identifier: &[u8; 32], + member: &[u8; 32], + at: &str, +) -> Result { + let value = rpc + .get_storage_at(&member_record_key(identifier, member), at) + .await? + .ok_or(RingError::MemberRecordMissing)?; + let value_type = metadata.storage_value_type("Members", "Members").ok_or( + MetadataError::MissingStorageType { + pallet: "Members", + entry: "Members", + }, + )?; + let mut input = value.as_slice(); + let position = MemberRingPosition::decode_as_type(&mut input, value_type, metadata.registry()) + .map_err(|err| RingError::DecodeAsType { + context: "Members.Members", + source: err, + })?; + match position { + MemberRingPosition::Included { ring_index, .. } => Ok(ring_index), + MemberRingPosition::Onboarding { .. } => Err(RingError::MemberNotIncluded { + status: "onboarding", + } + .into()), + MemberRingPosition::Suspended => Err(RingError::MemberNotIncluded { + status: "suspended", + } + .into()), + } +} + +/// Reads the ring exponent the subscriber chain, Asset Hub, verifies collection +/// `identifier` against. The source is +/// `MembersSubscriber.RingCollectionExponents` at the current best block. +pub async fn read_subscriber_ring_exponent( + rpc: &RpcClient, + metadata: &Metadata, + identifier: &[u8; 32], +) -> Result { + let value = rpc + .get_storage(&subscriber_exponent_key(identifier)) + .await? + .ok_or(RingError::SubscriberExponentMissing)?; + let value_type = metadata + .storage_value_type("MembersSubscriber", "RingCollectionExponents") + .ok_or(MetadataError::MissingStorageType { + pallet: "MembersSubscriber", + entry: "RingCollectionExponents", + })?; + let mut input = value.as_slice(); + RingExponent::decode_as_type(&mut input, value_type, metadata.registry()) + .map(RingExponent::exponent) + .map_err(|err| { + RingError::DecodeAsType { + context: "MembersSubscriber.RingCollectionExponents", + source: err, + } + .into() + }) +} + /// Read `Members.Root[LitePeople][ring_index].revision` pinned to block `at` /// (absent => 0). pub async fn read_ring_revision( @@ -374,13 +504,19 @@ mod tests { let scripted = ScriptedRpc::new([page.as_str(), "null", status]); let rpc = RpcClient::new(HostRpcClient::new(scripted.clone())); - let members = futures::executor::block_on(read_ring_members_at(&rpc, 3, "0xat")).unwrap(); + let members = futures::executor::block_on(read_collection_ring_members_at( + &rpc, + LITE_PEOPLE_IDENTIFIER, + 3, + "0xat", + )) + .unwrap(); assert_eq!(members, vec![[0xaa; 32]]); let expected: Vec<(String, String)> = [ - ring_keys_key(3, 0), - ring_keys_key(3, 1), - ring_keys_status_key(3), + collection_ring_keys_key(LITE_PEOPLE_IDENTIFIER, 3, 0), + collection_ring_keys_key(LITE_PEOPLE_IDENTIFIER, 3, 1), + collection_ring_keys_status_key(LITE_PEOPLE_IDENTIFIER, 3), ] .into_iter() .map(|key| { diff --git a/rust/crates/truapi-server/src/test_support.rs b/rust/crates/truapi-server/src/test_support.rs index a9331624d..366d6c982 100644 --- a/rust/crates/truapi-server/src/test_support.rs +++ b/rust/crates/truapi-server/src/test_support.rs @@ -194,6 +194,7 @@ pub(crate) fn runtime_config(product_id: &str) -> (PairingHostConfig, ProductCon PlatformInfo::default(), [0; 32], [0xbb; 32], + [0xcc; 32], "polkadotapp".to_string(), ) .expect("test host runtime config is valid"), diff --git a/rust/crates/truapi-server/src/wasm.rs b/rust/crates/truapi-server/src/wasm.rs index 74ea5998b..af9b24000 100644 --- a/rust/crates/truapi-server/src/wasm.rs +++ b/rust/crates/truapi-server/src/wasm.rs @@ -441,6 +441,7 @@ fn pairing_host_config_from_js(value: &JsValue) -> Result Result &str { "pairing_deeplink_scheme" => "pairing.deeplinkScheme", "people_chain_genesis_hash" => "people.genesisHash", "bulletin_chain_genesis_hash" => "bulletin.genesisHash", + "asset_hub_chain_genesis_hash" => "assetHub.genesisHash", other => other, } } diff --git a/rust/crates/truapi-server/src/ws_bridge.rs b/rust/crates/truapi-server/src/ws_bridge.rs index 00a1b240f..884984515 100644 --- a/rust/crates/truapi-server/src/ws_bridge.rs +++ b/rust/crates/truapi-server/src/ws_bridge.rs @@ -521,6 +521,7 @@ mod tests { PlatformInfo::default(), [0; 32], [0xbb; 32], + [0xcc; 32], ) .expect("test signing host config is valid"); let runtime = Arc::new(SigningHostRuntime::new(platform, config, test_spawner())); diff --git a/rust/crates/truapi-server/tests/common/mod.rs b/rust/crates/truapi-server/tests/common/mod.rs index be868eaf2..c59bf50e1 100644 --- a/rust/crates/truapi-server/tests/common/mod.rs +++ b/rust/crates/truapi-server/tests/common/mod.rs @@ -58,6 +58,7 @@ pub fn test_runtime_config() -> (PairingHostConfig, ProductContext) { PlatformInfo::default(), [0xa2; 32], [0xbb; 32], + [0xcc; 32], "polkadotapp".to_string(), ) .expect("test host runtime config is valid"), diff --git a/rust/crates/truapi-server/tests/fixtures/paseo-next-v2-asset-hub-metadata.scale b/rust/crates/truapi-server/tests/fixtures/paseo-next-v2-asset-hub-metadata.scale new file mode 100644 index 0000000000000000000000000000000000000000..d6163151353bebc4c7af2b3d5af408673d781661 GIT binary patch literal 652465 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

dvqbCg6_u%6_G0&qPDAo^S(F-H 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≠ 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�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 literal 0 HcmV?d00001 diff --git a/rust/crates/truapi-server/tests/wasm_crypto_vectors.rs b/rust/crates/truapi-server/tests/wasm_crypto_vectors.rs index 1fe502bf8..1e736fa44 100644 --- a/rust/crates/truapi-server/tests/wasm_crypto_vectors.rs +++ b/rust/crates/truapi-server/tests/wasm_crypto_vectors.rs @@ -51,6 +51,7 @@ fn runtime_config() -> PairingHostConfig { }, [0xa2; 32], [0xbb; 32], + [0xcc; 32], "polkadotapp".to_string(), ) .expect("test runtime config is valid") diff --git a/rust/crates/truapi/src/lib.rs b/rust/crates/truapi/src/lib.rs index a138df037..f782afd89 100644 --- a/rust/crates/truapi/src/lib.rs +++ b/rust/crates/truapi/src/lib.rs @@ -107,6 +107,8 @@ pub mod latest { pub type PreimageSubmitError = LatestOf; /// Transaction creation payload for a product account. pub type ProductAccountTxPayload = LatestOf; + /// Chain-head runtime-API call request. + pub type RemoteChainHeadCallRequest = LatestOf; /// Chain-head subscription item. pub type RemoteChainHeadFollowItem = LatestOf; /// Chain-head subscription request.