From 29a3800a3d3461efcc884c316952c9f951ae0484 Mon Sep 17 00:00:00 2001 From: Zebedeusz Date: Thu, 6 Aug 2026 11:34:06 +0200 Subject: [PATCH 01/19] Person's usernames - read from AH dotNS instead of PC --- Cargo.lock | 1 + android/truapi-host/README.md | 1 + .../kotlin/io/parity/truapi/TrUAPIHost.kt | 8 +- ios/truapi-host/README.md | 1 + .../Sources/TrUAPIHost/TrUAPIHost.swift | 12 +- .../Sources/TrUAPIHost/truapi_server.swift | 56 +- .../Tests/TrUAPIWsBridgeTests.swift | 3 +- 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 | 4 +- rust/crates/truapi-host-cli/SPEC.md | 32 +- rust/crates/truapi-host-cli/src/accounts.rs | 5 +- .../crates/truapi-host-cli/src/attestation.rs | 341 +++++-- 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 | 159 +++- .../truapi-host-cli/src/register_name.rs | 189 ++++ 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/extrinsic.rs | 1 + .../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 | 21 + rust/crates/truapi-server/src/runtime.rs | 1 + .../truapi-server/src/runtime/identity.rs | 294 +++--- .../truapi-server/src/runtime/pairing_host.rs | 8 +- .../truapi-server/src/runtime/signing_host.rs | 1 + .../src/runtime/signing_host/sso_responder.rs | 1 + .../truapi-server/src/runtime/sso_pairing.rs | 2 +- .../src/runtime/statement_allowance.rs | 15 +- .../runtime/statement_allowance/extension.rs | 97 +- .../runtime/statement_allowance/extrinsic.rs | 1 + .../runtime/statement_allowance/renewal.rs | 1 + .../src/runtime/statement_allowance/ring.rs | 182 +++- rust/crates/truapi-server/src/test_support.rs | 1 + rust/crates/truapi-server/src/wasm.rs | 13 + 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 + 49 files changed, 2422 insertions(+), 479 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 259915e51..44bd190d8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4980,6 +4980,7 @@ dependencies = [ "anyhow", "arboard", "async-trait", + "base64", "bip39", "clap", "crossterm", diff --git a/android/truapi-host/README.md b/android/truapi-host/README.md index 39d23e8b1..2ea1581ef 100644 --- a/android/truapi-host/README.md +++ b/android/truapi-host/README.md @@ -233,6 +233,7 @@ val runtimeConfig = RuntimeConfig( hostIcon = "https://host.example/icon.png", peopleChainGenesisHash = ByteArray(32), bulletinChainGenesisHash = ByteArray(32), + assetHubChainGenesisHash = ByteArray(32), // Optional: activate a local signing session from host-held BIP-39 entropy // (no SSO pairing). Omit for the QR pairing flow. localSessionSecret = null, diff --git a/android/truapi-host/src/main/kotlin/io/parity/truapi/TrUAPIHost.kt b/android/truapi-host/src/main/kotlin/io/parity/truapi/TrUAPIHost.kt index 8b9716a84..bc1f81111 100644 --- a/android/truapi-host/src/main/kotlin/io/parity/truapi/TrUAPIHost.kt +++ b/android/truapi-host/src/main/kotlin/io/parity/truapi/TrUAPIHost.kt @@ -88,8 +88,8 @@ enum class ProductExecutionKind { * * [hostName], [hostIcon], [hostVersion], [platformType], and [platformVersion] * describe the host to the wallet during SSO pairing. - * [peopleChainGenesisHash] and [bulletinChainGenesisHash] must each be exactly - * 32 bytes. [localSessionSecret] optionally activates a local signing session + * [peopleChainGenesisHash], [bulletinChainGenesisHash] and + * [assetHubChainGenesisHash] must each be exactly 32 bytes. [localSessionSecret] optionally activates a local signing session * from host-held BIP-39 entropy (no SSO pairing needed). */ data class RuntimeConfig( @@ -102,6 +102,7 @@ data class RuntimeConfig( val platformVersion: String? = null, val peopleChainGenesisHash: ByteArray, val bulletinChainGenesisHash: ByteArray, + val assetHubChainGenesisHash: ByteArray, val localSessionSecret: ByteArray? = null, val localSessionLiteUsername: String? = null, val pairingDeeplinkScheme: PairingDeeplinkScheme = PairingDeeplinkScheme.POLKADOT_APP, @@ -117,6 +118,7 @@ data class RuntimeConfig( platformVersion = platformVersion, peopleChainGenesisHash = peopleChainGenesisHash, bulletinChainGenesisHash = bulletinChainGenesisHash, + assetHubChainGenesisHash = assetHubChainGenesisHash, localSessionSecret = localSessionSecret, localSessionLiteUsername = localSessionLiteUsername, pairingDeeplinkScheme = pairingDeeplinkScheme.toNative(), @@ -134,6 +136,7 @@ data class RuntimeConfig( platformVersion == other.platformVersion && peopleChainGenesisHash.contentEquals(other.peopleChainGenesisHash) && bulletinChainGenesisHash.contentEquals(other.bulletinChainGenesisHash) && + assetHubChainGenesisHash.contentEquals(other.assetHubChainGenesisHash) && // Compare nullability explicitly so null (no session) is distinct // from an empty secret (invalid input) — matching hashCode, which // hashes null to 0 and an empty array to 1. @@ -152,6 +155,7 @@ data class RuntimeConfig( result = 31 * result + (platformVersion?.hashCode() ?: 0) result = 31 * result + peopleChainGenesisHash.contentHashCode() result = 31 * result + bulletinChainGenesisHash.contentHashCode() + result = 31 * result + assetHubChainGenesisHash.contentHashCode() result = 31 * result + (localSessionSecret?.contentHashCode() ?: 0) result = 31 * result + (localSessionLiteUsername?.hashCode() ?: 0) result = 31 * result + pairingDeeplinkScheme.hashCode() diff --git a/ios/truapi-host/README.md b/ios/truapi-host/README.md index 8067bb51e..1fbae81ed 100644 --- a/ios/truapi-host/README.md +++ b/ios/truapi-host/README.md @@ -232,6 +232,7 @@ let runtimeConfig = RuntimeConfig( hostIcon: "https://host.example/icon.png", peopleChainGenesisHash: Data(repeating: 0, count: 32), bulletinChainGenesisHash: Data(repeating: 0, count: 32), + assetHubChainGenesisHash: Data(repeating: 0, count: 32), pairingDeeplinkScheme: .polkadotApp ) let core = try TrUAPIHostCore(callbacks: callbacks, runtimeConfig: runtimeConfig) diff --git a/ios/truapi-host/Sources/TrUAPIHost/TrUAPIHost.swift b/ios/truapi-host/Sources/TrUAPIHost/TrUAPIHost.swift index 8398b48df..87c495bee 100644 --- a/ios/truapi-host/Sources/TrUAPIHost/TrUAPIHost.swift +++ b/ios/truapi-host/Sources/TrUAPIHost/TrUAPIHost.swift @@ -44,8 +44,8 @@ public enum PairingDeeplinkScheme: Sendable { /// /// `hostName`, `hostIcon`, `hostVersion`, `platformType`, and /// `platformVersion` describe the host to the wallet during SSO pairing. -/// `peopleChainGenesisHash` and `bulletinChainGenesisHash` must each be -/// exactly 32 bytes. +/// `peopleChainGenesisHash`, `bulletinChainGenesisHash` and +/// `assetHubChainGenesisHash` must each be exactly 32 bytes. public struct RuntimeConfig: Sendable { public let productId: String public let executionKind: ProductExecutionKind @@ -56,6 +56,7 @@ public struct RuntimeConfig: Sendable { public let platformVersion: String? public let peopleChainGenesisHash: Data public let bulletinChainGenesisHash: Data + public let assetHubChainGenesisHash: Data public let localSessionSecret: Data? public let localSessionLiteUsername: String? public let pairingDeeplinkScheme: PairingDeeplinkScheme @@ -70,6 +71,7 @@ public struct RuntimeConfig: Sendable { platformVersion: String? = nil, peopleChainGenesisHash: Data, bulletinChainGenesisHash: Data, + assetHubChainGenesisHash: Data, localSessionSecret: Data? = nil, localSessionLiteUsername: String? = nil, pairingDeeplinkScheme: PairingDeeplinkScheme = .polkadotApp @@ -83,6 +85,7 @@ public struct RuntimeConfig: Sendable { self.platformVersion = platformVersion self.peopleChainGenesisHash = peopleChainGenesisHash self.bulletinChainGenesisHash = bulletinChainGenesisHash + self.assetHubChainGenesisHash = assetHubChainGenesisHash self.localSessionSecret = localSessionSecret self.localSessionLiteUsername = localSessionLiteUsername self.pairingDeeplinkScheme = pairingDeeplinkScheme @@ -99,6 +102,7 @@ public struct RuntimeConfig: Sendable { platformVersion: platformVersion, peopleChainGenesisHash: peopleChainGenesisHash, bulletinChainGenesisHash: bulletinChainGenesisHash, + assetHubChainGenesisHash: assetHubChainGenesisHash, localSessionSecret: localSessionSecret, localSessionLiteUsername: localSessionLiteUsername, pairingDeeplinkScheme: pairingDeeplinkScheme.native @@ -115,6 +119,7 @@ public struct HostRuntimeConfig: Sendable, Equatable { public let platformVersion: String? public let peopleChainGenesisHash: Data public let bulletinChainGenesisHash: Data + public let assetHubChainGenesisHash: Data public let localSessionSecret: Data? public let localSessionLiteUsername: String? @@ -126,6 +131,7 @@ public struct HostRuntimeConfig: Sendable, Equatable { platformVersion: String? = nil, peopleChainGenesisHash: Data, bulletinChainGenesisHash: Data, + assetHubChainGenesisHash: Data, localSessionSecret: Data? = nil, localSessionLiteUsername: String? = nil ) { @@ -136,6 +142,7 @@ public struct HostRuntimeConfig: Sendable, Equatable { self.platformVersion = platformVersion self.peopleChainGenesisHash = peopleChainGenesisHash self.bulletinChainGenesisHash = bulletinChainGenesisHash + self.assetHubChainGenesisHash = assetHubChainGenesisHash self.localSessionSecret = localSessionSecret self.localSessionLiteUsername = localSessionLiteUsername } @@ -149,6 +156,7 @@ public struct HostRuntimeConfig: Sendable, Equatable { platformVersion: platformVersion, peopleChainGenesisHash: peopleChainGenesisHash, bulletinChainGenesisHash: bulletinChainGenesisHash, + assetHubChainGenesisHash: assetHubChainGenesisHash, localSessionSecret: localSessionSecret, localSessionLiteUsername: localSessionLiteUsername ) diff --git a/ios/truapi-host/Sources/TrUAPIHost/truapi_server.swift b/ios/truapi-host/Sources/TrUAPIHost/truapi_server.swift index a48d3721f..931b6e148 100644 --- a/ios/truapi-host/Sources/TrUAPIHost/truapi_server.swift +++ b/ios/truapi-host/Sources/TrUAPIHost/truapi_server.swift @@ -3706,6 +3706,10 @@ public struct NativeHostRuntimeConfig: Equatable, Hashable { * Bulletin-chain genesis hash. Must be exactly 32 bytes. */ public var bulletinChainGenesisHash: Data + /** + * Asset Hub genesis hash. Must be exactly 32 bytes. + */ + public var assetHubChainGenesisHash: Data /** * Optional local signing-host secret material (raw BIP-39 entropy). */ @@ -3739,6 +3743,9 @@ public struct NativeHostRuntimeConfig: Equatable, Hashable { /** * Bulletin-chain genesis hash. Must be exactly 32 bytes. */bulletinChainGenesisHash: Data, + /** + * Asset Hub genesis hash. Must be exactly 32 bytes. + */assetHubChainGenesisHash: Data, /** * Optional local signing-host secret material (raw BIP-39 entropy). */localSessionSecret: Data?, @@ -3752,6 +3759,7 @@ public struct NativeHostRuntimeConfig: Equatable, Hashable { self.platformVersion = platformVersion self.peopleChainGenesisHash = peopleChainGenesisHash self.bulletinChainGenesisHash = bulletinChainGenesisHash + self.assetHubChainGenesisHash = assetHubChainGenesisHash self.localSessionSecret = localSessionSecret self.localSessionLiteUsername = localSessionLiteUsername } @@ -3779,6 +3787,7 @@ public struct FfiConverterTypeNativeHostRuntimeConfig: FfiConverterRustBuffer { platformVersion: FfiConverterOptionString.read(from: &buf), peopleChainGenesisHash: FfiConverterData.read(from: &buf), bulletinChainGenesisHash: FfiConverterData.read(from: &buf), + assetHubChainGenesisHash: FfiConverterData.read(from: &buf), localSessionSecret: FfiConverterOptionData.read(from: &buf), localSessionLiteUsername: FfiConverterOptionString.read(from: &buf) ) @@ -3792,6 +3801,7 @@ public struct FfiConverterTypeNativeHostRuntimeConfig: FfiConverterRustBuffer { FfiConverterOptionString.write(value.platformVersion, into: &buf) FfiConverterData.write(value.peopleChainGenesisHash, into: &buf) FfiConverterData.write(value.bulletinChainGenesisHash, into: &buf) + FfiConverterData.write(value.assetHubChainGenesisHash, into: &buf) FfiConverterOptionData.write(value.localSessionSecret, into: &buf) FfiConverterOptionString.write(value.localSessionLiteUsername, into: &buf) } @@ -3922,6 +3932,10 @@ public struct NativeRuntimeConfig: Equatable, Hashable { * Bulletin-chain genesis hash. Must be exactly 32 bytes. */ public var bulletinChainGenesisHash: Data + /** + * Asset Hub genesis hash. Must be exactly 32 bytes. + */ + public var assetHubChainGenesisHash: Data /** * Optional local signing-host secret material (raw BIP-39 entropy). */ @@ -3965,6 +3979,9 @@ public struct NativeRuntimeConfig: Equatable, Hashable { /** * Bulletin-chain genesis hash. Must be exactly 32 bytes. */bulletinChainGenesisHash: Data, + /** + * Asset Hub genesis hash. Must be exactly 32 bytes. + */assetHubChainGenesisHash: Data, /** * Optional local signing-host secret material (raw BIP-39 entropy). */localSessionSecret: Data?, @@ -3983,6 +4000,7 @@ public struct NativeRuntimeConfig: Equatable, Hashable { self.platformVersion = platformVersion self.peopleChainGenesisHash = peopleChainGenesisHash self.bulletinChainGenesisHash = bulletinChainGenesisHash + self.assetHubChainGenesisHash = assetHubChainGenesisHash self.localSessionSecret = localSessionSecret self.localSessionLiteUsername = localSessionLiteUsername self.pairingDeeplinkScheme = pairingDeeplinkScheme @@ -4013,6 +4031,7 @@ public struct FfiConverterTypeNativeRuntimeConfig: FfiConverterRustBuffer { platformVersion: FfiConverterOptionString.read(from: &buf), peopleChainGenesisHash: FfiConverterData.read(from: &buf), bulletinChainGenesisHash: FfiConverterData.read(from: &buf), + assetHubChainGenesisHash: FfiConverterData.read(from: &buf), localSessionSecret: FfiConverterOptionData.read(from: &buf), localSessionLiteUsername: FfiConverterOptionString.read(from: &buf), pairingDeeplinkScheme: FfiConverterTypeNativePairingDeeplinkScheme.read(from: &buf) @@ -4029,6 +4048,7 @@ public struct FfiConverterTypeNativeRuntimeConfig: FfiConverterRustBuffer { FfiConverterOptionString.write(value.platformVersion, into: &buf) FfiConverterData.write(value.peopleChainGenesisHash, into: &buf) FfiConverterData.write(value.bulletinChainGenesisHash, into: &buf) + FfiConverterData.write(value.assetHubChainGenesisHash, into: &buf) FfiConverterOptionData.write(value.localSessionSecret, into: &buf) FfiConverterOptionString.write(value.localSessionLiteUsername, into: &buf) FfiConverterTypeNativePairingDeeplinkScheme.write(value.pairingDeeplinkScheme, into: &buf) @@ -4766,6 +4786,14 @@ enum NativeRuntimeConfigError: Swift.Error, Equatable, Hashable, Foundation.Loca * Supplied byte length. */actual: UInt64 ) + /** + * Asset Hub genesis hash was not exactly 32 bytes. + */ + case InvalidAssetHubChainGenesisHash( + /** + * Supplied byte length. + */actual: UInt64 + ) /** * Host icon URL could not be parsed. */ @@ -4844,19 +4872,22 @@ public struct FfiConverterTypeNativeRuntimeConfigError: FfiConverterRustBuffer { case 3: return .InvalidBulletinChainGenesisHash( actual: try FfiConverterUInt64.read(from: &buf) ) - case 4: return .InvalidHostIcon( + case 4: return .InvalidAssetHubChainGenesisHash( + actual: try FfiConverterUInt64.read(from: &buf) + ) + case 5: return .InvalidHostIcon( reason: try FfiConverterString.read(from: &buf) ) - case 5: return .InsecureHostIcon( + case 6: return .InsecureHostIcon( scheme: try FfiConverterString.read(from: &buf) ) - case 6: return .InvalidDeeplinkScheme( + case 7: return .InvalidDeeplinkScheme( scheme: try FfiConverterString.read(from: &buf) ) - case 7: return .InvalidProductId( + case 8: return .InvalidProductId( productId: try FfiConverterString.read(from: &buf) ) - case 8: return .LocalSessionActivation( + case 9: return .LocalSessionActivation( reason: try FfiConverterString.read(from: &buf) ) @@ -4886,28 +4917,33 @@ public struct FfiConverterTypeNativeRuntimeConfigError: FfiConverterRustBuffer { FfiConverterUInt64.write(actual, into: &buf) - case let .InvalidHostIcon(reason): + case let .InvalidAssetHubChainGenesisHash(actual): writeInt(&buf, Int32(4)) + FfiConverterUInt64.write(actual, into: &buf) + + + case let .InvalidHostIcon(reason): + writeInt(&buf, Int32(5)) FfiConverterString.write(reason, into: &buf) case let .InsecureHostIcon(scheme): - writeInt(&buf, Int32(5)) + writeInt(&buf, Int32(6)) FfiConverterString.write(scheme, into: &buf) case let .InvalidDeeplinkScheme(scheme): - writeInt(&buf, Int32(6)) + writeInt(&buf, Int32(7)) FfiConverterString.write(scheme, into: &buf) case let .InvalidProductId(productId): - writeInt(&buf, Int32(7)) + writeInt(&buf, Int32(8)) FfiConverterString.write(productId, into: &buf) case let .LocalSessionActivation(reason): - writeInt(&buf, Int32(8)) + writeInt(&buf, Int32(9)) FfiConverterString.write(reason, into: &buf) } diff --git a/ios/truapi-host/Tests/TrUAPIWsBridgeTests.swift b/ios/truapi-host/Tests/TrUAPIWsBridgeTests.swift index e0e438d9b..8232a4466 100644 --- a/ios/truapi-host/Tests/TrUAPIWsBridgeTests.swift +++ b/ios/truapi-host/Tests/TrUAPIWsBridgeTests.swift @@ -36,7 +36,8 @@ private extension TrUAPIWsBridgeTests { productId: "test.dot", hostName: "truapi-host-tests", peopleChainGenesisHash: Data(repeating: 0, count: 32), - bulletinChainGenesisHash: Data(repeating: 0, count: 32) + bulletinChainGenesisHash: Data(repeating: 0, count: 32), + assetHubChainGenesisHash: Data(repeating: 0, count: 32) ) } diff --git a/js/packages/truapi-host/src/runtime.ts b/js/packages/truapi-host/src/runtime.ts index e1cba8673..ec093c05f 100644 --- a/js/packages/truapi-host/src/runtime.ts +++ b/js/packages/truapi-host/src/runtime.ts @@ -87,6 +87,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 4c3903a28..268d48e86 100644 --- a/js/packages/truapi-host/src/web/worker-provider.test.ts +++ b/js/packages/truapi-host/src/web/worker-provider.test.ts @@ -95,6 +95,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 c07962f5e..8c5fe62d4 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 9a5585ebb..8933d8152 100644 --- a/rust/crates/truapi-host-cli/README.md +++ b/rust/crates/truapi-host-cli/README.md @@ -422,8 +422,8 @@ cleans up a unique temporary Unix socket. - **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 +- **`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 (ring onboarding). `truapi-host identity-check --mnemonic ` probes which derivation carries a username. diff --git a/rust/crates/truapi-host-cli/SPEC.md b/rust/crates/truapi-host-cli/SPEC.md index 41f0a652e..a86072d2e 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. | | `pgas-check` | Inspect or submit an Asset Hub PGAS allowance claim. | @@ -231,7 +232,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 @@ -269,7 +270,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. | @@ -812,14 +813,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 @@ -1114,7 +1128,8 @@ There are no public endpoint override flags. Every role the preset serves — People, Bulletin and Asset Hub — is always routed, because host internals require all three: statement-store traffic addressed to the -People genesis, preimage submission, and PGAS claims respectively. The SSO sentinel is +People genesis, preimage submission, and PGAS claims plus dotNS username reads +respectively. The SSO sentinel is a separate case — it is an unmapped genesis and reaches People through the fallback below, not through People's own route. `E2E_LIVE_CHAIN=1` only widens routing to endpoints the preset carries without serving them as a role, of which `paseo-next-v2` has none. @@ -1512,6 +1527,9 @@ 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. | 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/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 e99e94c55..a033e8af8 100644 --- a/rust/crates/truapi-host-cli/src/main.rs +++ b/rust/crates/truapi-host-cli/src/main.rs @@ -14,9 +14,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; @@ -153,7 +155,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")] @@ -162,6 +165,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. @@ -321,7 +346,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, @@ -718,13 +771,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")?; @@ -1020,12 +1074,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); @@ -1114,6 +1168,7 @@ fn build_signing_runtime( platform_info(), network.people_genesis, network.bulletin_genesis, + network.asset_hub_genesis, ) .context("invalid signing host config")?; let runtime = Arc::new(SigningHostRuntime::new(platform, config, tokio_spawner())); diff --git a/rust/crates/truapi-host-cli/src/network.rs b/rust/crates/truapi-host-cli/src/network.rs index 45e192143..c0da9a655 100644 --- a/rust/crates/truapi-host-cli/src/network.rs +++ b/rust/crates/truapi-host-cli/src/network.rs @@ -13,55 +13,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", - asset_hub_ws: "wss://paseo-asset-hub-next-rpc.polkadot.io", - people_genesis: hex_literal_genesis( - "89a63b11fef2c0273fc72c0d864da0793a665dade5db153e0cab995348c5440f", - ), - bulletin_genesis: hex_literal_genesis( - "8cfe6717dc4becfda2e13c488a1e2061ff2dfee96e7d031157f72d36716c0a22", - ), - asset_hub_genesis: hex_literal_genesis( - "23e730eb1c6fecae09c917439a5038cb6122d0d48980e8b9bbf0ff56f94a2ca6", - ), + 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( - "23e730eb1c6fecae09c917439a5038cb6122d0d48980e8b9bbf0ff56f94a2ca6", - ), - ws: "wss://paseo-asset-hub-next-rpc.polkadot.io", - required_for_host: true, - }, - ChainEndpoint { - genesis: hex_literal_genesis( - "89a63b11fef2c0273fc72c0d864da0793a665dade5db153e0cab995348c5440f", - ), - 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( + "23e730eb1c6fecae09c917439a5038cb6122d0d48980e8b9bbf0ff56f94a2ca6", + ), + ws: "wss://paseo-asset-hub-next-rpc.polkadot.io", + required_for_host: true, +}; + +const PASEO_PEOPLE: ChainEndpoint = ChainEndpoint { + genesis: hex_literal_genesis( + "89a63b11fef2c0273fc72c0d864da0793a665dade5db153e0cab995348c5440f", + ), + 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)] @@ -314,17 +377,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, @@ -470,4 +537,22 @@ mod tests { skipped one and this test stopped covering it" ); } + + #[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..e09ab5c1d --- /dev/null +++ b/rust/crates/truapi-host-cli/src/register_name.rs @@ -0,0 +1,189 @@ +//! 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_after_extension( + &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_with_extra( + &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 3b953dce1..6feea3086 100644 --- a/rust/crates/truapi-platform/src/lib.rs +++ b/rust/crates/truapi-platform/src/lib.rs @@ -65,6 +65,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 @@ -85,6 +87,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. @@ -170,6 +174,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)?; @@ -182,6 +187,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) @@ -196,11 +202,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 948c2b721..397343d03 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 dda599a03..a18e25399 100644 --- a/rust/crates/truapi-server/src/chain_runtime.rs +++ b/rust/crates/truapi-server/src/chain_runtime.rs @@ -1703,6 +1703,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/extrinsic.rs b/rust/crates/truapi-server/src/host_logic/extrinsic.rs index 54cdb0a8a..56ef79ece 100644 --- a/rust/crates/truapi-server/src/host_logic/extrinsic.rs +++ b/rust/crates/truapi-server/src/host_logic/extrinsic.rs @@ -822,6 +822,7 @@ pub(crate) mod tests { transaction_version: 1, genesis_hash: [0xab; 32], nonce: 0, + restrict_origins: false, } } 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 67c9d58cc..077be58c1 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 42312bf8a..7b350775f 100644 --- a/rust/crates/truapi-server/src/host_logic/sso/messages.rs +++ b/rust/crates/truapi-server/src/host_logic/sso/messages.rs @@ -1961,6 +1961,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 41fd9865b..c1a3cbd10 100644 --- a/rust/crates/truapi-server/src/native.rs +++ b/rust/crates/truapi-server/src/native.rs @@ -139,6 +139,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. @@ -164,6 +166,8 @@ pub struct NativeHostRuntimeConfig { 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. @@ -213,6 +217,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 { @@ -259,6 +269,7 @@ impl TryFrom for NativeResolvedRuntimeConfig { platform_version, people_chain_genesis_hash, bulletin_chain_genesis_hash, + asset_hub_chain_genesis_hash, local_session_secret, local_session_lite_username, pairing_deeplink_scheme: _, @@ -271,6 +282,7 @@ impl TryFrom for NativeResolvedRuntimeConfig { platform_version, people_chain_genesis_hash, bulletin_chain_genesis_hash, + asset_hub_chain_genesis_hash, local_session_secret, local_session_lite_username, } @@ -300,6 +312,12 @@ impl TryFrom for NativeResolvedHostRuntimeConfig { 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 signing = SigningHostConfig::new( HostInfo { name: config.host_name, @@ -312,6 +330,7 @@ impl TryFrom for NativeResolvedHostRuntimeConfig { }, people_chain_genesis_hash, bulletin_chain_genesis_hash, + asset_hub_chain_genesis_hash, )?; Ok(Self { signing, @@ -2104,6 +2123,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, @@ -2119,6 +2139,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: Some(vec![7; 32]), local_session_lite_username: Some("alice".to_string()), } diff --git a/rust/crates/truapi-server/src/runtime.rs b/rust/crates/truapi-server/src/runtime.rs index 80fcda211..276779929 100644 --- a/rust/crates/truapi-server/src/runtime.rs +++ b/rust/crates/truapi-server/src/runtime.rs @@ -377,6 +377,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 c49563d22..5ad427ba4 100644 --- a/rust/crates/truapi-server/src/runtime/pairing_host.rs +++ b/rust/crates/truapi-server/src/runtime/pairing_host.rs @@ -454,7 +454,7 @@ impl PairingHost { let activation_epoch = self.advance_session_lifecycle(); let resolved = resolve_session_identity_with_chain( &self.chain, - self.host_config.people_chain_genesis_hash, + self.host_config.asset_hub_chain_genesis_hash, session, ) .await; @@ -523,7 +523,7 @@ impl PairingHost { }; let resolved = resolve_session_identity_with_chain( &self.chain, - self.host_config.people_chain_genesis_hash, + self.host_config.asset_hub_chain_genesis_hash, session, ) .await; @@ -1113,13 +1113,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 50048ccb3..c761c8e3c 100644 --- a/rust/crates/truapi-server/src/runtime/signing_host.rs +++ b/rust/crates/truapi-server/src/runtime/signing_host.rs @@ -1269,6 +1269,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 1aa5cef97..63f39be17 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 @@ -1574,6 +1574,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/sso_pairing.rs b/rust/crates/truapi-server/src/runtime/sso_pairing.rs index ad08608d5..6aa7d71aa 100644 --- a/rust/crates/truapi-server/src/runtime/sso_pairing.rs +++ b/rust/crates/truapi-server/src/runtime/sso_pairing.rs @@ -240,7 +240,7 @@ impl<'a> SsoPairingFlow<'a> { }; let resolve_session = resolve_session_identity_with_chain( &self.host.chain, - self.host.host_config.people_chain_genesis_hash, + self.host.host_config.asset_hub_chain_genesis_hash, session, ) .fuse(); diff --git a/rust/crates/truapi-server/src/runtime/statement_allowance.rs b/rust/crates/truapi-server/src/runtime/statement_allowance.rs index b3429be8d..103bb05b7 100644 --- a/rust/crates/truapi-server/src/runtime/statement_allowance.rs +++ b/rust/crates/truapi-server/src/runtime/statement_allowance.rs @@ -209,6 +209,7 @@ pub async fn fetch_chain_state(rpc: &RpcClient) -> Result Result { + let variant = self.extension_info_variant(AS_DOTNS_GATEWAY, "RegisterFullName")?; + 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) + } + /// Resolve one named authorization variant on an extension shaped as /// `Wrapper(Option)`, with or without the struct wrapper. fn extension_info_variant( @@ -662,8 +701,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), @@ -799,6 +838,7 @@ mod tests { transaction_version: 1, genesis_hash: [0xab; 32], nonce: 0, + restrict_origins: false, } } @@ -1116,6 +1156,59 @@ 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(); 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 4ae8e6162..428eaabef 100644 --- a/rust/crates/truapi-server/src/runtime/statement_allowance/extrinsic.rs +++ b/rust/crates/truapi-server/src/runtime/statement_allowance/extrinsic.rs @@ -260,6 +260,7 @@ mod tests { transaction_version: 1, genesis_hash: [0xab; 32], nonce: 0, + restrict_origins: false, } } diff --git a/rust/crates/truapi-server/src/runtime/statement_allowance/renewal.rs b/rust/crates/truapi-server/src/runtime/statement_allowance/renewal.rs index a3314a3f2..772b2ef8d 100644 --- a/rust/crates/truapi-server/src/runtime/statement_allowance/renewal.rs +++ b/rust/crates/truapi-server/src/runtime/statement_allowance/renewal.rs @@ -307,6 +307,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/ring.rs b/rust/crates/truapi-server/src/runtime/statement_allowance/ring.rs index 31521d397..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. -pub(super) 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 e8ac1cca8..73f18d57a 100644 --- a/rust/crates/truapi-server/src/test_support.rs +++ b/rust/crates/truapi-server/src/test_support.rs @@ -202,6 +202,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 95da42029..042b9c113 100644 --- a/rust/crates/truapi-server/src/wasm.rs +++ b/rust/crates/truapi-server/src/wasm.rs @@ -445,6 +445,7 @@ fn pairing_host_config_from_js(value: &JsValue) -> Result Result 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 78c30a95c..a1c30b6d2 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 zcmeFa4`^groiBb)s(L2vW_C0CGW#;`#oaq=X043rGToD&&BV;SPIuBKKRey&PBPQ8 zGfmY^)lE`%{j>gbclM3o!Uzfq3N9$DprD|zf`WpAf`WpAg2D(ZC@8F;pr9~<@>6)9 z&-a{j>)yIm`P1Fe8Eu|#Ctdg6bH3+$zW=}9^F5WI?0G8$q)%v-$2PhC@~@X4vEv>LNdPEB1? zX{FLbV*tHSYXqZ(#%{e;n{;p$A3uf1v60lEQYjB`4dMT(V<|PvH`Ity_YJF2r3Qx7 z7~a6Y@!T7%SA$!voZoB)t=vv~GgtSULCv9Q@`^#%Ilo+1`oBikcAKrBl2+sTGrAd+ zw`OMnmtQaCc1p!!P|cO9g>t(XGy!Y1*=n>4EjQBaZ2Xnk*rgLUph)K z!kHofq||f>-EwuSmR4WX-#SVlVzWg6Nh@!)5tJ(ac92$I)?YYM805r-LwZcwN zY?p&ZT7CU!HzJ%V0>FJLL)27)R?ujs)pzw*juZpoR1pyFSF0ku&X#M1>no-0hTkfI z_tNT7t$%uy(Da%$0@tAO&iZA)S_lBkiauJ}a5F{bf1QHRZMI6)?X(KC{@hWb!cG*K z{jb&Nxw2oXVC@xsw6fC76Pf#O)WW$MM~Q>Plo5y?Qe$gS zvjWGzAMsw;i6XQAof^MdsMIgFgEpkp$Mw;QiEye21b?q4S8L_#ezDfVdX~^!BY$CwYRmKe#Hk6S88nt&b0b;>S$>*F=Ygz ze^gT|piO8nOCX;~>2Dn6=0sS*PuohUMUMtP!dnk9(y2hvBYP25b8`6txKYNK`wYkxSc zKYf&2A=5-=eoT$Qy(ozqeKdWvc()TpW`C0!ou_92JvNa(T0LgwiOhWp6jv?1qfGrWo#hI5w4ywhqL9#Bx)t~$hjCVjNVeQdf2B&T8A)@o%3 z##hout1mipMPPVB&B^@dYSkMsvBiS;TKZ@mzpe=*(EPLV)*FG}Y&R$bk5~%1nIiMg zsWU`GNldzDYR0(zep-M3DCx6%#t1x5sqrhpR?vWs^3&?)>7%u*!l@z2+qLIVYH}3F{O_kt!8pFMdqJZ)8}?d<)V9(R>zJVt-Od$7lGs~ zB3RTFSNs<2?BmCdmKwsTA`qNYOQO5wSqC8x9@9TMN)AcR8G&g*4K4Tx6FqcHfBYzc zz$B4b7uC{Q3;VF%C2J`j-7>j=*;n@gc(rY4xT1j@HMq6Gdi!PA#kil}(tJYwgWup;5x0 z(4zMrZA>CDWdtH$P0yE0e$ys-ASWm8KU$q0n=S&$rW#(|_5r}j`;XQO;w+JQ3u^RA zaHDhshQ#UnkJj%r^F-z@s;PxqtJ-`k^yf_m{{Kpi4^3?`(UNOQ26D8HOkvO(X2LL? zURQc@v=LPN#`Uzee?*e5YG`N0zaDtaS|#vm)f19M^vwHN^`3Kn)!Pib;8svTII`$% z?s@^7&0Q~F6ZGe^Gm;w({5q1O+pzQXi2OIBhuTj}E1mxBTF~-ZJAqeiQ;vAGEl*zX zN>%y!*!xw7BAgzb_p9ts!>0&r`aMQENYmaYb zUvQu0ZkAd*2&Lwb3oBd)c-^C{rgr;5JZum(0VyJT2tJdU+SK{ZTricF(xLHM+$@BTb3FL|CNOQ(!Jt%nA zxqoDmYic5M!Hl9FfoE4~PU>l<&)*j8KI50$!P6?8-r;IwJLL_6YCpN-6$0UuicbkIs|7S^V+CIXgsAkhZ)_w8F+q!gI4e<5ROs&LZOXB zA*aFK6xv7@aZdxhTiV{ie0J})+Es})WETv#2&Lt~9k5kAnT&pL3Qs6vlNAXNU7p{9 z7_UP^*1V=I*ANc1jGR^Zc`%-laUfbO9E@1I85FbW2QS6w8 zLGPS}3M@p5Yjzk*Z7txT((9$_CIXAa94d{No@~muFX{`+=69P%Y|7he{9ROuq+&>R z4gzesNj=V%Rr6ZpLldS#8CFh!&AL&$4l)9p%a+zqtRcvioy&U5R$|osDo_jg!Qr>S z66Mmr%4*(I2YNA%p>+|TH$ix`0PNUV_1$%RJdE; zO7NOgb-~saNUob0L!+b5njO`?WT6ASHfq>r(R8TZ5*C3R3bOeotj$~#>;X}6({F?r zPwO#@`%yi;A&)H5QQCKRNONIdl3MD}SjnPiFS1d?dakqcLNZJGg;nNGAV7<-pe!XC zB7p`Q!qQbW(+aN$l8|DdGjvd0eR(M?-dyLueO?3^%9Uw!#?|_@>{Mi8lg_jX)Uad+UCy_htW(6v0TT?Mi zqrNo5cXCz8YCLA@{>sdOFN4yp*5 z6-J4mpfPH8XmIqR_$qp4c!^dPidREKiv2akpJ!(;wOjReOX*RAzr42JDV-5=u~_SR zdoGm7&E7AJ7E6$5t)d=XkUx~lqdav+6?;N%UJhS{CNI~vn>$+YVFp-+iUC@%kb$x) z?=ecI^S;s}nbk&c#gw=Xs=7wuR271t*={aY;dlqjMHSt@`_y>&!U0}S!(9&i@&VpF zrbaG7jv?$6r507Rpe=TM#*I3Jc~E4HZyiOvsOv=$Z!0}&Sc{}^OAiiTtW^U%8D4{Y zORHN7@=Dzrx*+-_D8ko-_7cOsIq8185fm~^nZPIml?`H`q4XM6;HVxY*{uV+FW!!{ z>aDK^^+K_Bvl8QbOsDwE@-`$Qjk$tr;DCr1Vgwxjm}qx)7|? zd%!?1srTXken5R-@I#@!2^)n5N}_d`$hv!4(Bg6OhH6iuqXo4PUYw9--`mZ?>45 zqxu5P>cZ^ojR60L)W;JE>J!-O535h%|Nf`?Pg%kT)MtmzSPPeDB_QZ4^+d!iJp|J> zt%p#w_IW)HZ+!*ArrJ{K!g{wU%0aSf!`PoH95ZL; zBCcl~RDp)pFgz@`Jg)=MHX>7q3h-qUf&pXRDAf>gfmCI5kQS$3VbWh5VrHWlHQaeV z5A6W=38*MlOO=|J%wNl@uXD14S;099?;Im-Vy=fD70P~7qG`Dm_Vz?E?9tltX8|&VZEzVL8!UP8ASD`^I0}$T zI30s=cYN6C?i>7CYd27pMwicjNoEmfOcYf@3N@-UiK;ZSYWfXt@F?WWL?`tL_??-P z6L~L#kbn&_l=zrLB;Ez^y+!9VJOKf+%6sB0_~m9zJO&wJVgsX{31nDJ@zDL$*$ z8uU;Qi~?hk&FKIbrs#x+vUBZf*NTU!-6JteJ^I6Q<-o^k5rM0Qz;~7|A!knDB2CsE z$g1B16Zvvb1pyKR5}m@T@h&8pb;#i>`(T8^4ny@m!yz^NA7PrT(qt{dlV?Cs{M?=H zC2)BdL_)$0!Z83Y%Y{@4vR}jigur}{zqS#ymXL4<3BofnOZJRiUxWg>@i8zwudkRb zj1mAqXxGa%xFv`k8ib1{8U`lo{w@U$lM>x~-iv4~AV{l#di*j4!LHE(4v`SyCjB$X zusFA-88mJHotVKA#FN@*D6%2pt{oo_>wXD4Ai*}qPKl@jEYL;`K`Rsba3tm-3uAvP zHAF@SgH5 zi!L^4145haGNOFQcr}6(5HiiJ3Kw3UI^=Q`^ zHxUZ+n(YEqOp~VdE=WaSyG!!)t1x)x)Yn5p$3-R^3Hg=@qn5*Z_$K&p1|B(Mo5svO zkMoLfj`R$|qni+eqVr3IZ|eWF;SM+Q?5wc|<`N%2jn9F<5<;M@-66dc`NICFkkbPO zJ^kX@+O4^{bq@q?jE$#fXRVib4llr-OV{VCwdyYHlO~Ynh63BQ{O0xSXn{wv$_PRB zU&%;`=UJ^$s*!#{h7{jm2sawo6@z&hrUHr*;&oF_OwvLMC9p7yF%BTSAkjJ_AO`p_ zG7L<*LgYf-g2j*^3$RN3mrN#LI2Mx$#ijw#$cPACBP@)oBFiYC z@hg~yR&E-3$A5+ruUxBLZ`ZLL#*yv($oJYXbxuYZ(SxPpp!U`=*hLJB&FGK3R|y#m zq_3{ISbG#1*(pN+W@YK_m8H_R!p$}qbf_>}9zl)21$B-S(emF{HA|$^pW1^ZJXruJ zrpTaRcGWG|U??X~lN9h8SoX2>2YQk~*Mi4kwB{N?s{zR~On&*czC}v6=hS;hE9AlX zJ}RDv^k91i!9gSRdJ@5XGtWuwfe%H7IjA{1878aLI4ctJV0s^MelWSPF%3Jn8E`|| zFz){m8$j|#K;Ts-8X);<2p2h*4blM$P{7X%Ojrj+7^8q? zj6E(&Cn&;afyWBSAe44Wu(hgzF;g&W2P`vzSsJZSct{XnNXEY%K_VJ8Hlr>_gpDmz zysVAfg7=6l?4)P%r_6UWf^FpD;d)|PiRn#Guf#b@UNVf#|D(hjelaF3tTg)+QW@z^N2O)igS zv@8S$0L&pXZhZeG+lf^mFpcC}#}Zx9u^}MpW8Xo^z|fM9#X|Ev<_jCld9{(E!fxhG zwiIj5ldMEL6vHv|LJ>ms*nO0>oY3NIi;jKlF^EVQ_^8DIWH1HAI0csop8C5oJ3&;2vk_T`_>+=t?#YucfoTXPq=XbK#9kBdkte_34gfZ-x~)D!|$ z$|BSZKnfm$dz^tH;sU82vef=6X)yh%D=ct=4C36qWQ_N{NME8%#8Z((1MH3AR}qoP$c}9^fcuW)5!7y%B)b8 z#+rn7@UmWoYiIq3Rrf{{nWNU4EhjJ{Rd5#$VPd(-fF|DR6taX_{rO_0RJ~+MDTN`3 zP+L_9NDweXRvOVANZa?o2173mslf!$F4~CTM9s@HE}FkfSbll$wl@n&l;|OMTBCC~ z;=lA~i@TtJQo*3NiFP=MB|+)rT;l~eeG#u zddeYC)<*6=GVOp;|24d4o1dBZU}w5l5$SRQ<8RU&9au$(AQ)6X)X{${^<#a(I=&G< z7k(-Fl=!#Im#DL^l%yX-b5Q-a?)iWNP1=D_YY7ioO}nEFcIKab=_|u48KqscHR)dBh zVNkuI`#eeva!k!|{)WF`%diI3Z*|vCO8vfroF}HAR`)6O!#msJ_$C6Xs2rL}$RSit zA*cM`cQ$9AjaTYtcQ$S}St#|3I~y=YFG~ID&PMA+NlLwPXM^@7FQtBaXCo(Q57ZYW zj!c3$GC~|$A8Oul=aMvH(q>SI#EM0f3^|56rj6X_ev;rPrKZ78OvgPd#m$52xYqHX zlzKqx87pkeTa-8I*zK;MC(ICmnEEzx(hV>{j&8z(Il$gIfCD89%f+ZvLCQ zaca~ol<9hyOItZ+u|984G@zCVRY_9*WPef*9S-v8m^zB@=p6RrQgyjnY8em))x%nM z|ESdKbyzVL-x?|t`#N%PZj495F~T4B90RC%_)gZ81enuB_XI@MU5fL7*^@`^!o-nP zDm42FEx}6I11w<)4xfQPQotD+*n=n&ZPpqH^qW5>bf~t^$&*W4h-w4VBiHmit8o!T z;7NqgmN%dA-!i5N(Zg&noFPQ;Ql#F5q?3=Vqh@ zMj$gT@Fm2LKv2lktRq~36fu-+wXo||q!|Os#xJ@Y@-dB22BBcAg@$HgUdETiO0B@l zN6bKcU`{N~KR!93pU;P9rq_@-Za0@qwpYx%2JF-PHE#!jW3Ew-G$6i^-!P|O@PWKx zk2$>1!8nR+Szv4sH-y72(J1>=iNM&0Pj)CD4J2dxA!T=*05v^7*2ScybzagD5MQIu z_1rjQ>je(h34;K7ZQz^t*|$Y4!4hl@)*YZ-(FuvyxsrBx0ng{rc?``VJBv570dG=% zfJ_A`$`TWB?(4klnyf2t<2ZQC9b|t>6VS8g2_lJHvy(TaP{9%pQo_v=qWK_0RKN#| zI-h_7VX5ozHvqlA*{qdu>Z@yfI6p!_F`xjB@jQVo^dQg6IjX{v$YkDVuXI_Fs)k&4BuiV!k5=bDxsu$+WMV;$A~ zSWvrFs~`OvsY5VPH>#1#-7R zKev&IIxK9az_d5Ifij<6Dx@_$f0oPDa3o28Y{~x2RA;qaH{Iz#ughp1+LA-vR_dfi zb|f8YaFKW42q0o+K^*<#ckMOvSjjZws zAcVWZM3Yg%cgdHXh$)>DNu_hbD4i2}&_xSEq1c=YCEYoZ2ZN*LzxrgXU{J#Y7@Zfv zffKn}zhwS4@6MV3OnO}o-^g04UARsgPf3e|mJSNlm@XZx9PAUs*z zY^7{h6%j07TiyKIqDt%tI!u%}g|FHUsEDkya?ZLunHvAQC37 zDk+kc`K(m{4l*eQ3P?*O z*%_mu+?+#t)YSX+-H6-Rrh!3UFWR$5SZ{CCPona$5b*REmStDt;1`Qj>T>YRz~}I? zz{6@(3WlA4nG0?$x$8lIrqyL^(QQ#z3YOIxVyUi7;rZTxUlW7UWvb*fFnm>drLTJ=ST?NBIvF@}g zIL`=*o03`y9OgP4xIt>A7Qi-7zEgkc5srq8nEVFC0&XWf>&X zC}Te7ik)Ctcmk{Y0-Zvs7t3HOJK}u>{H3%74s7OlP)GUmRdNc?Q}GDV647V;8O?5Nc&oSidHOog^4ggp>{lU2M0J z2>{};O|}2<&>A2>-BA)05lkKqfVG2(?3@w9VWHxn9=f?x>nnsuOg;?3@&8{aag>!v+f&) zeyu@E01q(}RJ`$%f~&?cl@_`r)WQzjk%{br*@i@xnWH7sW%LXV{pXs^ z*|{h+N=v4)Z^w*I18r{ zMVgnF=s+&WS#kUfGYyHomfr>O@sTf@m(7+*k&YWxTq#t>z-I%ugX7>;p*XrXWrlWu zU&d=FFcoJNwKJ>^CrLmuVS3bgjK9Hy#ld3+!<#@qZ0TP1qwb~1UN`ppv-rfYY)H$! zEXz=fS#u@C`J>JrG!G*7AY>R0_SX+?6du>sD&#W6>Ws=%+Lt24_O=x->y2u;#LhUF zpGdZX6O`-aIB?GM&7~+#kReXMrXdxHpHO(2FK{MY3Uwv!@`#3v1nF>qsJs>yg)(eI z1(}MhaI>xbX%E%N)QAM#CI9#`yp{VlG^)7}(Z3(P5bz&`5DI*lwDObA$%wQ7=uO9i z-m>Xj!{e3SLLgmiP!Otc$H35x^>R>zK$49*IXIY!h};&-xj@F$GI`jcUF&Ja#$=%QEAK4=X~ z1xs3g*6t^7kC{7bymIlVV?mtrHkLdvT|6g;;vg5yw4qUyRkwxwZ}-E!@trA)NV|42 zq1f!N@Quq6TBCr^`L6q!twwN35cmn&mjgH(JgZ(X^z2C1tfvT){k=U#LE;w?)wayZ zI!>Eiq%qm3wYN?0rb0B-+n?swjnXu02={4!Px|QgF*TFNmO}jY*v7-0P`m=804EfG z#to5EfL%74VIV7K!geEWFQ5Vt8Rnm9OS%ai9WeifU_m_8=JCwz@rTO!56 ziHRB6l43aSAsTJi+Z&v-*$ObGo?J8o78(O6{Et2EPSonz`DoPQ zHe`!A6GT}?D6yMa-;UU41F%PZv4K-pT(4V4xD!GtK@pu?fJbhsLzX!$gOFlbNUnn; zN?CMCMcS-6dzy?a`v&lGLLU$Y`IY&r88_3(Z2URG4z6h9RQIDL&-E`dV@>3jHMtux%Z|J)Kh(5f(p4U6)4ivBEk|C zpo*P&GD#>0r=x=)`wQaSe(H#?>(eHBtYJglTb*xPMk|@k%~`n+ilm4Y@jg29Ce}_y zUE>Mrg84VjoUMu6$Ei0rLrhK`8-39z8Z(X6H&I!2s1xy4-+VKwGv?v-xZn+wZF1x@ zA?F?v^0cTjAX;6OkcbBJUFyg3z};!~R7$uAQx5+n*kO~;7;A<$+2EKp%JfE89uB#z zQA4kY^0>EJt$BaI?`PX;4(+MYBXf}0I* z;nF&?+=nqqSR4$KWYrafvZmN}x|%(v=WTHZrs1k6ql*EO-bK?Tjmkw7kps{kV8nBz z(?LDW7x_521-GBh8kfdrrr8UEIkRf#X>SqlG_Z2dcwpX&c|7}}r`dMEoO;4nh}ELC zC`0eyhlz}JuqV_fD1h_!pU5nmi3KhbSatNC#cg6?P4Vo1fhi(!SN>^R?7R1gU7Wk+ z(PXYdFp#|_Qq@A-*(=K&;odP&g7oriy9v`GtGqdr z@P7LD>v+%nZDO_92+2rMZ&qD@Kbj*lVyTMWQ-Ix-wr78AioNizxn>lR`)zjpgDqsX zo9C14?&M{mXlaNjTo#475?<bBILg>&gf zu-H`8X+pO%$H-0`zEkTT+U#p4`YQaZy}hjoWNF92wdiMs5Zb%7Ubn+g0LyQ0gQUVs zb1$K$nP-xy)eTuk*y!*aWQA;$w&-znBCrk_E+%Bco1o~(v|<4n3ZsiB!8J=$wj>4w zMcA;v5ZcbvkT2jC94A$FqY~dk*w>_Y95ST#aYwa*?no_g+0CNw8nwM%BkGH$D{bWZ zZq`^2B6UszxmZdL6mo93WhS>vra%TRU=hIqsBcOz01E^*RSV}Z$;P)STO-sHX~8j2 z{_lMyF3o5%4`D~V-qG9L6T}k{L2TI5u2`~Ye>{o&)dNXn)8LL=IDtxsgG%SOh~%P4 zY`E3d2#KV$L@z0!QuC{!KA|DgkN_-LNfclmy>)oJ0-a5eGujruf6dALC&l;*5n+OC z=;(sWNDS%|Iwnc)^GlBvruSQ=X8gm=JeSsr>%XN+qP)1L~Gh!`C=@ z6|OeInyCw~%*$FSNF#iQBetdv7C*ZlL_%C5E2KG$siS>ZhaI4oA#&iuPz{!0ei&jh zJyk-TnO{YrkX4sv;wECL@LFHUuQN$&(q@adP?Ew6*Wlmuw5p=aY}VJ;tfE`Wb}00W zIlC-?hp$afprH5)xyv+-U|ptol#YE&1{GuxvWpe?&c2EB#cPDIYq#tUmkZXkWli*@U zS|D91hX+wYu?sixIZdxZ+z7EJUCaT=H5(zY)}{=@lC}l z*PVe7#1Z?w!(gV1BZA)&+o!7pe2bqZeOPdKW?tH?+o}c7K96(&&uEi(8mWBg!xbVE zQDhrZHwXKLrz+?g7u^^;1ya@qBktUWX$EhrB8(PygzNINn2$$kqPSfmZY@p=ySYuo zHPIG;|4I1a17o+=Yz<8K0fO2fZc|#otS{(@lXN`?93Um62gtfoj$k1JOM<~&W}&mS z)?77tj}`6ec4em=j(K+FrkUS)y4OoAHS97`lPsGypc`6aNRc0KP=AB zFx=QyIlt*}*9ufh`taFKj6wreLW=zt5}iX%3&gPwT)CHIlQRAUPy$S$w;osjdYrg& zc3ey{i%`jIPO!30L>>6rR#?goIC)dpJ&PTJnfb$Y=1)Y9;lGq({ta~eao@&d7Q}|_ zFW<~gp>`0~4g?-t3zY_&Er4k4DqVqD{}ou~V)#9dW z6=6stEZKu-!eu8#UOZ4n3Dd*6vq~Wfw)AlUNK+Qm;pQm0$r-02bb=`odI4wDs*7dZ z4+brL&fy}TJ<~0cO@Ur_FyYwnqDE8;!ki@PHdvxP)VSmjr_p10hpW&*yonAO6}~4a zh!8V_V`mZdbrRAFYsVc{RNp4iqv1b0fW#D1@73Rjrgm54We{oC>ci@-gRH+xjgmn! zAa@+BlemvuesNgdoivROt)QF4{y?)xDO*C`-9L6s8)0c^*Ps;#BSq#xLA$-`U_7k2 zG~TA{zUgI5?dfI$H_lU8*%Wf@5vCFY$@W|dg}sxJP86CpR3ZPBnm?}L+Nrwf=!x0u zC-MtMKb&*q0P1R%!S{~+SwG468BIJV8e&Wn8Qlns9xFD9V1rbqIE{rRg8E_TH_?VX zUMDS^9il7_#UOjL2{5__Vjy11CTz2-7suHG6vTDpY>taS@AZD$=1O(CnR0M!1q(H{ zEQ_uT<VXp&ufY_(AE=I#d1#G=h94T!vj2^OA6cDwcN*KkQ;N*&q{ zpg8p$?T!6o&MH;%9N1ADe>8KrngC^`yX=tmdOdFr?M4J99s>aEGaB`E#)IR6PNozu=&#KSpBYWI;S8KC`l_%p zcZeO@$vF0gAH=NKfsZsGM}F;)wxhA-1rtQ@ppC;K51==Q9vzy|p0V;fCk)?2n$S)qS-1kzLPUi`?@vJKOzu zLQ2oYIA?yD**S)jkd!m2z&VdTCKhI%WiyC6Z<~>o_P%KpB-jvMR|Hx8$wiKhU>6Rv zw=CaJmbUiY&oz^I1w#tPoZaHK+g7V?2G=w)u1F)c#W`$RtcH!BJFV zqv)6Crd@K2kIhx1IEq2iYNOg6{`+NZ4iDKFHN=H9f{q+k0yFV?GC0n>CM^mE4AcZ5 zI8cP%k{`08Bc;rfNp5gNi<`Jb(!>@f?ov7QNA%vkuO`9oQ#AdCQcybOXp!a*9BY?J_@ZzdDFz#16&;uZP*fIzXOcGPEy%00(=#zPSvVakDB z2#niXs=k@rFSq`X_R)2sj2PO%*O0f?sY5XeV*|um%J9hLAE`i^t05hGX+H^M|3Bsu}k*+d@*%p>}3(huNN9=yjgR>;zNW`78^r z03k%OH9M)4T)WWA?3#uhLri3Wx8=Fz$t(GFyJ)&!x!x;Ik1XE7icQ$bpahNS#m$4< zea}|Afi=SZ0#CAeStoE{_in#MvsCQBi;gcZ|AJk$rP|)>J~py)uys?zneBCZZq9

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 2f1756b6c..37b849362 100644 --- a/rust/crates/truapi-server/tests/wasm_crypto_vectors.rs +++ b/rust/crates/truapi-server/tests/wasm_crypto_vectors.rs @@ -53,6 +53,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 06bb1393f..f260818de 100644 --- a/rust/crates/truapi/src/lib.rs +++ b/rust/crates/truapi/src/lib.rs @@ -161,6 +161,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-identifier resolution error. From 8ef6af37c96105c454a1c33f06ef64a14adaa3cd Mon Sep 17 00:00:00 2001 From: Filip Baciu Date: Mon, 17 Aug 2026 11:29:04 +0300 Subject: [PATCH 02/19] docs(host-cli): point the DotnsPopController override at the live paseo-assethub address MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The documented 0x1c858C… value predates the Paseo Asset Hub Next V2 reset; the live DotnsPopController is 0xCC9323… (paritytech/dotns DEPLOYMENTS.md, same drift as paritytech/dotns#221). Discovery is on-chain, so this only touches the env-override docs. --- rust/crates/truapi-host-cli/SPEC.md | 2 +- rust/crates/truapi-host-cli/src/dotns_read.rs | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/rust/crates/truapi-host-cli/SPEC.md b/rust/crates/truapi-host-cli/SPEC.md index a86072d2e..75791beee 100644 --- a/rust/crates/truapi-host-cli/SPEC.md +++ b/rust/crates/truapi-host-cli/SPEC.md @@ -1529,7 +1529,7 @@ ended. This preserves the child status but bypasses later Rust destructors. | `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`. | +| `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 (post-reset) that address is `0xCC932348606cc1f3318cADeC5A5Cd2CA447f8a4b`; the authoritative value is the paseo-assethub entry in `DEPLOYMENTS.md` of paritytech/dotns, which changes on every chain reset. | | `XDG_STATE_HOME` | Preferred default state parent. | | `HOME` | Fallback default state parent. | | `VISUAL` | Preferred script editor. | diff --git a/rust/crates/truapi-host-cli/src/dotns_read.rs b/rust/crates/truapi-host-cli/src/dotns_read.rs index bb2c6c8ee..d7dd33025 100644 --- a/rust/crates/truapi-host-cli/src/dotns_read.rs +++ b/rust/crates/truapi-host-cli/src/dotns_read.rs @@ -19,7 +19,10 @@ use truapi_server::host_logic::dotns_gateway::{ /// discovery. /// /// Needed on networks whose deployed dispatcher exposes no target getter. On -/// paseo-next-v2 that address is `0x1c858C31497a7715C0D56A11208feB6b74FaB2aB`. +/// paseo-next-v2 (post-reset) that address is +/// `0xCC932348606cc1f3318cADeC5A5Cd2CA447f8a4b`; the authoritative value is the +/// paseo-assethub entry in `DEPLOYMENTS.md` of paritytech/dotns, which changes +/// on every chain reset. pub const DOTNS_POP_CONTROLLER_ENV: &str = "HOST_CLI_DOTNS_POP_CONTROLLER"; /// One Asset Hub RPC connection for a batch of dotNS reads. From 8a8bdf4484c06d3a3d830c61ee1dd48e98914885 Mon Sep 17 00:00:00 2001 From: Filip Baciu Date: Mon, 17 Aug 2026 13:09:07 +0300 Subject: [PATCH 03/19] fix(server,cli): make dotNS username reads and register-name work against the live gateway Review findings on #349 after the rebase, verified against the live paseo-next-v2 and previewnet Asset Hubs (spec 2000036, identical) and the dotNS contracts on master. In-core identity lookup - Open the identity follow withRuntime=true. chainHead_v1_call is refused on a follow opened without runtime, so every ReviveApi_call view failed and no in-core session ever resolved a username; only the CLI's plain-RPC path worked. Adds a scripted chainHead test that fails on the old value. - LabelStore labels carry the network TLD (DotnsPopController._writeRecord appends protocolRegistry.tld(); ".paseo" on paseo-next-v2), and the classifier only stripped ".dot", so a settled "alice01.paseo" became the full username "alice01.paseo". resolve_labels now reads tld(), strips it, and drops subnames; classify_labels takes bare labels. - Always merge pendingClaims with the store: a public registration or an incoming transfer deploys the store while gateway names stay pending, so the store alone hid them. Page the store (append-only ledger shared with public names) instead of reading only the first 16 labels. - Probe TARGET() and pendingClaims first; target()/pendingClaim are the legacy fallbacks (the deployed dispatcher and controller only have the former). checked_add on chain-supplied ABI lengths. - Lite stems follow StringUtils.isSingleDotLiteLabel (DNS label + two digits), not alphabetic-only. register-name - AsDotnsGatewayInfo::RegisterFullName is {proof, ring_index, revision, signature} on the live runtime (individuality#1013); the shape assertion and the extra encoding lacked revision, so the command failed at the metadata check. Read the People-collection root revision at the pinned block, wait for Asset Hub's members-subscriber to import it, and encode it. Ring/root/roots helpers take the collection identifier; the LitePeople wrappers keep their behaviour. - Refresh the Asset Hub metadata fixture to the live V16 (spec 2000036). - Fail early when the member key is onboarded but not yet built into a root (the sliced members read cannot prove it), and validate the label and --link-lite shape before signing. --- rust/crates/truapi-host-cli/src/dotns_read.rs | 11 +- .../truapi-host-cli/src/register_name.rs | 60 +++- .../src/host_logic/dotns_gateway.rs | 252 +++++++++++--- .../truapi-server/src/runtime/identity.rs | 323 +++++++++++++++++- .../runtime/statement_allowance/extension.rs | 18 +- .../src/runtime/statement_allowance/pgas.rs | 23 +- .../src/runtime/statement_allowance/ring.rs | 23 +- .../paseo-next-v2-asset-hub-metadata.scale | Bin 652465 -> 672225 bytes 8 files changed, 634 insertions(+), 76 deletions(-) diff --git a/rust/crates/truapi-host-cli/src/dotns_read.rs b/rust/crates/truapi-host-cli/src/dotns_read.rs index d7dd33025..1dd3eec95 100644 --- a/rust/crates/truapi-host-cli/src/dotns_read.rs +++ b/rust/crates/truapi-host-cli/src/dotns_read.rs @@ -18,8 +18,9 @@ use truapi_server::host_logic::dotns_gateway::{ /// 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 (post-reset) that address is +/// Discovery reads `DotnsGateway.DispatcherAddress` and asks the dispatcher +/// for its `TARGET()`; the override is for networks where that fails. On +/// paseo-next-v2 (post-reset) the controller is /// `0xCC932348606cc1f3318cADeC5A5Cd2CA447f8a4b`; the authoritative value is the /// paseo-assethub entry in `DEPLOYMENTS.md` of paritytech/dotns, which changes /// on every chain reset. @@ -69,11 +70,7 @@ impl AssetHubReader { } /// `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. + /// discovery through the dispatcher's `TARGET()` getter. async fn pop_controller(&mut self) -> Result<[u8; 20]> { if let Ok(value) = std::env::var(DOTNS_POP_CONTROLLER_ENV) { let trimmed = value.trim(); diff --git a/rust/crates/truapi-host-cli/src/register_name.rs b/rust/crates/truapi-host-cli/src/register_name.rs index e09ab5c1d..132653777 100644 --- a/rust/crates/truapi-host-cli/src/register_name.rs +++ b/rust/crates/truapi-host-cli/src/register_name.rs @@ -14,8 +14,9 @@ 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, + DOTNS_GATEWAY_CONTEXT, Link, MAX_BASE_LABEL_LEN, build_register_proof_message, + encode_register_full_name_extra, encode_register_name_call, is_dotted_lite_username, + is_full_person_label, }; use truapi_server::host_logic::product_account::{ SR25519_SIGNING_CONTEXT, derive_full_person_ring_vrf_entropy, derive_identity_keypair, @@ -46,6 +47,21 @@ pub struct RegisterNameConfig { /// 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<()> { + // The pallet and the contract reject malformed labels only at submission + // (as an invalid transaction or a revert), so check the cheap rules here. + if !is_full_person_label(&config.label) { + bail!( + "label {:?} is not a full-person base label: lowercase ASCII letters, digits and \ + hyphens, not starting or ending with a hyphen, not ending with a digit, at most \ + {MAX_BASE_LABEL_LEN} bytes", + config.label + ); + } + if let Some(lite) = &config.link_lite + && !is_dotted_lite_username(lite) + { + bail!("--link-lite {lite:?} is not a dotted lite username (`name.NN`)"); + } 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(); @@ -76,8 +92,28 @@ pub async fn register_name(config: &RegisterNameConfig) -> Result<()> { &at, ) .await?; + // `Members.Members` reports a ring index as soon as the key is onboarded; + // the members read above is sliced to the keys already built into the + // ring's root, so a fresh member can be missing from it for a few blocks. + if !members.contains(&member) { + bail!( + "the account's full-person key is onboarded into People ring {ring_index} but \ + not yet built into its root; retry once the next root revision is built" + ); + } + // The revision of the root the proof is built against, at the same pinned + // block as the members read, so the two cannot disagree. + let revision = ring::read_collection_ring_revision( + &people_rpc, + &people_metadata, + ring::PEOPLE_IDENTIFIER, + ring_index, + &at, + ) + .await?; debug!( ring_index, + revision, members = members.len(), "full-person ring resolved" ); @@ -124,7 +160,25 @@ pub async fn register_name(config: &RegisterNameConfig) -> Result<()> { &proof_message, )?; - let extra = encode_register_full_name_extra(info_variant, &ring_proof, ring_index, &signature); + // Asset Hub only verifies proofs against root revisions it has imported + // from People; wait for this one before submitting. + alloc::pgas::await_collection_ring_revision( + &ah_rpc, + &ah_metadata, + ring::PEOPLE_IDENTIFIER, + ring_index, + revision, + ) + .await + .context("wait for Asset Hub to import the People ring root revision")?; + + let extra = encode_register_full_name_extra( + info_variant, + &ring_proof, + ring_index, + revision, + &signature, + ); let extrinsic = extrinsic::build_unsigned_extrinsic_with_extra( &ah_metadata, &chain_state, diff --git a/rust/crates/truapi-server/src/host_logic/dotns_gateway.rs b/rust/crates/truapi-server/src/host_logic/dotns_gateway.rs index 5cb53af61..3323e2bce 100644 --- a/rust/crates/truapi-server/src/host_logic/dotns_gateway.rs +++ b/rust/crates/truapi-server/src/host_logic/dotns_gateway.rs @@ -105,18 +105,23 @@ pub fn encode_register_name_call( /// Encodes the `AsDotnsGateway` extension extra. /// -/// The value is `Some(RegisterFullName { proof, ring_index, signature })`. The +/// The value is `Some(RegisterFullName { proof, ring_index, revision, +/// signature })`; `revision` is the People-collection root revision the proof +/// was built against, which Asset Hub's `members-subscriber` must hold. The /// signature is wrapped as `MultiSignature::Sr25519`. pub fn encode_register_full_name_extra( info_variant: u8, proof: &[u8], ring_index: u32, + revision: u32, signature: &[u8; 64], ) -> Vec { - // Option::Some ‖ info variant ‖ Vec(proof) ‖ ring_index ‖ Sr25519 ‖ sig. + // Option::Some ‖ info variant ‖ Vec(proof) ‖ ring_index ‖ revision ‖ + // Sr25519 ‖ sig. let mut extra = vec![0x01, info_variant]; proof.encode_to(&mut extra); ring_index.encode_to(&mut extra); + revision.encode_to(&mut extra); extra.push(0x01); extra.extend_from_slice(signature); extra @@ -280,8 +285,9 @@ fn describe_revert(data: &[u8]) -> String { .try_into() .expect("8-byte slice from a length-checked buffer; qed"), ) as usize; - if let Some(message) = data - .get(68..68 + len) + if let Some(message) = 68usize + .checked_add(len) + .and_then(|end| data.get(68..end)) .and_then(|bytes| core::str::from_utf8(bytes).ok()) { return format!("Error({message:?})"); @@ -321,12 +327,20 @@ fn decode_string_at(data: &[u8], at: usize) -> Result Result { + decode_string_at(data, word_usize(data, 0)?) +} + /// Decodes a single `address` return value. pub fn decode_address(data: &[u8]) -> Result<[u8; 20], DotnsContractError> { let bytes = word(data, 0)?; @@ -398,11 +412,19 @@ pub struct DotnsIdentity { pub full_username: Option, } -/// Classifies flat contract labels into lite and full usernames. +/// Classifies bare 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` → +/// The gateway flattens a lite username `stem.NN` (a DNS-label stem plus exactly +/// two digits, `StringUtils.isSingleDotLiteLabel`) into the flat label `stemNN` +/// before minting, so a flat label whose last two characters are digits after a +/// DNS-label stem is read back as a lite username and re-dotted (`alice01` → /// `alice.01`). Everything else is a full username. First hit per slot wins. +/// Labels are expected bare: [`resolve_labels`] strips the network TLD, and any +/// label still carrying a dot (a subname such as `app.alice`) is skipped. +/// +/// Lite and public names share one namespace on the contract side, so a public +/// name ending in two digits is indistinguishable from a flattened lite name +/// here; the contract documents that ambiguity as accepted. pub fn classify_labels(labels: I) -> DotnsIdentity where I: IntoIterator, @@ -410,12 +432,13 @@ where { let mut identity = DotnsIdentity::default(); for label in labels { - let label = label.as_ref().trim_end_matches(".dot"); + let label = label.as_ref(); + if label.contains('.') { + continue; + } 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()); + let is_lite = + is_dns_label(stem) && digits.len() == 2 && digits.chars().all(|c| c.is_ascii_digit()); if is_lite { identity .lite_username @@ -429,9 +452,50 @@ where identity } -/// Page size for `LabelStore.getLabels`. Gateway users hold at most a lite and -/// a full name. +/// A canonical DNS label: lowercase ASCII letters, digits and hyphens, neither +/// starting nor ending with a hyphen (`StringUtils._isDnsLabel`). +pub fn is_dns_label(value: &str) -> bool { + !value.is_empty() + && !value.starts_with('-') + && !value.ends_with('-') + && value + .chars() + .all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '-') +} + +/// Longest label the gateway pallet accepts (`BaseLabel = BoundedVec`). +pub const MAX_BASE_LABEL_LEN: usize = 32; + +/// Whether `label` can be a full-person base label: a DNS label of at most +/// [`MAX_BASE_LABEL_LEN`] bytes with no trailing digits, so it can never be +/// mistaken for a flattened lite username (`is_lite_person_label`). +pub fn is_full_person_label(label: &str) -> bool { + is_dns_label(label) + && label.len() <= MAX_BASE_LABEL_LEN + && !label.ends_with(|c: char| c.is_ascii_digit()) +} + +/// Whether `value` is a dotted lite username, `stem.NN`: a DNS-label stem, one +/// dot, exactly two digits (`StringUtils.isSingleDotLiteLabel`), and at most +/// [`MAX_BASE_LABEL_LEN`] bytes once flattened. +pub fn is_dotted_lite_username(value: &str) -> bool { + match value.rsplit_once('.') { + Some((stem, digits)) => { + is_dns_label(stem) + && digits.len() == 2 + && digits.chars().all(|c| c.is_ascii_digit()) + && stem.len() + 2 <= MAX_BASE_LABEL_LEN + } + None => false, + } +} + +/// Page size for `LabelStore.getLabels`. const LABEL_PAGE_LIMIT: u64 = 16; +/// Upper bound on pages read from one `LabelStore`. The store is an append-only +/// ledger shared with public registrations and incoming transfers, so it can +/// grow well past the one lite and one full name a gateway user holds. +const LABEL_PAGE_MAX: u64 = 16; /// How a caller reaches Asset Hub for dotNS reads. /// @@ -450,9 +514,9 @@ pub trait DotnsTransport { /// 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. +/// target getter answers: `TARGET()` on the deployed `RootGatewayDispatcher` +/// (dotNS `master`), with `target()` kept as a fallback for older builds. `None` +/// when the gateway is not deployed on the chain at all. pub async fn discover_pop_controller( transport: &mut T, ) -> Result, String> { @@ -470,11 +534,11 @@ pub async fn discover_pop_controller( else { return Ok(None); }; - let output = match transport.view(&dispatcher, call_no_args("target()")).await { + let output = match transport.view(&dispatcher, call_no_args("TARGET()")).await { Ok(output) => output, Err(_) => { transport - .view(&dispatcher, call_no_args("TARGET()")) + .view(&dispatcher, call_no_args("target()")) .await? } }; @@ -483,11 +547,18 @@ pub async fn discover_pop_controller( .map_err(|err| format!("RootGatewayDispatcher target getter: {err}")) } -/// Resolves the flat contract labels `account` holds. +/// Resolves the bare 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. +/// Two sources are merged. The controller's pending claims hold gateway-minted +/// names the user has not settled into a `LabelStore` yet (`claimLabelStore`); +/// those come first. The user's `LabelStore`, when deployed, holds every name +/// written for them: gateway names once settled, plus public registrations and +/// incoming transfers. Store labels carry the network TLD (`alice01.paseo`), +/// which is stripped here; subnames (`app.alice`) are dropped. +/// +/// A store alone is not proof that pending claims are settled: a public +/// registration or an incoming transfer deploys the store while gateway names +/// stay pending, so both sources are always read. pub async fn resolve_labels( transport: &mut T, controller: &[u8; 20], @@ -495,6 +566,8 @@ pub async fn resolve_labels( ) -> Result, String> { let user = account_to_h160(account); + let mut labels = pending_claim_labels(transport, controller, &user).await?; + let registry_output = transport .view(controller, call_no_args("protocolRegistry()")) .await?; @@ -515,36 +588,75 @@ pub async fn resolve_labels( .await?; let store = decode_address(&store_output) .map_err(|err| format!("StoreFactory.getLabelStore: {err}"))?; + if store == [0u8; 20] { + return Ok(labels); + } - if store != [0u8; 20] { + let tld_output = transport.view(®istry, call_no_args("tld()")).await?; + let tld = decode_string(&tld_output).map_err(|err| format!("ProtocolRegistry.tld(): {err}"))?; + + for page in 0..LABEL_PAGE_MAX { let labels_output = transport .view( &store, - call_u256_pair("getLabels(uint256,uint256)", 0, LABEL_PAGE_LIMIT), + call_u256_pair( + "getLabels(uint256,uint256)", + page * LABEL_PAGE_LIMIT, + LABEL_PAGE_LIMIT, + ), ) .await?; - return decode_string_array(&labels_output) - .map_err(|err| format!("LabelStore.getLabels: {err}")); + let stored = decode_string_array(&labels_output) + .map_err(|err| format!("LabelStore.getLabels: {err}"))?; + let short_page = (stored.len() as u64) < LABEL_PAGE_LIMIT; + for label in stored { + if let Some(bare) = bare_store_label(&label, &tld) + && !labels.iter().any(|known| known == bare) + { + labels.push(bare.to_string()); + } + } + if short_page { + break; + } } + Ok(labels) +} - // The current controller exposes `pendingClaim`, one struct. The - // deployed-era one exposes `pendingClaims`, an array. +/// Strips the network TLD from a `LabelStore` label. `None` for labels that do +/// not carry it or that still hold a dot afterwards (subnames). +fn bare_store_label<'a>(label: &'a str, tld: &str) -> Option<&'a str> { + let bare = if tld.is_empty() { + label + } else { + label.strip_suffix(tld)? + }; + (!bare.is_empty() && !bare.contains('.')).then_some(bare) +} + +/// Gateway-minted labels of `user` still waiting for `claimLabelStore`. +/// +/// The deployed controller exposes `pendingClaims(address)`, an array. Older +/// builds exposed `pendingClaim(address)`, one struct; it is kept as a fallback. +async fn pending_claim_labels( + transport: &mut T, + controller: &[u8; 20], + user: &[u8; 20], +) -> Result, String> { match transport - .view(controller, call_address("pendingClaim(address)", &user)) + .view(controller, call_address("pendingClaims(address)", user)) .await { - Ok(claim_output) => { + Ok(claims_output) => decode_pending_claims_array(&claims_output) + .map_err(|err| format!("DotnsPopController.pendingClaims: {err}")), + Err(_) => { + let claim_output = transport + .view(controller, call_address("pendingClaim(address)", user)) + .await?; 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}")) - } } } @@ -649,15 +761,17 @@ mod tests { ); // 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]); + // revision LE ‖ 0x01 (Sr25519) ‖ signature. Matches registerTx.ts + // encodeAsDotnsGatewayValue plus the revision field the runtime added + // (individuality#1013). + let extra = encode_register_full_name_extra(0, &[0xEE; 785], 3, 5, &[0xAB; 64]); assert_eq!( extra, [ vec![0x01, 0x00], vec![0xEEu8; 785].encode(), 3u32.to_le_bytes().to_vec(), + 5u32.to_le_bytes().to_vec(), vec![0x01], vec![0xAB; 64], ] @@ -676,6 +790,9 @@ mod tests { assert_eq!(hex::encode(selector("protocolRegistry()")), "7656419f"); assert_eq!(hex::encode(selector("get(bytes32)")), "8eaa6ac0"); assert_eq!(hex::encode(selector("target()")), "d4b83992"); + assert_eq!(hex::encode(selector("TARGET()")), "cc1f2afa"); + assert_eq!(hex::encode(selector("pendingClaims(address)")), "ca78533d"); + assert_eq!(hex::encode(selector("tld()")), "2d551432"); } #[test] @@ -843,21 +960,58 @@ mod tests { 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")); + // Dotted labels are not base names and are skipped; a DNS stem may hold + // digits and hyphens (`isSingleDotLiteLabel`), a hyphen may not lead or + // trail it. + let identity = classify_labels(["bobby42.dot", "app.web3app", "web3app", "a2b34", "-x01"]); + assert_eq!(identity.lite_username.as_deref(), Some("a2b.34")); 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"]); + // One trailing digit is not lite format. + let identity = classify_labels(["alice1"]); assert_eq!(identity.lite_username, None); assert_eq!(identity.full_username.as_deref(), Some("alice1")); } + #[test] + fn label_validators_follow_the_contract_rules() { + assert!(is_full_person_label("alicebc")); + assert!(is_full_person_label("web3-app")); + assert!( + !is_full_person_label("alice01"), + "trailing digits are lite format" + ); + assert!(!is_full_person_label("Alice")); + assert!(!is_full_person_label("alice.bc")); + assert!(!is_full_person_label("-alice")); + assert!(!is_full_person_label(&"a".repeat(33))); + + assert!(is_dotted_lite_username("alice.01")); + assert!(is_dotted_lite_username("a2b.34")); + assert!(!is_dotted_lite_username("alice01")); + assert!(!is_dotted_lite_username("alice.1")); + assert!(!is_dotted_lite_username("alice.012")); + assert!(!is_dotted_lite_username("a.lice.01")); + assert!(!is_dotted_lite_username(".01")); + assert!(!is_dotted_lite_username(&format!("{}.01", "a".repeat(31)))); + } + + #[test] + fn store_labels_lose_the_network_tld_and_subnames_are_dropped() { + assert_eq!(bare_store_label("alice01.paseo", ".paseo"), Some("alice01")); + assert_eq!(bare_store_label("alice01.dot", ".dot"), Some("alice01")); + // Wrong TLD, subname, or nothing left after the TLD. + assert_eq!(bare_store_label("alice01.dot", ".paseo"), None); + assert_eq!(bare_store_label("app.alice.paseo", ".paseo"), None); + assert_eq!(bare_store_label(".paseo", ".paseo"), None); + // An empty TLD leaves bare labels as they are. + assert_eq!(bare_store_label("alice01", ""), Some("alice01")); + } + #[test] fn storage_keys_have_the_expected_layout() { let alias_key = account_alias_key(&[0x11; 32]); diff --git a/rust/crates/truapi-server/src/runtime/identity.rs b/rust/crates/truapi-server/src/runtime/identity.rs index 61edc8d86..122f30e82 100644 --- a/rust/crates/truapi-server/src/runtime/identity.rs +++ b/rust/crates/truapi-server/src/runtime/identity.rs @@ -194,7 +194,7 @@ impl<'a> DotnsLookup<'a> { follow_id.clone(), RemoteChainHeadFollowRequest { genesis_hash: genesis_hash.clone(), - with_runtime: false, + with_runtime: true, }, ); let hash = wait_for_chain_head_best_hash( @@ -293,3 +293,324 @@ fn started_operation_id(operation: OperationStartedResult) -> Result [u8; 32] { + let mut word = [0u8; 32]; + word[24..].copy_from_slice(&value.to_be_bytes()); + word + } + + fn abi_address(address: &[u8; 20]) -> Vec { + let mut word = [0u8; 32]; + word[12..].copy_from_slice(address); + word.to_vec() + } + + /// ABI string tail: a length word plus padded bytes. + fn abi_string_tail(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 + } + + /// A single `string` return value. + fn abi_string(value: &str) -> Vec { + [abi_word(32).to_vec(), abi_string_tail(value)].concat() + } + + /// A `string[]` return value. + fn abi_string_array(values: &[&str]) -> Vec { + let tails: Vec> = values.iter().map(|v| abi_string_tail(v)).collect(); + let mut out = abi_word(32).to_vec(); + out.extend_from_slice(&abi_word(values.len() as u64)); + let mut offset = 32 * values.len(); + for tail in &tails { + out.extend_from_slice(&abi_word(offset as u64)); + offset += tail.len(); + } + for tail in tails { + out.extend_from_slice(&tail); + } + out + } + + /// A `(string label, uint64 mintedAt)[]` return value with one element. + fn abi_pending_claims(label: &str, minted_at: u64) -> Vec { + [ + abi_word(32).to_vec(), + abi_word(1).to_vec(), + abi_word(32).to_vec(), + abi_word(64).to_vec(), + abi_word(minted_at).to_vec(), + abi_string_tail(label), + ] + .concat() + } + + /// `ReviveApi_call` output carrying successful return `data`. + fn contract_result(data: &[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.push(0x00); + 0u32.encode_to(&mut out); + data.encode_to(&mut out); + out + } + + /// The scripted contract side: `(dest, selector)` → return data. + fn view_output(dest: &[u8; 20], input: &[u8]) -> Vec { + let sel: [u8; 4] = input[..4].try_into().unwrap(); + let data = match (*dest, sel) { + (DISPATCHER, s) if s == selector("TARGET()") => abi_address(&CONTROLLER), + (CONTROLLER, s) if s == selector("pendingClaims(address)") => { + abi_pending_claims("alice01", 42) + } + (CONTROLLER, s) if s == selector("protocolRegistry()") => abi_address(®ISTRY), + (REGISTRY, s) if s == selector("get(bytes32)") => abi_address(&FACTORY), + (REGISTRY, s) if s == selector("tld()") => abi_string(".paseo"), + (FACTORY, s) if s == selector("getLabelStore(address)") => abi_address(&STORE), + (STORE, s) if s == selector("getLabels(uint256,uint256)") => { + abi_string_array(&["myproject.paseo", "app.myproject.paseo"]) + } + (dest, sel) => panic!( + "unscripted view {} on 0x{}", + hex::encode(sel), + hex::encode(dest) + ), + }; + contract_result(&data) + } + + struct ScriptedAssetHub { + sent: Arc>>, + follow_with_runtime: Arc>>, + sender: Arc>>>, + receiver: Arc>>>, + next_operation: Arc>, + } + + impl ScriptedAssetHub { + fn new() -> Self { + let (sender, receiver) = mpsc::unbounded(); + Self { + sent: Arc::new(Mutex::new(Vec::new())), + follow_with_runtime: Arc::new(Mutex::new(None)), + sender: Arc::new(Mutex::new(Some(sender))), + receiver: Arc::new(Mutex::new(Some(receiver))), + next_operation: Arc::new(Mutex::new(0)), + } + } + + fn share(&self) -> Self { + Self { + sent: self.sent.clone(), + follow_with_runtime: self.follow_with_runtime.clone(), + sender: self.sender.clone(), + receiver: self.receiver.clone(), + next_operation: self.next_operation.clone(), + } + } + + fn frames(&self, request: &str) -> Vec { + let request: JsonValue = serde_json::from_str(request).unwrap(); + let id = request.get("id").cloned().unwrap_or(JsonValue::Null); + let method = request["method"].as_str().unwrap(); + let response = |result: JsonValue| { + json!({"jsonrpc": "2.0", "id": id, "result": result}).to_string() + }; + let follow_event = |result: JsonValue| { + json!({ + "jsonrpc": "2.0", + "method": "chainHead_v1_followEvent", + "params": {"subscription": FOLLOW_ID, "result": result} + }) + .to_string() + }; + let mut next_operation = self.next_operation.lock().unwrap(); + match method { + "chainHead_v1_follow" => { + *self.follow_with_runtime.lock().unwrap() = request["params"][0].as_bool(); + vec![ + response(json!(FOLLOW_ID)), + follow_event(json!({ + "event": "initialized", + "finalizedBlockHashes": [BEST_HASH], + "finalizedBlockRuntime": null + })), + follow_event(json!({ + "event": "bestBlockChanged", + "bestBlockHash": BEST_HASH + })), + ] + } + "chainHead_v1_storage" => { + *next_operation += 1; + let operation_id = format!("storage-{}", *next_operation); + let key = request["params"][2][0]["key"].as_str().unwrap(); + let key_bytes = hex::decode(key.trim_start_matches("0x")).unwrap(); + let mut frames = vec![response( + json!({"result": "started", "operationId": operation_id}), + )]; + if key_bytes == dispatcher_address_key() { + frames.push(follow_event(json!({ + "event": "operationStorageItems", + "operationId": operation_id, + "items": [{"key": key, "value": format!("0x{}", hex::encode(DISPATCHER))}] + }))); + } + frames.push(follow_event(json!({ + "event": "operationStorageDone", + "operationId": operation_id + }))); + frames + } + "chainHead_v1_call" => { + *next_operation += 1; + let operation_id = format!("call-{}", *next_operation); + assert_eq!(request["params"][2].as_str(), Some("ReviveApi_call")); + let args = hex::decode( + request["params"][3] + .as_str() + .unwrap() + .trim_start_matches("0x"), + ) + .unwrap(); + // origin[32] ‖ dest[20] ‖ value u128 ‖ None ‖ None ‖ Vec(input). + let dest: [u8; 20] = args[32..52].try_into().unwrap(); + let input = Vec::::decode(&mut &args[70..]).unwrap(); + vec![ + response(json!({"result": "started", "operationId": operation_id})), + follow_event(json!({ + "event": "operationCallDone", + "operationId": operation_id, + "output": format!("0x{}", hex::encode(view_output(&dest, &input))) + })), + ] + } + "chainHead_v1_unpin" | "chainHead_v1_unfollow" => vec![response(JsonValue::Null)], + other => panic!("unscripted method {other}"), + } + } + } + + struct ScriptedConnection { + provider: ScriptedAssetHub, + receiver: Mutex>>, + } + + impl JsonRpcConnection for ScriptedConnection { + fn send(&self, request: String) { + self.provider.sent.lock().unwrap().push(request.clone()); + let frames = self.provider.frames(&request); + if let Some(sender) = self.provider.sender.lock().unwrap().as_ref() { + for frame in frames { + sender.unbounded_send(frame).unwrap(); + } + } + } + + fn responses(&self) -> BoxStream<'static, String> { + self.receiver + .lock() + .unwrap() + .take() + .expect("responses called once") + .boxed() + } + + fn close(&self) { + self.provider.sender.lock().unwrap().take(); + } + } + + #[async_trait] + impl RuntimeChainProvider for ScriptedAssetHub { + async fn connect( + &self, + _genesis_hash: Vec, + ) -> Result, RuntimeFailure> { + Ok(Arc::new(ScriptedConnection { + receiver: Mutex::new(self.receiver.lock().unwrap().take()), + provider: self.share(), + })) + } + } + + #[test] + fn in_core_lookup_resolves_usernames_over_one_runtime_follow() { + let provider = Arc::new(ScriptedAssetHub::new()); + let chain = ChainRuntime::new(provider.clone(), thread_per_subscription_spawner()); + let session = SessionInfo { + public_key: [0x11; 32], + sso: None, + root_entropy_source: None, + identity_account_id: Some(ACCOUNT), + lite_username: None, + full_username: None, + }; + + let resolved = futures::executor::block_on(resolve_session_identity_with_chain( + &chain, [0xcc; 32], session, + )); + + // The pending claim yields the lite name; the settled store yields the + // full name with the network TLD stripped and the subname dropped. + assert_eq!(resolved.lite_username.as_deref(), Some("alice.01")); + assert_eq!(resolved.full_username.as_deref(), Some("myproject")); + // `chainHead_v1_call` is only served on follows opened with runtime. + assert_eq!( + *provider.follow_with_runtime.lock().unwrap(), + Some(true), + "the identity follow must be opened withRuntime=true or every view fails" + ); + // Every view was addressed to the mapped H160 of the identity account. + let calls = provider + .sent + .lock() + .unwrap() + .iter() + .filter(|request| request.contains("chainHead_v1_call")) + .count(); + assert!( + calls >= 7, + "expected the full discovery and label chain, got {calls} views" + ); + let _ = account_to_h160(&ACCOUNT); + } +} 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 129ed48e4..da9799546 100644 --- a/rust/crates/truapi-server/src/runtime/statement_allowance/extension.rs +++ b/rust/crates/truapi-server/src/runtime/statement_allowance/extension.rs @@ -93,7 +93,7 @@ pub enum MetadataError { /// `AsDotnsGatewayInfo::RegisterFullName` did not have the expected field shape. #[error( "AsDotnsGatewayInfo::RegisterFullName fields are [{actual}], expected \ - [proof, ring_index, signature]; the runtime shape drifted" + [proof, ring_index, revision, signature]; the runtime shape drifted" )] RegisterFullNameShapeDrift { /// Actual comma-separated field names. @@ -565,9 +565,10 @@ impl Metadata { /// Resolve `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. + /// Asserts the exact `{proof, ring_index, revision, signature}` field shape + /// (the People-collection root revision the proof was built against). A + /// runtime that changes the variant then fails loudly instead of + /// mis-encoding. pub fn dotns_register_full_name_variant(&self) -> Result { let variant = self.extension_info_variant(AS_DOTNS_GATEWAY, "RegisterFullName")?; let fields: Vec<&str> = variant @@ -575,7 +576,7 @@ impl Metadata { .iter() .map(|field| field.name.as_deref().unwrap_or("")) .collect(); - if fields != ["proof", "ring_index", "signature"] { + if fields != ["proof", "ring_index", "revision", "signature"] { return Err(MetadataError::RegisterFullNameShapeDrift { actual: fields.join(", "), } @@ -1156,8 +1157,9 @@ mod tests { ); } - /// Asset Hub fixture metadata captured from paseo-next-v2. That is the - /// runtime the dotNS gateway flows were validated against. + /// Asset Hub fixture metadata captured from paseo-next-v2 (V16, spec + /// 2000036, identical to previewnet). 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"); @@ -1169,7 +1171,7 @@ mod tests { ..fixture_state() }; - // RegisterFullName resolves with the asserted 3-field shape. + // RegisterFullName resolves with the asserted 4-field shape. assert_eq!(metadata.dotns_register_full_name_variant().unwrap(), 0); assert!( metadata diff --git a/rust/crates/truapi-server/src/runtime/statement_allowance/pgas.rs b/rust/crates/truapi-server/src/runtime/statement_allowance/pgas.rs index 3d7eb43d9..9a30266ea 100644 --- a/rust/crates/truapi-server/src/runtime/statement_allowance/pgas.rs +++ b/rust/crates/truapi-server/src/runtime/statement_allowance/pgas.rs @@ -94,11 +94,11 @@ pub struct PgasClaimOutcome { /// /// Both map keys are `Blake2_128Concat` here, unlike the People chain's /// `Members` maps which take the collection identifier verbatim. -fn ring_roots_key(ring_index: u32) -> Vec { +fn collection_ring_roots_key(identifier: &[u8; 32], ring_index: u32) -> Vec { [ twox_128(b"MembersSubscriber").as_slice(), twox_128(b"RingRoots").as_slice(), - &blake2_128_concat(LITE_PEOPLE_IDENTIFIER), + &blake2_128_concat(identifier), &blake2_128_concat(&ring_index.to_le_bytes()), ] .concat() @@ -274,6 +274,18 @@ pub async fn await_ring_revision( metadata: &Metadata, ring_index: u32, revision: u32, +) -> Result<(), StatementAllowanceError> { + await_collection_ring_revision(rpc, metadata, LITE_PEOPLE_IDENTIFIER, ring_index, revision) + .await +} + +/// Same, for collection `identifier`'s `ring_index`. +pub async fn await_collection_ring_revision( + rpc: &RpcClient, + metadata: &Metadata, + identifier: &[u8; 32], + ring_index: u32, + revision: u32, ) -> Result<(), StatementAllowanceError> { let value_type = metadata .storage_value_type("MembersSubscriber", "RingRoots") @@ -283,7 +295,10 @@ pub async fn await_ring_revision( })?; let started = Instant::now(); loop { - if let Some(bytes) = rpc.get_storage(&ring_roots_key(ring_index)).await? { + if let Some(bytes) = rpc + .get_storage(&collection_ring_roots_key(identifier, ring_index)) + .await? + { let mut input = bytes.as_slice(); let records = Vec::::decode_as_type( &mut input, @@ -342,7 +357,7 @@ mod tests { /// Both map keys are hashed here, unlike the People chain's `Members` maps. #[test] fn subscriber_ring_key_hashes_both_map_keys() { - let key = ring_roots_key(136); + let key = collection_ring_roots_key(LITE_PEOPLE_IDENTIFIER, 136); assert_eq!(key.len(), 16 + 16 + 16 + 32 + 16 + 4); assert_eq!( 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 b128c2a33..a119340bc 100644 --- a/rust/crates/truapi-server/src/runtime/statement_allowance/ring.rs +++ b/rust/crates/truapi-server/src/runtime/statement_allowance/ring.rs @@ -127,12 +127,12 @@ fn collections_key() -> Vec { .concat() } -/// `Members.Root[(id, ring_index)]` storage key. -fn ring_root_key(ring_index: u32) -> Vec { +/// `Members.Root[(identifier, ring_index)]` storage key. +fn collection_ring_root_key(identifier: &[u8; 32], ring_index: u32) -> Vec { [ twox_128(b"Members").as_slice(), twox_128(b"Root").as_slice(), - LITE_PEOPLE_IDENTIFIER.as_slice(), + identifier.as_slice(), &blake2_128_concat(&ring_index.to_le_bytes()), ] .concat() @@ -394,7 +394,22 @@ pub async fn read_ring_revision( ring_index: u32, at: &str, ) -> Result { - match rpc.get_storage_at(&ring_root_key(ring_index), at).await? { + read_collection_ring_revision(rpc, metadata, LITE_PEOPLE_IDENTIFIER, ring_index, at).await +} + +/// Same, for collection `identifier`'s `ring_index`: the revision of its +/// current root, `0` when no root has been built yet. +pub async fn read_collection_ring_revision( + rpc: &RpcClient, + metadata: &Metadata, + identifier: &[u8; 32], + ring_index: u32, + at: &str, +) -> Result { + match rpc + .get_storage_at(&collection_ring_root_key(identifier, ring_index), at) + .await? + { Some(bytes) => { let value_type = metadata.storage_value_type("Members", "Root").ok_or( MetadataError::MissingStorageType { 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 index d6163151353bebc4c7af2b3d5af408673d781661..e38a0bf5272fa463212489131d16ccca239917d3 100644 GIT binary patch delta 48234 zcmce94}4U`wf~*FbMM_vHpwO=kU#<(NFaeE5FnTUK_Uc+8c2XBQA4sM3#=sBu$us> zVnoGC6_w`UNEHjVw8e@_pSaQ|YOM5?wy3o7Uah5-wzNe>i@uzM_A!OY8q`QYNnq=Z#Aq#TxprOumC1?0+Ts9)5(g z!~ONnrv7iI6tJWHZ>HRqxU|(1X!UjXa^*OnH|K`Gi`+|kI=g)x-Z{!l#l0-x^#pqY zD0PZT^*`?_U}yV(;qW|Ah$Eq3TYfV}%&(hE$VfW*-e3sMy=d>+qu9-_X z{&LuOR@l$d@8-oC+at@JPS0o6NNVoCC8Jbr()y35tmyx*i~@DLS?#Xj1?mnnHEE>L zpEhD5JA~qG{gs(X{ZEgW$j@r*qJ%WXsUh)7el9*CssF>wDd_W{vC+6L++-J1gB`8gu2ejos1ZX>a#- zHwUeOm^pKCz_;4hIXi~_*=7+mype&}Gv!(Z^_UD9hSFsX?&eyGGv$8JO*xagipC?$= z+Uf_d)^x7&6Y>`*d$f3UHCr{}qK(a-=SQO(yL&wCvb3UHw26T`_-N6zhLsZuyJqMk z#ep?!Qjx})bH1k+c(wX5D6QQ-f2V`9^19WY;Jio*HKK80_pyNL;NmtPTc{>+;V;mJ zi(mOzqI$+A&iL4PHI)n3Ydl*#Jxe!4%Pnk@n!&~GH*1-py|=I(D$5cb>seu_t%KPe zEGvYMrFNDR8n~Tp)(f*Z&s^L^7|?j-aAtQc@^^aCwri=s!wa6}ioz5nN37k#ma<&& z@)ow7<%?Ut#;QV%_ppCutWbQol@*05?q$#6BZQBQs+HJ3d6p6S+WqVS8!`$XV)Y3u zKZL45TVZ}&!-Yc|E;Ji16s}vqBr_go-($t0Gmo?339R(_ee9W9Ru+2puk0*S%edHn zk3L1b@hLMhDmcr$-rG8R*&Icw00`BkRC4jadTm7i&c>ur$^|w<1L_MtXa8Yi)xw>j zxYDpMU zaj|-Jsj5w7jUjxDS6NSJ?hN&QmGy>>m8&<{*yd3FZ1qYF7<;Q!m(H3(>#tUQ z)SY*)Q74#>Tu1h*fY*Mgg+AM;E@W({_(*6|LmR%Nj?>JK z$)O+jsqZniR~+1|7P5Vz-)vUfRCYkjzgwM%PygNOadcNyeOX=3n#9+>tj=X#@y?gk zY4}27x~Tn%n#4S!^j>u&9x|!rUUd^n$@h=%RjZg!Oy8>J0=4V6s-43+IGaMN!cuQ{ zx36<`P*Hql+4r}qwX7owcKAB3aHW71#gtZ*b!LIPzoK5ldd&CVeMMa|zBi6cuPfsp)L93FFLtYUU+S+hXPx+^?=;Tg~@x+^=4#ZZp{>V>{Vf^=rA3TVm+b_;OsE){WhQ794-CfHLS#$M})uF>yhX!IgbXfRLsR73T_2n>Ej>zhxeyiRdo$5!qIPh

rYaaGOEO!bkpWh0R1N%U9 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 X5+Cn>FDE_ABeH(rb8YEtfXDLxLmf23 delta 30279 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 bM@WpvafrmvxYO&ChDf&Kl54@8GuHhD Date: Mon, 17 Aug 2026 13:09:07 +0300 Subject: [PATCH 04/19] fix(host-cli): give previewnet its own Bulletin and document both presets MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Previewnet has a live Bulletin chain (wss://previewnet.substrate.dev/bulletin, genesis 0x2778b1c9…, "Bulletin Local"), which dotli and dotns-sdk already use; the preset borrowed paseo's, so the host advertised and routed a Bulletin genesis that disagreed with the app's config. - The live drift test accepts "Individuality" for the People role (previewnet's People chain calls itself "Individuality Local"); it now passes for all six roles. - SPEC.md: previewnet endpoint table (pinned by the genesis-table test for every preset), register-name section, env/override wording; README: register-name, previewnet, HOST_CLI_* env vars. - Doc comments still describing usernames as People-chain Resources.Consumers reads now name the dotNS contracts on Asset Hub (regenerated Swift bindings and codegen golden follow). --- .../Sources/TrUAPIHost/truapi_platform.swift | 12 ++-- js/packages/truapi-host/src/runtime.ts | 2 +- .../tests/golden/host-callbacks.ts | 6 +- rust/crates/truapi-host-cli/README.md | 12 +++- rust/crates/truapi-host-cli/SPEC.md | 70 ++++++++++++++++--- rust/crates/truapi-host-cli/src/network.rs | 59 ++++++++++------ rust/crates/truapi-platform/src/lib.rs | 6 +- .../src/host_logic/attestation.rs | 7 +- .../truapi-server/src/host_logic/session.rs | 6 +- .../truapi-server/src/runtime/authority.rs | 4 +- 10 files changed, 129 insertions(+), 55 deletions(-) diff --git a/ios/truapi-host/Sources/TrUAPIHost/truapi_platform.swift b/ios/truapi-host/Sources/TrUAPIHost/truapi_platform.swift index 08ecbf22d..1e1544884 100644 --- a/ios/truapi-host/Sources/TrUAPIHost/truapi_platform.swift +++ b/ios/truapi-host/Sources/TrUAPIHost/truapi_platform.swift @@ -1118,15 +1118,15 @@ public struct SessionUiInfo: Equatable, Hashable { */ public var publicKey: Bytes32 /** - * Wallet identity account id used for People-chain username lookup. + * Wallet identity account id used for the dotNS username lookup on Asset Hub. */ public var identityAccountId: Bytes32? /** - * Short username from the People-chain identity record. + * Short username from the dotNS identity record on Asset Hub. */ public var liteUsername: String? /** - * Fully qualified username from the People-chain identity record. + * Fully qualified username from the dotNS identity record on Asset Hub. */ public var fullUsername: String? @@ -1137,13 +1137,13 @@ public struct SessionUiInfo: Equatable, Hashable { * 32-byte sr25519 root public key of the active session. */publicKey: Bytes32, /** - * Wallet identity account id used for People-chain username lookup. + * Wallet identity account id used for the dotNS username lookup on Asset Hub. */identityAccountId: Bytes32?, /** - * Short username from the People-chain identity record. + * Short username from the dotNS identity record on Asset Hub. */liteUsername: String?, /** - * Fully qualified username from the People-chain identity record. + * Fully qualified username from the dotNS identity record on Asset Hub. */fullUsername: String?) { self.publicKey = publicKey self.identityAccountId = identityAccountId diff --git a/js/packages/truapi-host/src/runtime.ts b/js/packages/truapi-host/src/runtime.ts index ec093c05f..e4ca7abe6 100644 --- a/js/packages/truapi-host/src/runtime.ts +++ b/js/packages/truapi-host/src/runtime.ts @@ -77,7 +77,7 @@ export interface ProductRuntimeConfig { /** Platform or operating-system version. */ version?: string; }; - /** People-chain configuration used for identity lookup. */ + /** People-chain configuration used for statement-store SSO. */ people: { /** People-chain genesis hash. */ genesisHash: string | Uint8Array; diff --git a/rust/crates/truapi-codegen/tests/golden/host-callbacks.ts b/rust/crates/truapi-codegen/tests/golden/host-callbacks.ts index ca8b8941b..c930a6e96 100644 --- a/rust/crates/truapi-codegen/tests/golden/host-callbacks.ts +++ b/rust/crates/truapi-codegen/tests/golden/host-callbacks.ts @@ -334,17 +334,17 @@ export interface SessionUiInfo { publicKey: Bytes32; /** - * Wallet identity account id used for People-chain username lookup. + * Wallet identity account id used for the dotNS username lookup on Asset Hub. */ identityAccountId?: Bytes32; /** - * Short username from the People-chain identity record. + * Short username from the dotNS identity record on Asset Hub. */ liteUsername?: string; /** - * Fully qualified username from the People-chain identity record. + * Fully qualified username from the dotNS identity record on Asset Hub. */ fullUsername?: string; } diff --git a/rust/crates/truapi-host-cli/README.md b/rust/crates/truapi-host-cli/README.md index 8933d8152..7b6782445 100644 --- a/rust/crates/truapi-host-cli/README.md +++ b/rust/crates/truapi-host-cli/README.md @@ -21,7 +21,8 @@ One binary, `truapi-host`: | --- | --- | | `pairing-host` | Seedless host: serves product frames, emits pairing deeplinks, and can run product scripts. | | `signing-host` | Wallet-local host: owns signer identity, can run product scripts, accepts pairing deeplinks, registers statement allowance on-chain, signs. | -| `identity-check` | Probe the root and canonical `uid.dot` identity account for a registered username. | +| `identity-check` | Probe the root and canonical `uid.dot` identity account for a registered username (read from the dotNS contracts on Asset Hub). | +| `register-name` | Register a full-person username via `DotnsGateway.register_name` on Asset Hub, linked to a lite username or standalone with a chat key. | | `alloc-check` | Diagnose (or `--submit`) on-chain statement-store allowance: ring membership, chosen slot, and the `set_statement_store_account` extrinsic. On a full period it prints each occupied slot's age and which one would be replaced. | | `pgas-check` | Diagnose (or `--submit`) an Asset Hub PGAS allowance claim: ring membership on People, whether Asset Hub has imported that ring revision, the day's first unclaimed slot, and the `Pgas.claim_pgas` extrinsic. | @@ -406,10 +407,15 @@ HOST_CLI_SIGNER_MNEMONIC="spin battle …" truapi-host signing-host --deeplink ' truapi-host alloc-check --mnemonic "spin battle …" --lookback 100 ``` -Both hosts take `--network` (default `paseo-next-v2`). The network preset owns +Both hosts take `--network` (default `paseo-next-v2`; `previewnet` is the +other preset). The network preset owns the identity backend URL, the People, Bulletin and Asset Hub RPCs, and their genesis hashes; there is -no public `--statement-store` flag. Both also accept `--frame-listen

` +no public `--statement-store` flag. `HOST_CLI_IDENTITY_BACKEND_BASE` swaps only +the identity backend (for a local one); `HOST_CLI_IDENTITY_BACKEND_TOKEN` +supplies its bearer token instead of the CLI minting one; and +`HOST_CLI_DOTNS_POP_CONTROLLER` overrides on-chain `DotnsPopController` +discovery (see SPEC.md §21). Both also accept `--frame-listen
` to opt into a TCP product-frame WebSocket; without it, the CLI creates and cleans up a unique temporary Unix socket. diff --git a/rust/crates/truapi-host-cli/SPEC.md b/rust/crates/truapi-host-cli/SPEC.md index 75791beee..8368698c1 100644 --- a/rust/crates/truapi-host-cli/SPEC.md +++ b/rust/crates/truapi-host-cli/SPEC.md @@ -1110,9 +1110,13 @@ selection files. ## 14. Network and transport -### 14.1 Network preset +### 14.1 Network presets -v0.1 supports only `paseo-next-v2`. +Two presets are selectable with `--network`: `paseo-next-v2` (default) and +`previewnet`. Every preset is a test network; the account store keeps BIP-39 +entropy for disposable test identities only. + +`paseo-next-v2`: | Purpose | Value | | --- | --- | @@ -1124,7 +1128,20 @@ v0.1 supports only `paseo-next-v2`. | Asset Hub RPC | `wss://paseo-asset-hub-next-rpc.polkadot.io` | | Asset Hub genesis | `0x23e730eb1c6fecae09c917439a5038cb6122d0d48980e8b9bbf0ff56f94a2ca6` | -There are no public endpoint override flags. +`previewnet`: + +| Purpose | Value | +| --- | --- | +| Identity backend | `https://polkadot-app-stg.parity.io/api/v1` | +| People RPC | `wss://previewnet.substrate.dev/people` | +| People genesis | `0x3138c6d4ce58c760047a413c2a930e919b4673a841ab4890de59aac3bd037f3d` | +| Bulletin RPC | `wss://previewnet.substrate.dev/bulletin` | +| Bulletin genesis | `0x2778b1c94c4362e49a54be57d3056bc714f3712e4486625312704ffb74eb973d` | +| Asset Hub RPC | `wss://previewnet.substrate.dev/asset-hub` | +| Asset Hub genesis | `0x4d11c803cc6921429e3876638977ad006ea1bba8cd3976a0bca2f164e7026210` | + +There are no public endpoint override flags. `HOST_CLI_IDENTITY_BACKEND_BASE` +replaces only the identity backend base URL (§21). Every role the preset serves — People, Bulletin and Asset Hub — is always routed, because host internals require all three: statement-store traffic addressed to the @@ -1132,7 +1149,7 @@ People genesis, preimage submission, and PGAS claims plus dotNS username reads respectively. The SSO sentinel is a separate case — it is an unmapped genesis and reaches People through the fallback below, not through People's own route. `E2E_LIVE_CHAIN=1` only widens routing to endpoints the -preset carries without serving them as a role, of which `paseo-next-v2` has none. +preset carries without serving them as a role, of which neither preset has any. The all-zero SSO sentinel and every genesis hash not present in the active route map fall back to the People RPC. @@ -1464,7 +1481,44 @@ IDENTITY_ERROR path= account= error= Per-path RPC errors are printed and do not make the command itself fail. Mnemonic parsing failures do fail the command. The mnemonic is not persisted. -### 19.2 `alloc-check` +### 19.2 `register-name` + +```text +truapi-host register-name \ + --mnemonic \ + --label \ + [--network paseo-next-v2] \ + [--link-lite | --chat-key <65-byte-hex>] +``` + +Registers `label` as the full-person username of the mnemonic's RFC-0022 +`uid.dot` identity account, through `DotnsGateway.register_name` on Asset Hub. +The account must be a recognized full person: its ring-VRF key must be built +into a People-collection ring root on People, and Asset Hub's +`members-subscriber` must already hold that root revision (the command waits for +it). The People ring, its members and its root revision are read at one +finalized People block. + +`--link-lite` links the new name to a dotted lite username, inheriting that +name's chat key; without it and without `--chat-key`, the account's own lite +username (from dotNS) is linked. `--chat-key` registers standalone with the +given ECDH key. The two are mutually exclusive. + +The transaction is a General (v5) extrinsic authorized by the `AsDotnsGateway` +extension: `RegisterFullName { proof, ring_index, revision, signature }`, where +`signature` is the account's sr25519 signature over the inherited-implication +digest and `proof` the ring-VRF membership proof, built for the `revision` read +above. `RestrictOrigins` carries `true`. Success prints: + +```text +REGISTER_SUBMITTED label=pc_7XT69z( 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 z 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 From 2873ff9d14eacf418e05cbee72bd0a1ffec39e45 Mon Sep 17 00:00:00 2001 From: Filip Baciu Date: Tue, 18 Aug 2026 16:08:43 +0300 Subject: [PATCH 14/19] fix(server,cli): budget the in-core lookup, warn on truncated stores, harden the CLI reads Review of #426 by sphamjoli. - The in-core dotNS lookup has a 45 s budget for the whole walk (best block, discovery, contract views, label pages) and 10 s per step, instead of 10 s for everything. - resolve_labels warns when the last label page read was still full, so a name past the paged range shows in the logs rather than as "no username". - Backend access tokens are cached per backend base, so two backends in one process never share a token. - AssetHubReader errors on AccountAlias / LiteLabelOwner values of the wrong length instead of treating them as absent, and resolves the controller once per reader. --- .../crates/truapi-host-cli/src/attestation.rs | 40 +++++++++++++------ rust/crates/truapi-host-cli/src/dotns_read.rs | 38 +++++++++++++++--- .../src/host_logic/dotns_gateway.rs | 8 ++++ .../truapi-server/src/runtime/identity.rs | 17 +++++--- 4 files changed, 80 insertions(+), 23 deletions(-) diff --git a/rust/crates/truapi-host-cli/src/attestation.rs b/rust/crates/truapi-host-cli/src/attestation.rs index 10eff7a13..a6665ea1a 100644 --- a/rust/crates/truapi-host-cli/src/attestation.rs +++ b/rust/crates/truapi-host-cli/src/attestation.rs @@ -17,7 +17,7 @@ use base64::engine::general_purpose::STANDARD as BASE64; use bip39::Mnemonic; use serde_json::{Value, json}; use sha2::{Digest as _, Sha256}; -use tokio::sync::OnceCell; +use std::sync::Mutex; use tracing::{debug, warn}; use truapi_server::host_logic::attestation::build_lite_registration; use truapi_server::host_logic::dotns_gateway::{RESERVED_LABEL_LEN, is_reservable_base_label}; @@ -33,8 +33,9 @@ use crate::dotns_read::AssetHubReader; /// 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(); +/// Access tokens for the username routes, minted once per backend base and +/// process. Keyed by base so two backends in one process never share a token. +static BACKEND_TOKENS: Mutex> = Mutex::new(Vec::new()); /// Bearer token for the identity backend's username routes. /// @@ -44,15 +45,30 @@ static BACKEND_TOKEN: OnceCell = OnceCell::const_new(); /// 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> { - BACKEND_TOKEN - .get_or_try_init(|| async { - match std::env::var(IDENTITY_BACKEND_TOKEN_ENV) { - Ok(token) if !token.trim().is_empty() => Ok(token.trim().to_string()), - _ => mint_backend_token(client, backend_base).await, - } - }) - .await - .map(String::as_str) + if let Ok(token) = std::env::var(IDENTITY_BACKEND_TOKEN_ENV) + && !token.trim().is_empty() + { + return Ok(Box::leak(token.trim().to_string().into_boxed_str())); + } + let cached = BACKEND_TOKENS + .lock() + .expect("backend token cache mutex poisoned") + .iter() + .find(|(base, _)| base == backend_base) + .map(|(_, token)| *token); + if let Some(token) = cached { + return Ok(token); + } + let token: &'static str = Box::leak( + mint_backend_token(client, backend_base) + .await? + .into_boxed_str(), + ); + BACKEND_TOKENS + .lock() + .expect("backend token cache mutex poisoned") + .push((backend_base.to_string(), token)); + Ok(token) } /// Runs the backend's auth handshake and returns its access JWT. diff --git a/rust/crates/truapi-host-cli/src/dotns_read.rs b/rust/crates/truapi-host-cli/src/dotns_read.rs index 3a6af4b9d..7a99f889a 100644 --- a/rust/crates/truapi-host-cli/src/dotns_read.rs +++ b/rust/crates/truapi-host-cli/src/dotns_read.rs @@ -28,6 +28,8 @@ 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, + /// `DotnsPopController` once resolved; it does not change within a run. + pop_controller: Option<[u8; 20]>, } impl AssetHubReader { @@ -36,7 +38,10 @@ impl AssetHubReader { let rpc = RpcClient::from_insecure_url(asset_hub_ws) .await .with_context(|| format!("connect {asset_hub_ws}"))?; - Ok(Self { rpc }) + Ok(Self { + rpc, + pop_controller: None, + }) } /// Asset Hub chain time in Unix seconds. `Timestamp.Now` itself is @@ -56,7 +61,7 @@ impl AssetHubReader { /// 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())) + account_id_value("DotnsGateway.AccountAlias", value) } /// Account that reserved the dotted lite username `lite_label` through the @@ -65,7 +70,7 @@ impl AssetHubReader { let value = self .raw_storage(&lite_label_owner_key(lite_label.as_bytes())) .await?; - Ok(value.and_then(|bytes| bytes.try_into().ok())) + account_id_value("DotnsGateway.LiteLabelOwner", value) } /// Usernames of `account` as recorded by the dotNS contracts. @@ -86,9 +91,19 @@ impl AssetHubReader { .map_err(anyhow::Error::msg) } - /// `DotnsPopController` address. The env override wins, otherwise on-chain - /// discovery through the dispatcher's `TARGET()` getter. + /// `DotnsPopController` address, resolved once per reader. The env override + /// wins, otherwise on-chain discovery through the dispatcher's `TARGET()` + /// getter. async fn pop_controller(&mut self) -> Result<[u8; 20]> { + if let Some(controller) = self.pop_controller { + return Ok(controller); + } + let controller = self.resolve_pop_controller().await?; + self.pop_controller = Some(controller); + Ok(controller) + } + + async fn resolve_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() { @@ -161,6 +176,19 @@ impl AssetHubReader { } } +/// Decodes an optional 32-byte account id storage value. A value of another +/// length is an error, not "absent": treating it as absent would let a +/// registration proceed that the gateway then rejects. +fn account_id_value(entry: &str, value: Option>) -> Result> { + value + .map(|bytes| { + let len = bytes.len(); + <[u8; 32]>::try_from(bytes) + .map_err(|_| anyhow::anyhow!("{entry} value is {len} bytes, expected 32")) + }) + .transpose() +} + #[async_trait] impl DotnsTransport for AssetHubReader { async fn storage(&mut self, key: Vec) -> Result>, String> { diff --git a/rust/crates/truapi-server/src/host_logic/dotns_gateway.rs b/rust/crates/truapi-server/src/host_logic/dotns_gateway.rs index 52f4435d4..de85a05d6 100644 --- a/rust/crates/truapi-server/src/host_logic/dotns_gateway.rs +++ b/rust/crates/truapi-server/src/host_logic/dotns_gateway.rs @@ -17,6 +17,7 @@ use parity_scale_codec::{Compact, Decode, Encode}; use sp_crypto_hashing::{blake2_128, blake2_256, keccak_256, twox_128}; use thiserror::Error; +use tracing::warn; /// Ring-VRF context for `register_name` proofs. /// @@ -643,6 +644,13 @@ pub async fn resolve_labels( if short_page { break; } + if page + 1 == LABEL_PAGE_MAX { + warn!( + store = %hex::encode(store), + read = LABEL_PAGE_MAX * LABEL_PAGE_LIMIT, + "LabelStore holds more labels than the pages read; a name past this point is not resolved" + ); + } } Ok(labels) } diff --git a/rust/crates/truapi-server/src/runtime/identity.rs b/rust/crates/truapi-server/src/runtime/identity.rs index 6202de33a..865f3d1e9 100644 --- a/rust/crates/truapi-server/src/runtime/identity.rs +++ b/rust/crates/truapi-server/src/runtime/identity.rs @@ -35,8 +35,13 @@ use truapi::latest::{ StorageQueryType, }; -/// Budget for the whole Asset Hub lookup: best block, storage, contract views. -const LOOKUP_TIMEOUT: Duration = Duration::from_secs(10); +/// Budget for the whole Asset Hub lookup: best block, controller discovery, the +/// contract walk and the label pages of a large store, well over a dozen +/// round trips on one follow. +const LOOKUP_BUDGET: Duration = Duration::from_secs(45); +/// Budget for one step of it: opening the follow, one storage read, one +/// contract view. A step that stalls this long is not going to answer. +const OPERATION_TIMEOUT: Duration = Duration::from_secs(10); const BEST_BLOCK_TIMEOUT: Duration = Duration::from_secs(2); /// Monotonic salt for local identity lookup follow ids. It keeps concurrent @@ -147,7 +152,7 @@ async fn lookup_dotns_identity( asset_hub_chain_genesis_hash: [u8; 32], account_id: [u8; 32], ) -> Result, String> { - let timeout = futures_timer::Delay::new(LOOKUP_TIMEOUT).fuse(); + let timeout = futures_timer::Delay::new(LOOKUP_BUDGET).fuse(); pin_mut!(timeout); let lookup = async { let mut lookup = @@ -200,7 +205,7 @@ impl<'a> DotnsLookup<'a> { let hash = wait_for_chain_head_best_hash( &mut follow, "Asset Hub", - LOOKUP_TIMEOUT, + OPERATION_TIMEOUT, BEST_BLOCK_TIMEOUT, ) .await?; @@ -242,7 +247,7 @@ impl DotnsTransport for DotnsLookup<'_> { operation_id: &operation_id, key: &key, label: "Asset Hub", - timeout: LOOKUP_TIMEOUT, + timeout: OPERATION_TIMEOUT, }, ) .await?; @@ -278,7 +283,7 @@ impl DotnsTransport for DotnsLookup<'_> { &mut self.follow, &operation_id, "Asset Hub", - LOOKUP_TIMEOUT, + OPERATION_TIMEOUT, ) .await .map_err(DotnsViewError::Failed)?; From fcb3349284c5a2413abb7416268337d0795a0ece Mon Sep 17 00:00:00 2001 From: Filip Baciu Date: Wed, 19 Aug 2026 10:52:56 +0300 Subject: [PATCH 15/19] fix(server,cli): drop lapsed pending claims; reserved names follow the gateway's rule Review of #426 by re-gius. - A pending claim older than the controller's reservationDuration is lapsed (claimLabelStore skips it, expirePendingClaim sweeps it), so resolve_labels reads reservationDuration() and Timestamp.Now at the pinned block and drops such entries instead of reporting them as usernames until swept. - The gateway reserves through PopRules.reserveBaseNameForPop, which has no upper length: a reserved base name is any registrable full-person label (letters only, six to thirty-two bytes). --reserved-username and the attest pre-flight use that rule. - classify_labels documents the first-letters-only-label heuristic for the full username and the DotnsPopController.usernameNodeOf(address) endpoint that should replace it. --- rust/crates/truapi-host-cli/SPEC.md | 2 +- .../crates/truapi-host-cli/src/attestation.rs | 11 +- rust/crates/truapi-host-cli/src/main.rs | 10 +- .../src/host_logic/dotns_gateway.rs | 135 ++++++++++++------ .../truapi-server/src/runtime/identity.rs | 68 ++++++--- 5 files changed, 158 insertions(+), 68 deletions(-) diff --git a/rust/crates/truapi-host-cli/SPEC.md b/rust/crates/truapi-host-cli/SPEC.md index 58762e25c..48ce34913 100644 --- a/rust/crates/truapi-host-cli/SPEC.md +++ b/rust/crates/truapi-host-cli/SPEC.md @@ -289,7 +289,7 @@ startup: - `--account` with `--session`; - `--account` with `--lite-username-prefix` or `--reserved-username`; and - a `--reserved-username` that is not a full-person base label (lowercase ASCII - letters only, at most 32 bytes). + letters only, 6 to 32 bytes). The same conflicts apply when the mnemonic came from `HOST_CLI_SIGNER_MNEMONIC`. diff --git a/rust/crates/truapi-host-cli/src/attestation.rs b/rust/crates/truapi-host-cli/src/attestation.rs index a6665ea1a..8b08b7150 100644 --- a/rust/crates/truapi-host-cli/src/attestation.rs +++ b/rust/crates/truapi-host-cli/src/attestation.rs @@ -20,7 +20,9 @@ use sha2::{Digest as _, Sha256}; use std::sync::Mutex; use tracing::{debug, warn}; use truapi_server::host_logic::attestation::build_lite_registration; -use truapi_server::host_logic::dotns_gateway::{RESERVED_LABEL_LEN, is_reservable_base_label}; +use truapi_server::host_logic::dotns_gateway::{ + MAX_BASE_LABEL_LEN, MIN_PERSON_LABEL_LEN, is_registrable_full_label, +}; use truapi_server::host_logic::product_account::{ SR25519_SIGNING_CONTEXT, derive_identity_keypair, derive_root_keypair_from_entropy, product_public_key_to_address, @@ -214,12 +216,11 @@ pub async fn attest(config: &AttestConfig) -> Result { // reservation window; neither the backend nor the gateway checks it. Ask // the registrar first. if let Some(reserved) = config.reserved_username.as_deref() { - if !is_reservable_base_label(reserved) { + if !is_registrable_full_label(reserved) { bail!( "reserved username {reserved:?} is not a reservable base label (lowercase ASCII \ - letters only, {} to {} bytes); the gateway would reject the whole attestation", - RESERVED_LABEL_LEN.start(), - RESERVED_LABEL_LEN.end() + letters only, {MIN_PERSON_LABEL_LEN} to {MAX_BASE_LABEL_LEN} bytes); the \ + gateway would reject the whole attestation" ); } if !reader.label_available(reserved).await? { diff --git a/rust/crates/truapi-host-cli/src/main.rs b/rust/crates/truapi-host-cli/src/main.rs index 1abbcc30d..41e4f2927 100644 --- a/rust/crates/truapi-host-cli/src/main.rs +++ b/rust/crates/truapi-host-cli/src/main.rs @@ -40,7 +40,9 @@ use tracing_subscriber::Layer; use tracing_subscriber::layer::SubscriberExt; use tracing_subscriber::util::SubscriberInitExt; use truapi_platform::{HostInfo, PlatformInfo}; -use truapi_server::host_logic::dotns_gateway::{RESERVED_LABEL_LEN, is_reservable_base_label}; +use truapi_server::host_logic::dotns_gateway::{ + MAX_BASE_LABEL_LEN, MIN_PERSON_LABEL_LEN, is_registrable_full_label, +}; use truapi_server::statement_allowance as alloc; use truapi_server::subscription::Spawner; use truapi_server::{ @@ -1275,13 +1277,11 @@ fn validate_signing_args(args: &SigningHostArgs) -> Result<()> { bail!("--reserved-username only applies when --account is omitted"); } if let Some(reserved) = &reserved - && !is_reservable_base_label(reserved) + && !is_registrable_full_label(reserved) { bail!( "--reserved-username {reserved:?} is not a reservable base label: lowercase ASCII \ - letters only, {} to {} bytes", - RESERVED_LABEL_LEN.start(), - RESERVED_LABEL_LEN.end() + letters only, {MIN_PERSON_LABEL_LEN} to {MAX_BASE_LABEL_LEN} bytes" ); } Ok(()) diff --git a/rust/crates/truapi-server/src/host_logic/dotns_gateway.rs b/rust/crates/truapi-server/src/host_logic/dotns_gateway.rs index de85a05d6..94b9314a5 100644 --- a/rust/crates/truapi-server/src/host_logic/dotns_gateway.rs +++ b/rust/crates/truapi-server/src/host_logic/dotns_gateway.rs @@ -308,18 +308,7 @@ fn word(data: &[u8], index: usize) -> Result<&[u8], DotnsContractError> { /// 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", - }); - } - let value = u64::from_be_bytes( - bytes[24..] - .try_into() - .expect("8-byte tail of a 32-byte word; qed"), - ); - usize::try_from(value).map_err(|_| DotnsContractError::Abi { + usize::try_from(word_u64(data, index)?).map_err(|_| DotnsContractError::Abi { context: "oversized word", }) } @@ -372,8 +361,8 @@ pub fn decode_string_array(data: &[u8]) -> Result, DotnsContractErro } /// Decodes `DotnsPopController.pendingClaims(address)`, an ABI -/// `(string label, uint64 mintedAt)[]`. Returns the labels. -pub fn decode_pending_claims_array(data: &[u8]) -> Result, DotnsContractError> { +/// `(string label, uint64 mintedAt)[]`, as `(label, minted_at)` pairs. +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", @@ -390,11 +379,33 @@ pub fn decode_pending_claims_array(data: &[u8]) -> Result, DotnsCont .ok_or(DotnsContractError::Abi { context: "claim offset", })?; - decode_string_at(claim, word_usize(claim, 0)?) + let label = decode_string_at(claim, word_usize(claim, 0)?)?; + let minted_at = word_u64(claim, 1)?; + Ok((label, minted_at)) }) .collect() } +/// Decodes a single `uint64` (or narrower) return value. +pub fn decode_u64(data: &[u8]) -> Result { + word_u64(data, 0) +} + +/// Reads the 32-byte ABI word at `index` as a `u64`; wider values are an error. +fn word_u64(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"), + )) +} + /// Usernames of one account as recorded by the dotNS contracts. #[derive(Debug, Clone, Default, PartialEq, Eq)] pub struct DotnsIdentity { @@ -417,6 +428,12 @@ pub struct DotnsIdentity { /// Lite and public names share one namespace on the contract side, so a public /// name ending in two digits is indistinguishable from a flattened lite name /// here; the contract documents that ambiguity as accepted. +/// +/// TODO(dotns): the full username is the first letters-only label, which a +/// self-registered or purchased name older than the gateway name can win. +/// `chatKey(node)` proves gateway provenance but survives transfers, so it is +/// not a filter either. Once `DotnsPopController` exposes +/// `usernameNodeOf(address)`, read the full username from it instead. pub fn classify_labels(labels: I) -> DotnsIdentity where I: IntoIterator, @@ -467,24 +484,17 @@ pub fn is_full_person_label(label: &str) -> bool { && label.chars().all(|c| c.is_ascii_lowercase()) } -/// Shortest base a person can register: `PopRules` classifies bases of five -/// characters or fewer as reserved for governance. +/// Shortest base a person can register or reserve: `PopRules` classifies bases +/// of five characters or fewer as reserved for governance. pub const MIN_PERSON_LABEL_LEN: usize = 6; -/// Lengths `PopRules.reserveBaseName` accepts for a reserved base name. -pub const RESERVED_LABEL_LEN: core::ops::RangeInclusive = 6..=8; -/// Whether `label` can be registered as a full-person username: the pallet's -/// [`is_full_person_label`] plus the `PopRules` length tier. +/// Whether `label` can be registered as a full-person username, or reserved +/// for a later claim through the gateway (`PopRules.reserveBaseNameForPop`): +/// the pallet's [`is_full_person_label`] plus the governance-reserved tier. pub fn is_registrable_full_label(label: &str) -> bool { is_full_person_label(label) && label.len() >= MIN_PERSON_LABEL_LEN } -/// Whether `label` can be reserved for a later full-person claim: the pallet's -/// [`is_full_person_label`] plus the `PopRules` reservation length band. -pub fn is_reservable_base_label(label: &str) -> bool { - is_full_person_label(label) && RESERVED_LABEL_LEN.contains(&label.len()) -} - /// Whether `value` is a dotted lite username, `stem.NN`: a DNS-label stem, one /// dot, exactly two digits (`StringUtils.isSingleDotLiteLabel`), and at most /// [`MAX_BASE_LABEL_LEN`] bytes once flattened. @@ -575,7 +585,9 @@ pub async fn discover_pop_controller( /// /// Two sources are merged. The controller's pending claims hold gateway-minted /// names the user has not settled into a `LabelStore` yet (`claimLabelStore`); -/// those come first. The user's `LabelStore`, when deployed, holds every name +/// those come first. A claim older than the controller's `reservationDuration` +/// is lapsed — `claimLabelStore` skips it and `expirePendingClaim` will sweep +/// it — so it is not a username here either, whether or not it has been swept. The user's `LabelStore`, when deployed, holds every name /// written for them: gateway names once settled, plus public registrations and /// incoming transfers. Store labels carry the network TLD (`alice01.paseo`), /// which is stripped here; subnames (`app.alice`) are dropped. @@ -746,7 +758,9 @@ pub async fn label_available( } /// Gateway-minted labels of `user` still waiting for `claimLabelStore`, from -/// `DotnsPopController.pendingClaims(address)`. +/// `DotnsPopController.pendingClaims(address)`, without the entries that have +/// lapsed (`mintedAt + reservationDuration < now`, the controller's own +/// `_isExpired`; `now` is `Timestamp.Now` at the pinned block). async fn pending_claim_labels( transport: &mut T, controller: &[u8; 20], @@ -756,8 +770,42 @@ async fn pending_claim_labels( .view(controller, call_address("pendingClaims(address)", user)) .await .map_err(|err| format!("DotnsPopController.pendingClaims: {err}"))?; - decode_pending_claims_array(&output) - .map_err(|err| format!("DotnsPopController.pendingClaims: {err}")) + let claims = decode_pending_claims_array(&output) + .map_err(|err| format!("DotnsPopController.pendingClaims: {err}"))?; + if claims.is_empty() { + return Ok(Vec::new()); + } + let duration_output = transport + .view(controller, call_no_args("reservationDuration()")) + .await + .map_err(|err| format!("DotnsPopController.reservationDuration: {err}"))?; + let duration = decode_u64(&duration_output) + .map_err(|err| format!("DotnsPopController.reservationDuration: {err}"))?; + let now = chain_time_secs(transport).await?; + Ok(claims + .into_iter() + .filter(|(_, minted_at)| !claim_lapsed(*minted_at, duration, now)) + .map(|(label, _)| label) + .collect()) +} + +/// Whether a pending claim minted at `minted_at` has lapsed at chain time +/// `now`, mirroring `DotnsPopController._isExpired`. +fn claim_lapsed(minted_at: u64, duration: u64, now: u64) -> bool { + minted_at.saturating_add(duration) < now +} + +/// Asset Hub chain time in Unix seconds, from `Timestamp.Now` (milliseconds). +async fn chain_time_secs(transport: &mut T) -> Result { + let value = transport + .storage(timestamp_now_key()) + .await? + .ok_or("Timestamp.Now is unset")?; + let millis: [u8; 8] = value + .as_slice() + .try_into() + .map_err(|_| "Timestamp.Now is not a u64".to_string())?; + Ok(u64::from_le_bytes(millis) / 1000) } /// `DotnsGateway.DispatcherAddress` storage key. @@ -1053,12 +1101,22 @@ mod tests { claims.extend_from_slice(&struct_b); assert_eq!( decode_pending_claims_array(&claims).unwrap(), - vec!["alice01".to_string(), "bob".to_string()] + vec![("alice01".to_string(), 42), ("bob".to_string(), 7)] ); assert_eq!( decode_pending_claims_array(&[abi_word(0x20), abi_word(0)].concat()).unwrap(), - Vec::::new() + Vec::<(String, u64)>::new() ); + assert_eq!(decode_u64(&abi_word(604_800)).unwrap(), 604_800); + assert!(decode_u64(&[0xff; 32]).is_err()); + } + + #[test] + fn a_pending_claim_lapses_after_the_reservation_duration() { + // DotnsPopController._isExpired: mintedAt + reservationDuration < now. + assert!(!claim_lapsed(1_000, 100, 1_100)); + assert!(claim_lapsed(1_000, 100, 1_101)); + assert!(!claim_lapsed(u64::MAX, 100, u64::MAX)); } #[test] @@ -1100,15 +1158,12 @@ mod tests { assert!(!is_full_person_label("Alice")); assert!(!is_full_person_label("")); - // PopRules tiers: five letters or fewer are governance-reserved; a - // reservation is six to eight. - assert!(is_registrable_full_label("georgex")); + // PopRules: five letters or fewer are governance-reserved; longer + // bases can be registered and reserved alike. + assert!(is_registrable_full_label("george")); + assert!(is_registrable_full_label("georgeabc")); assert!(is_registrable_full_label(&"a".repeat(32))); assert!(!is_registrable_full_label("alice")); - assert!(is_reservable_base_label("george")); - assert!(is_reservable_base_label("georgeab")); - assert!(!is_reservable_base_label("georg")); - assert!(!is_reservable_base_label("georgeabc")); assert!(!is_full_person_label("alice.bc")); assert!(!is_full_person_label("-alice")); assert!(!is_full_person_label(&"a".repeat(33))); diff --git a/rust/crates/truapi-server/src/runtime/identity.rs b/rust/crates/truapi-server/src/runtime/identity.rs index 865f3d1e9..ee04d711a 100644 --- a/rust/crates/truapi-server/src/runtime/identity.rs +++ b/rust/crates/truapi-server/src/runtime/identity.rs @@ -311,7 +311,9 @@ mod tests { use super::*; use crate::chain_runtime::{RuntimeChainProvider, RuntimeFailure}; - use crate::host_logic::dotns_gateway::{account_to_h160, dispatcher_address_key, selector}; + use crate::host_logic::dotns_gateway::{ + account_to_h160, dispatcher_address_key, selector, timestamp_now_key, + }; use crate::subscription::thread_per_subscription_spawner; use async_trait::async_trait; use futures::StreamExt; @@ -371,19 +373,37 @@ mod tests { out } - /// A `(string label, uint64 mintedAt)[]` return value with one element. - fn abi_pending_claims(label: &str, minted_at: u64) -> Vec { - [ - abi_word(32).to_vec(), - abi_word(1).to_vec(), - abi_word(32).to_vec(), - abi_word(64).to_vec(), - abi_word(minted_at).to_vec(), - abi_string_tail(label), - ] - .concat() + /// A `(string label, uint64 mintedAt)[]` return value. + fn abi_pending_claims(claims: &[(&str, u64)]) -> Vec { + let structs: Vec> = claims + .iter() + .map(|(label, minted_at)| { + [ + abi_word(64).to_vec(), + abi_word(*minted_at).to_vec(), + abi_string_tail(label), + ] + .concat() + }) + .collect(); + let mut out = abi_word(32).to_vec(); + out.extend_from_slice(&abi_word(claims.len() as u64)); + let mut offset = 32 * claims.len(); + for element in &structs { + out.extend_from_slice(&abi_word(offset as u64)); + offset += element.len(); + } + for element in structs { + out.extend_from_slice(&element); + } + out } + /// Chain time the scripted `Timestamp.Now` reports, in seconds. + const NOW_SECS: u64 = 1_800_000_000; + /// The scripted controller's `reservationDuration()`. + const RESERVATION_DURATION: u64 = 604_800; + /// `ReviveApi_call` output carrying successful return `data`. fn contract_result(data: &[u8]) -> Vec { let mut out = Vec::new(); @@ -408,7 +428,14 @@ mod tests { (CONTROLLER, s) if s == selector("pendingClaims(address)") => { // The user argument is the mapped H160 of the identity account. assert_eq!(&input[16..36], account_to_h160(&ACCOUNT)); - abi_pending_claims("alice01", 42) + // One live claim and one that lapsed a second ago. + abi_pending_claims(&[ + ("alice01", NOW_SECS - 10), + ("stale01", NOW_SECS - RESERVATION_DURATION - 1), + ]) + } + (CONTROLLER, s) if s == selector("reservationDuration()") => { + abi_word(RESERVATION_DURATION).to_vec() } (CONTROLLER, s) if s == selector("protocolRegistry()") => abi_address(®ISTRY), (REGISTRY, s) if s == selector("get(bytes32)") => abi_address(&FACTORY), @@ -499,6 +526,13 @@ mod tests { let mut frames = vec![response( json!({"result": "started", "operationId": operation_id}), )]; + if key_bytes == timestamp_now_key() { + frames.push(follow_event(json!({ + "event": "operationStorageItems", + "operationId": operation_id, + "items": [{"key": key, "value": format!("0x{}", hex::encode((NOW_SECS * 1_000).to_le_bytes()))}] + }))); + } if key_bytes == dispatcher_address_key() { frames.push(follow_event(json!({ "event": "operationStorageItems", @@ -626,11 +660,11 @@ mod tests { .iter() .filter(|request| request.contains("chainHead_v1_call")) .count(); - // TARGET, pendingClaims, protocolRegistry, get(storeFactory), - // getLabelStore, tld, one short getLabels page. + // TARGET, pendingClaims, reservationDuration, protocolRegistry, + // get(storeFactory), getLabelStore, tld, one short getLabels page. assert_eq!( - calls, 7, - "the discovery and label chain is exactly seven views" + calls, 8, + "the discovery and label chain is exactly eight views" ); } } From b987b9b1e007167f7dd5a7e6e325c3a8a218fd90 Mon Sep 17 00:00:00 2001 From: Filip Baciu Date: Wed, 19 Aug 2026 13:41:15 +0300 Subject: [PATCH 16/19] fix(server): pass the Asset Hub genesis in the mobile-host test configs SigningHostConfig::new takes the Asset Hub genesis; the mobile-host SSO tests from #433 construct it with one. --- rust/crates/truapi-server/src/host_core.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rust/crates/truapi-server/src/host_core.rs b/rust/crates/truapi-server/src/host_core.rs index fcd33d75e..eebe8cfe6 100644 --- a/rust/crates/truapi-server/src/host_core.rs +++ b/rust/crates/truapi-server/src/host_core.rs @@ -1262,6 +1262,7 @@ mod tests { PlatformInfo::default(), [0; 32], [0xbb; 32], + [0xcc; 32], ) .expect("signing host config is valid"); let runtime = @@ -1307,6 +1308,7 @@ mod tests { PlatformInfo::default(), [0; 32], [0xbb; 32], + [0xcc; 32], ) .expect("signing host config is valid"); let runtime = From 097ec3b12c7f376d9363946c69ca1a3b2b501006 Mon Sep 17 00:00:00 2001 From: Filip Baciu Date: Thu, 20 Aug 2026 10:49:46 +0300 Subject: [PATCH 17/19] fix(server,cli,platform): address the #426 review by TarikGul - hosts/dotli passes runtimeConfig.assetHub (dotli-community#180); the submodule points at that commit, so make dev and make e2e-dotli keep working with the required field. - The auto-linked lite username passes the same `name.NN` shape check as an explicit --link-lite before it enters the signed ring-VRF message. - The 45 s lookup budget bounds the whole username resolution of a session (identity account, root fallback, every retry); each step keeps its 10 s operation timeout, and running out of budget installs the session without a username instead of blocking activation. - The ".dot" TLD fallback is verified instead of guessed: on a tld() revert the registry must hold the record for namehash("dot") (DotnsRegistry.recordExists), or resolution errors. - Every dotNS view names its call in errors; a pendingClaims revert is the controller's answer (warn, read the store) rather than the end of the whole resolution. - SigningHostConfig carries no Asset Hub genesis: nothing reads it there, the pairing config is where it drives the dotNS lookup. The field is gone from the native records, the wasm signing-host config, and the Swift and Kotlin wrappers, so native hosts do not carry a dead required field. - VIEW_CALL_ORIGIN maps to a non-zero eth-derived address, since Solidity guards commonly reject address(0) and a revert is read as an answer. - TODO(#334) on the second Members.Members reader. --- android/truapi-host/README.md | 1 - .../kotlin/io/parity/truapi/TrUAPIHost.kt | 9 +- hosts/dotli | 2 +- ios/truapi-host/README.md | 1 - .../Sources/TrUAPIHost/TrUAPIHost.swift | 12 +- .../Sources/TrUAPIHost/truapi_server.swift | 56 +----- .../Tests/TrUAPIWsBridgeTests.swift | 3 +- rust/crates/truapi-host-cli/src/main.rs | 1 - .../truapi-host-cli/src/register_name.rs | 10 +- rust/crates/truapi-platform/src/lib.rs | 6 - rust/crates/truapi-server/src/host_core.rs | 2 - .../src/host_logic/dotns_gateway.rs | 174 ++++++++++++++---- rust/crates/truapi-server/src/native.rs | 21 --- .../truapi-server/src/runtime/identity.rs | 75 ++++---- .../truapi-server/src/runtime/signing_host.rs | 1 - .../src/runtime/signing_host/sso_responder.rs | 1 - .../src/runtime/statement_allowance/ring.rs | 4 + rust/crates/truapi-server/src/wasm.rs | 6 - rust/crates/truapi-server/src/ws_bridge.rs | 1 - 19 files changed, 209 insertions(+), 177 deletions(-) diff --git a/android/truapi-host/README.md b/android/truapi-host/README.md index 469ecb23c..8b50acc29 100644 --- a/android/truapi-host/README.md +++ b/android/truapi-host/README.md @@ -314,7 +314,6 @@ val runtimeConfig = RuntimeConfig( hostIcon = "https://host.example/icon.png", peopleChainGenesisHash = ByteArray(32), bulletinChainGenesisHash = ByteArray(32), - assetHubChainGenesisHash = ByteArray(32), // Optional: activate a local signing session from host-held BIP-39 entropy // (no SSO pairing). Omit for the QR pairing flow. localSessionSecret = null, diff --git a/android/truapi-host/src/main/kotlin/io/parity/truapi/TrUAPIHost.kt b/android/truapi-host/src/main/kotlin/io/parity/truapi/TrUAPIHost.kt index 0bb6883f9..410b61cdf 100644 --- a/android/truapi-host/src/main/kotlin/io/parity/truapi/TrUAPIHost.kt +++ b/android/truapi-host/src/main/kotlin/io/parity/truapi/TrUAPIHost.kt @@ -109,9 +109,8 @@ enum class ProductExecutionKind { * * [hostName], [hostIcon], [hostVersion], [platformType], and [platformVersion] * describe the host to the wallet during SSO pairing. - * [peopleChainGenesisHash], [bulletinChainGenesisHash] and - * [assetHubChainGenesisHash] must each be exactly 32 bytes. [localSessionSecret] - * optionally activates a local signing session + * [peopleChainGenesisHash] and [bulletinChainGenesisHash] must each be exactly + * 32 bytes. [localSessionSecret] optionally activates a local signing session * from host-held BIP-39 entropy (no SSO pairing needed). */ data class RuntimeConfig( @@ -124,7 +123,6 @@ data class RuntimeConfig( val platformVersion: String? = null, val peopleChainGenesisHash: ByteArray, val bulletinChainGenesisHash: ByteArray, - val assetHubChainGenesisHash: ByteArray, val localSessionSecret: ByteArray? = null, val localSessionLiteUsername: String? = null, val pairingDeeplinkScheme: PairingDeeplinkScheme = PairingDeeplinkScheme.POLKADOT_APP, @@ -140,7 +138,6 @@ data class RuntimeConfig( platformVersion = platformVersion, peopleChainGenesisHash = peopleChainGenesisHash, bulletinChainGenesisHash = bulletinChainGenesisHash, - assetHubChainGenesisHash = assetHubChainGenesisHash, localSessionSecret = localSessionSecret, localSessionLiteUsername = localSessionLiteUsername, pairingDeeplinkScheme = pairingDeeplinkScheme.toNative(), @@ -158,7 +155,6 @@ data class RuntimeConfig( platformVersion == other.platformVersion && peopleChainGenesisHash.contentEquals(other.peopleChainGenesisHash) && bulletinChainGenesisHash.contentEquals(other.bulletinChainGenesisHash) && - assetHubChainGenesisHash.contentEquals(other.assetHubChainGenesisHash) && // Compare nullability explicitly so null (no session) is distinct // from an empty secret (invalid input) — matching hashCode, which // hashes null to 0 and an empty array to 1. @@ -177,7 +173,6 @@ data class RuntimeConfig( result = 31 * result + (platformVersion?.hashCode() ?: 0) result = 31 * result + peopleChainGenesisHash.contentHashCode() result = 31 * result + bulletinChainGenesisHash.contentHashCode() - result = 31 * result + assetHubChainGenesisHash.contentHashCode() result = 31 * result + (localSessionSecret?.contentHashCode() ?: 0) result = 31 * result + (localSessionLiteUsername?.hashCode() ?: 0) result = 31 * result + pairingDeeplinkScheme.hashCode() diff --git a/hosts/dotli b/hosts/dotli index 29ef38b6c..e42198c60 160000 --- a/hosts/dotli +++ b/hosts/dotli @@ -1 +1 @@ -Subproject commit 29ef38b6c144b2048c17f9404878801b1492fed0 +Subproject commit e42198c607f971d77ded8c2d5cc48fd528d4e85f diff --git a/ios/truapi-host/README.md b/ios/truapi-host/README.md index dc8e02559..db399e8b8 100644 --- a/ios/truapi-host/README.md +++ b/ios/truapi-host/README.md @@ -333,7 +333,6 @@ let runtimeConfig = RuntimeConfig( hostIcon: "https://host.example/icon.png", peopleChainGenesisHash: Data(repeating: 0, count: 32), bulletinChainGenesisHash: Data(repeating: 0, count: 32), - assetHubChainGenesisHash: Data(repeating: 0, count: 32), pairingDeeplinkScheme: .polkadotApp ) let core = try TrUAPIHostCore(callbacks: callbacks, runtimeConfig: runtimeConfig) diff --git a/ios/truapi-host/Sources/TrUAPIHost/TrUAPIHost.swift b/ios/truapi-host/Sources/TrUAPIHost/TrUAPIHost.swift index f643af916..3abfa4c66 100644 --- a/ios/truapi-host/Sources/TrUAPIHost/TrUAPIHost.swift +++ b/ios/truapi-host/Sources/TrUAPIHost/TrUAPIHost.swift @@ -44,8 +44,8 @@ public enum PairingDeeplinkScheme: Sendable { /// /// `hostName`, `hostIcon`, `hostVersion`, `platformType`, and /// `platformVersion` describe the host to the wallet during SSO pairing. -/// `peopleChainGenesisHash`, `bulletinChainGenesisHash` and -/// `assetHubChainGenesisHash` must each be exactly 32 bytes. +/// `peopleChainGenesisHash` and `bulletinChainGenesisHash` must each be +/// exactly 32 bytes. public struct RuntimeConfig: Sendable { public let productId: String public let executionKind: ProductExecutionKind @@ -56,7 +56,6 @@ public struct RuntimeConfig: Sendable { public let platformVersion: String? public let peopleChainGenesisHash: Data public let bulletinChainGenesisHash: Data - public let assetHubChainGenesisHash: Data public let localSessionSecret: Data? public let localSessionLiteUsername: String? public let pairingDeeplinkScheme: PairingDeeplinkScheme @@ -71,7 +70,6 @@ public struct RuntimeConfig: Sendable { platformVersion: String? = nil, peopleChainGenesisHash: Data, bulletinChainGenesisHash: Data, - assetHubChainGenesisHash: Data, localSessionSecret: Data? = nil, localSessionLiteUsername: String? = nil, pairingDeeplinkScheme: PairingDeeplinkScheme = .polkadotApp @@ -85,7 +83,6 @@ public struct RuntimeConfig: Sendable { self.platformVersion = platformVersion self.peopleChainGenesisHash = peopleChainGenesisHash self.bulletinChainGenesisHash = bulletinChainGenesisHash - self.assetHubChainGenesisHash = assetHubChainGenesisHash self.localSessionSecret = localSessionSecret self.localSessionLiteUsername = localSessionLiteUsername self.pairingDeeplinkScheme = pairingDeeplinkScheme @@ -102,7 +99,6 @@ public struct RuntimeConfig: Sendable { platformVersion: platformVersion, peopleChainGenesisHash: peopleChainGenesisHash, bulletinChainGenesisHash: bulletinChainGenesisHash, - assetHubChainGenesisHash: assetHubChainGenesisHash, localSessionSecret: localSessionSecret, localSessionLiteUsername: localSessionLiteUsername, pairingDeeplinkScheme: pairingDeeplinkScheme.native @@ -119,7 +115,6 @@ public struct HostRuntimeConfig: Sendable, Equatable { public let platformVersion: String? public let peopleChainGenesisHash: Data public let bulletinChainGenesisHash: Data - public let assetHubChainGenesisHash: Data public let localSessionSecret: Data? public let localSessionLiteUsername: String? @@ -131,7 +126,6 @@ public struct HostRuntimeConfig: Sendable, Equatable { platformVersion: String? = nil, peopleChainGenesisHash: Data, bulletinChainGenesisHash: Data, - assetHubChainGenesisHash: Data, localSessionSecret: Data? = nil, localSessionLiteUsername: String? = nil ) { @@ -142,7 +136,6 @@ public struct HostRuntimeConfig: Sendable, Equatable { self.platformVersion = platformVersion self.peopleChainGenesisHash = peopleChainGenesisHash self.bulletinChainGenesisHash = bulletinChainGenesisHash - self.assetHubChainGenesisHash = assetHubChainGenesisHash self.localSessionSecret = localSessionSecret self.localSessionLiteUsername = localSessionLiteUsername } @@ -156,7 +149,6 @@ public struct HostRuntimeConfig: Sendable, Equatable { platformVersion: platformVersion, peopleChainGenesisHash: peopleChainGenesisHash, bulletinChainGenesisHash: bulletinChainGenesisHash, - assetHubChainGenesisHash: assetHubChainGenesisHash, localSessionSecret: localSessionSecret, localSessionLiteUsername: localSessionLiteUsername ) diff --git a/ios/truapi-host/Sources/TrUAPIHost/truapi_server.swift b/ios/truapi-host/Sources/TrUAPIHost/truapi_server.swift index c66a0132c..750111c8b 100644 --- a/ios/truapi-host/Sources/TrUAPIHost/truapi_server.swift +++ b/ios/truapi-host/Sources/TrUAPIHost/truapi_server.swift @@ -3827,10 +3827,6 @@ public struct NativeHostRuntimeConfig: Equatable, Hashable { * Bulletin-chain genesis hash. Must be exactly 32 bytes. */ public var bulletinChainGenesisHash: Data - /** - * Asset Hub genesis hash. Must be exactly 32 bytes. - */ - public var assetHubChainGenesisHash: Data /** * Optional local signing-host secret material (raw BIP-39 entropy). */ @@ -3864,9 +3860,6 @@ public struct NativeHostRuntimeConfig: Equatable, Hashable { /** * Bulletin-chain genesis hash. Must be exactly 32 bytes. */bulletinChainGenesisHash: Data, - /** - * Asset Hub genesis hash. Must be exactly 32 bytes. - */assetHubChainGenesisHash: Data, /** * Optional local signing-host secret material (raw BIP-39 entropy). */localSessionSecret: Data?, @@ -3880,7 +3873,6 @@ public struct NativeHostRuntimeConfig: Equatable, Hashable { self.platformVersion = platformVersion self.peopleChainGenesisHash = peopleChainGenesisHash self.bulletinChainGenesisHash = bulletinChainGenesisHash - self.assetHubChainGenesisHash = assetHubChainGenesisHash self.localSessionSecret = localSessionSecret self.localSessionLiteUsername = localSessionLiteUsername } @@ -3908,7 +3900,6 @@ public struct FfiConverterTypeNativeHostRuntimeConfig: FfiConverterRustBuffer { platformVersion: FfiConverterOptionString.read(from: &buf), peopleChainGenesisHash: FfiConverterData.read(from: &buf), bulletinChainGenesisHash: FfiConverterData.read(from: &buf), - assetHubChainGenesisHash: FfiConverterData.read(from: &buf), localSessionSecret: FfiConverterOptionData.read(from: &buf), localSessionLiteUsername: FfiConverterOptionString.read(from: &buf) ) @@ -3922,7 +3913,6 @@ public struct FfiConverterTypeNativeHostRuntimeConfig: FfiConverterRustBuffer { FfiConverterOptionString.write(value.platformVersion, into: &buf) FfiConverterData.write(value.peopleChainGenesisHash, into: &buf) FfiConverterData.write(value.bulletinChainGenesisHash, into: &buf) - FfiConverterData.write(value.assetHubChainGenesisHash, into: &buf) FfiConverterOptionData.write(value.localSessionSecret, into: &buf) FfiConverterOptionString.write(value.localSessionLiteUsername, into: &buf) } @@ -4053,10 +4043,6 @@ public struct NativeRuntimeConfig: Equatable, Hashable { * Bulletin-chain genesis hash. Must be exactly 32 bytes. */ public var bulletinChainGenesisHash: Data - /** - * Asset Hub genesis hash. Must be exactly 32 bytes. - */ - public var assetHubChainGenesisHash: Data /** * Optional local signing-host secret material (raw BIP-39 entropy). */ @@ -4100,9 +4086,6 @@ public struct NativeRuntimeConfig: Equatable, Hashable { /** * Bulletin-chain genesis hash. Must be exactly 32 bytes. */bulletinChainGenesisHash: Data, - /** - * Asset Hub genesis hash. Must be exactly 32 bytes. - */assetHubChainGenesisHash: Data, /** * Optional local signing-host secret material (raw BIP-39 entropy). */localSessionSecret: Data?, @@ -4121,7 +4104,6 @@ public struct NativeRuntimeConfig: Equatable, Hashable { self.platformVersion = platformVersion self.peopleChainGenesisHash = peopleChainGenesisHash self.bulletinChainGenesisHash = bulletinChainGenesisHash - self.assetHubChainGenesisHash = assetHubChainGenesisHash self.localSessionSecret = localSessionSecret self.localSessionLiteUsername = localSessionLiteUsername self.pairingDeeplinkScheme = pairingDeeplinkScheme @@ -4152,7 +4134,6 @@ public struct FfiConverterTypeNativeRuntimeConfig: FfiConverterRustBuffer { platformVersion: FfiConverterOptionString.read(from: &buf), peopleChainGenesisHash: FfiConverterData.read(from: &buf), bulletinChainGenesisHash: FfiConverterData.read(from: &buf), - assetHubChainGenesisHash: FfiConverterData.read(from: &buf), localSessionSecret: FfiConverterOptionData.read(from: &buf), localSessionLiteUsername: FfiConverterOptionString.read(from: &buf), pairingDeeplinkScheme: FfiConverterTypeNativePairingDeeplinkScheme.read(from: &buf) @@ -4169,7 +4150,6 @@ public struct FfiConverterTypeNativeRuntimeConfig: FfiConverterRustBuffer { FfiConverterOptionString.write(value.platformVersion, into: &buf) FfiConverterData.write(value.peopleChainGenesisHash, into: &buf) FfiConverterData.write(value.bulletinChainGenesisHash, into: &buf) - FfiConverterData.write(value.assetHubChainGenesisHash, into: &buf) FfiConverterOptionData.write(value.localSessionSecret, into: &buf) FfiConverterOptionString.write(value.localSessionLiteUsername, into: &buf) FfiConverterTypeNativePairingDeeplinkScheme.write(value.pairingDeeplinkScheme, into: &buf) @@ -4931,14 +4911,6 @@ enum NativeRuntimeConfigError: Swift.Error, Equatable, Hashable, Foundation.Loca * Supplied byte length. */actual: UInt64 ) - /** - * Asset Hub genesis hash was not exactly 32 bytes. - */ - case InvalidAssetHubChainGenesisHash( - /** - * Supplied byte length. - */actual: UInt64 - ) /** * Host icon URL could not be parsed. */ @@ -5017,22 +4989,19 @@ public struct FfiConverterTypeNativeRuntimeConfigError: FfiConverterRustBuffer { case 3: return .InvalidBulletinChainGenesisHash( actual: try FfiConverterUInt64.read(from: &buf) ) - case 4: return .InvalidAssetHubChainGenesisHash( - actual: try FfiConverterUInt64.read(from: &buf) - ) - case 5: return .InvalidHostIcon( + case 4: return .InvalidHostIcon( reason: try FfiConverterString.read(from: &buf) ) - case 6: return .InsecureHostIcon( + case 5: return .InsecureHostIcon( scheme: try FfiConverterString.read(from: &buf) ) - case 7: return .InvalidDeeplinkScheme( + case 6: return .InvalidDeeplinkScheme( scheme: try FfiConverterString.read(from: &buf) ) - case 8: return .InvalidProductId( + case 7: return .InvalidProductId( productId: try FfiConverterString.read(from: &buf) ) - case 9: return .LocalSessionActivation( + case 8: return .LocalSessionActivation( reason: try FfiConverterString.read(from: &buf) ) @@ -5062,33 +5031,28 @@ public struct FfiConverterTypeNativeRuntimeConfigError: FfiConverterRustBuffer { FfiConverterUInt64.write(actual, into: &buf) - case let .InvalidAssetHubChainGenesisHash(actual): - writeInt(&buf, Int32(4)) - FfiConverterUInt64.write(actual, into: &buf) - - case let .InvalidHostIcon(reason): - writeInt(&buf, Int32(5)) + writeInt(&buf, Int32(4)) FfiConverterString.write(reason, into: &buf) case let .InsecureHostIcon(scheme): - writeInt(&buf, Int32(6)) + writeInt(&buf, Int32(5)) FfiConverterString.write(scheme, into: &buf) case let .InvalidDeeplinkScheme(scheme): - writeInt(&buf, Int32(7)) + writeInt(&buf, Int32(6)) FfiConverterString.write(scheme, into: &buf) case let .InvalidProductId(productId): - writeInt(&buf, Int32(8)) + writeInt(&buf, Int32(7)) FfiConverterString.write(productId, into: &buf) case let .LocalSessionActivation(reason): - writeInt(&buf, Int32(9)) + writeInt(&buf, Int32(8)) FfiConverterString.write(reason, into: &buf) } diff --git a/ios/truapi-host/Tests/TrUAPIWsBridgeTests.swift b/ios/truapi-host/Tests/TrUAPIWsBridgeTests.swift index 83f1b4455..d8a95821e 100644 --- a/ios/truapi-host/Tests/TrUAPIWsBridgeTests.swift +++ b/ios/truapi-host/Tests/TrUAPIWsBridgeTests.swift @@ -36,8 +36,7 @@ private extension TrUAPIWsBridgeTests { productId: "test.dot", hostName: "truapi-host-tests", peopleChainGenesisHash: Data(repeating: 0, count: 32), - bulletinChainGenesisHash: Data(repeating: 0, count: 32), - assetHubChainGenesisHash: Data(repeating: 0, count: 32) + bulletinChainGenesisHash: Data(repeating: 0, count: 32) ) } diff --git a/rust/crates/truapi-host-cli/src/main.rs b/rust/crates/truapi-host-cli/src/main.rs index 21c9f38df..4a6eb3c56 100644 --- a/rust/crates/truapi-host-cli/src/main.rs +++ b/rust/crates/truapi-host-cli/src/main.rs @@ -1276,7 +1276,6 @@ fn build_signing_runtime( platform_info(), network.people_genesis, network.bulletin_genesis, - network.asset_hub_genesis, ) .context("invalid signing host config")?; let runtime = Arc::new(SigningHostRuntime::with_chat_platform( diff --git a/rust/crates/truapi-host-cli/src/register_name.rs b/rust/crates/truapi-host-cli/src/register_name.rs index b39c97108..61c3cd26d 100644 --- a/rust/crates/truapi-host-cli/src/register_name.rs +++ b/rust/crates/truapi-host-cli/src/register_name.rs @@ -241,10 +241,18 @@ async fn resolve_link( } let identity = reader.dotns_identity(who).await?; match identity.lite_username { - Some(lite) => { + // The resolved name goes into the signed ring-VRF message, so it has + // to pass the same shape check as an explicit --link-lite: a store can + // hold a two-digit-suffixed label whose flattened form exceeds the + // gateway's BaseLabel bound. + Some(lite) if is_dotted_lite_username(&lite) => { debug!(%lite, "linking the account's own lite username"); Ok(Link::LiteUsername(lite.into_bytes())) } + Some(lite) => bail!( + "the account's lite username {lite:?} is not a linkable `name.NN` label; pass \ + --chat-key for a standalone registration" + ), None => bail!( "account has no lite username to link; pass --link-lite or \ --chat-key for a standalone registration" diff --git a/rust/crates/truapi-platform/src/lib.rs b/rust/crates/truapi-platform/src/lib.rs index dd5ea3845..10b875414 100644 --- a/rust/crates/truapi-platform/src/lib.rs +++ b/rust/crates/truapi-platform/src/lib.rs @@ -91,10 +91,6 @@ 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. Not read by the local-signing paths, which take - /// the session's usernames from the host; retained for parity with - /// [`PairingHostConfig`], where it drives the dotNS username lookup. - pub asset_hub_chain_genesis_hash: [u8; 32], } /// Product identity attached to one product-facing TrUAPI connection. @@ -208,13 +204,11 @@ 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-server/src/host_core.rs b/rust/crates/truapi-server/src/host_core.rs index a7f81dcc3..34c729942 100644 --- a/rust/crates/truapi-server/src/host_core.rs +++ b/rust/crates/truapi-server/src/host_core.rs @@ -1282,7 +1282,6 @@ mod tests { PlatformInfo::default(), [0; 32], [0xbb; 32], - [0xcc; 32], ) .expect("signing host config is valid"); let runtime = @@ -1328,7 +1327,6 @@ mod tests { PlatformInfo::default(), [0; 32], [0xbb; 32], - [0xcc; 32], ) .expect("signing host config is valid"); let runtime = diff --git a/rust/crates/truapi-server/src/host_logic/dotns_gateway.rs b/rust/crates/truapi-server/src/host_logic/dotns_gateway.rs index 94b9314a5..11bfca985 100644 --- a/rust/crates/truapi-server/src/host_logic/dotns_gateway.rs +++ b/rust/crates/truapi-server/src/host_logic/dotns_gateway.rs @@ -33,17 +33,10 @@ pub const RESERVE_MSG_PREFIX: &[u8] = b"pop:dotns-gateway:reserve"; /// /// `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 -}; +/// They need no on-chain state, so views work from this synthetic account on +/// any network. The address half is non-zero: a revert is read as an answer +/// here, and Solidity guards commonly reject `address(0)` outright. +pub const VIEW_CALL_ORIGIN: [u8; 32] = [0xEE; 32]; /// Builds the payload the candidate signs to authorize a lite-name /// reservation. @@ -334,6 +327,19 @@ pub fn decode_string(data: &[u8]) -> Result { decode_string_at(data, word_usize(data, 0)?) } +/// Decodes a single `bool` return value. +pub fn decode_bool(data: &[u8]) -> Result { + let bytes = word(data, 0)?; + if bytes[..31].iter().any(|b| *b != 0) { + return Err(DotnsContractError::Abi { context: "bool" }); + } + match bytes[31] { + 0 => Ok(false), + 1 => Ok(true), + _ => Err(DotnsContractError::Abi { context: "bool" }), + } +} + /// Decodes a single `address` return value. pub fn decode_address(data: &[u8]) -> Result<[u8; 20], DotnsContractError> { let bytes = word(data, 0)?; @@ -608,7 +614,8 @@ pub async fn resolve_labels( let registry_output = transport .view(controller, call_no_args("protocolRegistry()")) - .await?; + .await + .map_err(|err| format!("DotnsPopController.protocolRegistry: {err}"))?; let registry = decode_address(®istry_output) .map_err(|err| format!("DotnsPopController.protocolRegistry(): {err}"))?; @@ -617,13 +624,15 @@ pub async fn resolve_labels( ®istry, call_bytes32("get(bytes32)", ®istry_key("storeFactory")), ) - .await?; + .await + .map_err(|err| format!("ProtocolRegistry.get(storeFactory): {err}"))?; 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?; + .await + .map_err(|err| format!("StoreFactory.getLabelStore: {err}"))?; let store = decode_address(&store_output) .map_err(|err| format!("StoreFactory.getLabelStore: {err}"))?; if store == [0u8; 20] { @@ -642,7 +651,8 @@ pub async fn resolve_labels( LABEL_PAGE_LIMIT, ), ) - .await?; + .await + .map_err(|err| format!("LabelStore.getLabels: {err}"))?; let stored = decode_string_array(&labels_output) .map_err(|err| format!("LabelStore.getLabels: {err}"))?; let short_page = (stored.len() as u64) < LABEL_PAGE_LIMIT; @@ -683,19 +693,47 @@ fn bare_store_label<'a>(label: &'a str, tld: &str) -> Option<&'a str> { const TLD_WITHOUT_VIEW: &str = ".dot"; /// The network TLD with its leading dot (`.paseo`), read from -/// `ProtocolRegistry.tld()`. A registry without that view reverts, which -/// yields [`TLD_WITHOUT_VIEW`]; any other failure is an error, since guessing -/// the TLD would drop every label carrying the real one. +/// `ProtocolRegistry.tld()`. A registry without that view reverts; the fall +/// back to [`TLD_WITHOUT_VIEW`] is then verified rather than guessed — +/// `DotnsRegistry.recordExists(namehash("dot"))` must hold the TLD's own +/// record, or the resolution errors. Any other failure is an error: a wrong +/// TLD would drop every label carrying the real one. async fn network_tld( transport: &mut T, registry: &[u8; 20], ) -> Result { match transport.view(registry, call_no_args("tld()")).await { Ok(output) => { - decode_string(&output).map_err(|err| format!("ProtocolRegistry.tld(): {err}")) + return decode_string(&output).map_err(|err| format!("ProtocolRegistry.tld(): {err}")); + } + Err(DotnsViewError::Reverted(_)) => {} + Err(DotnsViewError::Failed(reason)) => { + return Err(format!("ProtocolRegistry.tld(): {reason}")); } - Err(DotnsViewError::Reverted(_)) => Ok(TLD_WITHOUT_VIEW.to_string()), - Err(DotnsViewError::Failed(reason)) => Err(format!("ProtocolRegistry.tld(): {reason}")), + } + let dotns_registry_output = transport + .view( + registry, + call_bytes32("get(bytes32)", ®istry_key("registry")), + ) + .await + .map_err(|err| format!("ProtocolRegistry.get(registry): {err}"))?; + let dotns_registry = decode_address(&dotns_registry_output) + .map_err(|err| format!("ProtocolRegistry.get(registry): {err}"))?; + let exists_output = transport + .view( + &dotns_registry, + call_bytes32("recordExists(bytes32)", &tld_node(TLD_WITHOUT_VIEW)), + ) + .await + .map_err(|err| format!("DotnsRegistry.recordExists: {err}"))?; + if decode_bool(&exists_output).map_err(|err| format!("DotnsRegistry.recordExists: {err}"))? { + Ok(TLD_WITHOUT_VIEW.to_string()) + } else { + Err(format!( + "ProtocolRegistry has no tld() view and the registry holds no record for \ + {TLD_WITHOUT_VIEW:?}; the network TLD cannot be determined" + )) } } @@ -766,10 +804,21 @@ async fn pending_claim_labels( controller: &[u8; 20], user: &[u8; 20], ) -> Result, String> { - let output = transport + // A revert is the controller's answer: no readable pending claims. The + // store is a separate source, so it is still read. + let output = match transport .view(controller, call_address("pendingClaims(address)", user)) .await - .map_err(|err| format!("DotnsPopController.pendingClaims: {err}"))?; + { + Ok(output) => output, + Err(DotnsViewError::Reverted(reason)) => { + warn!(%reason, "DotnsPopController.pendingClaims reverted; reading the store only"); + return Ok(Vec::new()); + } + Err(DotnsViewError::Failed(reason)) => { + return Err(format!("DotnsPopController.pendingClaims: {reason}")); + } + }; let claims = decode_pending_claims_array(&output) .map_err(|err| format!("DotnsPopController.pendingClaims: {err}"))?; if claims.is_empty() { @@ -941,6 +990,7 @@ mod tests { assert_eq!(hex::encode(selector("TARGET()")), "cc1f2afa"); assert_eq!(hex::encode(selector("pendingClaims(address)")), "ca78533d"); assert_eq!(hex::encode(selector("tld()")), "2d551432"); + assert_eq!(hex::encode(selector("recordExists(bytes32)")), "f79fe538"); assert_eq!(hex::encode(selector("ownerOf(uint256)")), "6352211e"); } @@ -1111,6 +1161,16 @@ mod tests { assert!(decode_u64(&[0xff; 32]).is_err()); } + #[test] + fn bool_words_decode_strictly() { + assert!(decode_bool(&abi_word(1)).unwrap()); + assert!(!decode_bool(&abi_word(0)).unwrap()); + assert!(decode_bool(&abi_word(2)).is_err()); + let mut high = abi_word(1); + high[5] = 1; + assert!(decode_bool(&high).is_err()); + } + #[test] fn a_pending_claim_lapses_after_the_reservation_duration() { // DotnsPopController._isExpired: mintedAt + reservationDuration < now. @@ -1178,11 +1238,16 @@ mod tests { assert!(!is_dotted_lite_username(&format!("{}.01", "a".repeat(31)))); } - /// A transport whose every view answers with one scripted result. - struct OneAnswer(fn() -> Result, DotnsViewError>); + /// A transport answering views by selector: `tld()` with a scripted + /// result, `get(bytes32)` with a registry address, `recordExists(bytes32)` + /// with a scripted bool. + struct ScriptedTld { + tld: fn() -> Result, DotnsViewError>, + dot_record_exists: bool, + } #[truapi_platform::async_trait] - impl DotnsTransport for OneAnswer { + impl DotnsTransport for ScriptedTld { async fn storage(&mut self, _key: Vec) -> Result>, String> { Ok(None) } @@ -1190,34 +1255,67 @@ mod tests { async fn view( &mut self, _dest: &[u8; 20], - _input: Vec, + input: Vec, ) -> Result, DotnsViewError> { - (self.0)() + let sel: [u8; 4] = input[..4].try_into().expect("selector prefix; qed"); + if sel == selector("tld()") { + (self.tld)() + } else if sel == selector("get(bytes32)") { + let mut word = [0u8; 32]; + word[12..].copy_from_slice(&[0xd0; 20]); + Ok(word.to_vec()) + } else if sel == selector("recordExists(bytes32)") { + assert_eq!(&input[4..36], &tld_node(TLD_WITHOUT_VIEW)); + Ok(abi_word(self.dot_record_exists as u64).to_vec()) + } else { + panic!("unscripted view {}", hex::encode(sel)); + } } } #[test] - fn the_tld_falls_back_only_when_the_view_reverts() { + fn the_tld_falls_back_only_when_the_view_reverts_and_the_record_exists() { let registry = [0x9e; 20]; + fn reverted() -> Result, DotnsViewError> { + Err(DotnsViewError::Reverted(DotnsContractError::Reverted { + detail: "(empty)".to_string(), + })) + } + let served = futures::executor::block_on(network_tld( - &mut OneAnswer(|| Ok([abi_word(32).to_vec(), abi_string(".paseo")].concat())), + &mut ScriptedTld { + tld: || Ok([abi_word(32).to_vec(), abi_string(".paseo")].concat()), + dot_record_exists: false, + }, ®istry, )); assert_eq!(served.as_deref(), Ok(".paseo")); - let reverted = futures::executor::block_on(network_tld( - &mut OneAnswer(|| { - Err(DotnsViewError::Reverted(DotnsContractError::Reverted { - detail: "(empty)".to_string(), - })) - }), + // The fallback is verified: the registry must hold the ".dot" record. + let verified = futures::executor::block_on(network_tld( + &mut ScriptedTld { + tld: reverted, + dot_record_exists: true, + }, + ®istry, + )); + assert_eq!(verified.as_deref(), Ok(TLD_WITHOUT_VIEW)); + + let unverified = futures::executor::block_on(network_tld( + &mut ScriptedTld { + tld: reverted, + dot_record_exists: false, + }, ®istry, )); - assert_eq!(reverted.as_deref(), Ok(TLD_WITHOUT_VIEW)); + assert!(unverified.is_err(), "{unverified:?}"); // A transport failure is not an answer: no guessing. let failed = futures::executor::block_on(network_tld( - &mut OneAnswer(|| Err(DotnsViewError::Failed("timed out".to_string()))), + &mut ScriptedTld { + tld: || Err(DotnsViewError::Failed("timed out".to_string())), + dot_record_exists: true, + }, ®istry, )); assert!(failed.is_err(), "{failed:?}"); diff --git a/rust/crates/truapi-server/src/native.rs b/rust/crates/truapi-server/src/native.rs index 2824a9e87..0d22741c4 100644 --- a/rust/crates/truapi-server/src/native.rs +++ b/rust/crates/truapi-server/src/native.rs @@ -196,8 +196,6 @@ 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. @@ -223,8 +221,6 @@ pub struct NativeHostRuntimeConfig { 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. @@ -274,12 +270,6 @@ 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 { @@ -326,7 +316,6 @@ impl TryFrom for NativeResolvedRuntimeConfig { platform_version, people_chain_genesis_hash, bulletin_chain_genesis_hash, - asset_hub_chain_genesis_hash, local_session_secret, local_session_lite_username, pairing_deeplink_scheme: _, @@ -339,7 +328,6 @@ impl TryFrom for NativeResolvedRuntimeConfig { platform_version, people_chain_genesis_hash, bulletin_chain_genesis_hash, - asset_hub_chain_genesis_hash, local_session_secret, local_session_lite_username, } @@ -369,12 +357,6 @@ impl TryFrom for NativeResolvedHostRuntimeConfig { 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 signing = SigningHostConfig::new( HostInfo { name: config.host_name, @@ -387,7 +369,6 @@ impl TryFrom for NativeResolvedHostRuntimeConfig { }, people_chain_genesis_hash, bulletin_chain_genesis_hash, - asset_hub_chain_genesis_hash, )?; Ok(Self { signing, @@ -2329,7 +2310,6 @@ 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, @@ -2345,7 +2325,6 @@ 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: Some(vec![7; 32]), local_session_lite_username: Some("alice".to_string()), } diff --git a/rust/crates/truapi-server/src/runtime/identity.rs b/rust/crates/truapi-server/src/runtime/identity.rs index ee04d711a..c5a919e2d 100644 --- a/rust/crates/truapi-server/src/runtime/identity.rs +++ b/rust/crates/truapi-server/src/runtime/identity.rs @@ -35,9 +35,9 @@ use truapi::latest::{ StorageQueryType, }; -/// Budget for the whole Asset Hub lookup: best block, controller discovery, the -/// contract walk and the label pages of a large store, well over a dozen -/// round trips on one follow. +/// Budget for the whole username resolution of one session: every attempt for +/// the identity account plus the root-key fallback share it, so a slow or dead +/// endpoint delays session installation by at most this long. const LOOKUP_BUDGET: Duration = Duration::from_secs(45); /// Budget for one step of it: opening the follow, one storage read, one /// contract view. A step that stalls this long is not going to answer. @@ -61,27 +61,43 @@ pub(super) async fn resolve_session_identity_with_chain( return session; } - let preferred_account = session.identity_account_id.unwrap_or(session.public_key); - if lookup_and_apply( - chain, - asset_hub_chain_genesis_hash, - preferred_account, - &mut session, - "identity", - ) - .await - == LookupOutcome::NoRecord - && preferred_account != session.public_key { - let public_key = session.public_key; - lookup_and_apply( - chain, - asset_hub_chain_genesis_hash, - public_key, - &mut session, - "root identity", - ) - .await; + let budget = futures_timer::Delay::new(LOOKUP_BUDGET).fuse(); + pin_mut!(budget); + let resolve = async { + let preferred_account = session.identity_account_id.unwrap_or(session.public_key); + if lookup_and_apply( + chain, + asset_hub_chain_genesis_hash, + preferred_account, + &mut session, + "identity", + ) + .await + == LookupOutcome::NoRecord + && preferred_account != session.public_key + { + let public_key = session.public_key; + lookup_and_apply( + chain, + asset_hub_chain_genesis_hash, + public_key, + &mut session, + "root identity", + ) + .await; + } + } + .fuse(); + pin_mut!(resolve); + futures::select! { + () = resolve => {} + () = budget => { + warn!( + "dotNS username resolution ran out of budget; the session installs without one" + ); + } + } } session @@ -144,16 +160,16 @@ async fn lookup_and_apply( } /// 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. +/// Hub head. Each step carries [`OPERATION_TIMEOUT`]; the caller's +/// [`LOOKUP_BUDGET`] bounds the whole resolution. 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_dotns_identity( chain: &ChainRuntime, asset_hub_chain_genesis_hash: [u8; 32], account_id: [u8; 32], ) -> Result, String> { - let timeout = futures_timer::Delay::new(LOOKUP_BUDGET).fuse(); - pin_mut!(timeout); let lookup = async { let mut lookup = DotnsLookup::pinned_to_best_block(chain, asset_hub_chain_genesis_hash, account_id) @@ -169,10 +185,7 @@ async fn lookup_dotns_identity( } .fuse(); pin_mut!(lookup); - futures::select! { - value = lookup => value, - () = timeout => Err("dotNS identity lookup timed out".to_string()), - } + lookup.await } /// One pinned-block context for the dotNS lookup steps. It owns the diff --git a/rust/crates/truapi-server/src/runtime/signing_host.rs b/rust/crates/truapi-server/src/runtime/signing_host.rs index 87c3f52cd..ecb312be4 100644 --- a/rust/crates/truapi-server/src/runtime/signing_host.rs +++ b/rust/crates/truapi-server/src/runtime/signing_host.rs @@ -1294,7 +1294,6 @@ 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 730d30cc3..afb299dd2 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 @@ -1583,7 +1583,6 @@ 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/ring.rs b/rust/crates/truapi-server/src/runtime/statement_allowance/ring.rs index 1263ae271..534f08c59 100644 --- a/rust/crates/truapi-server/src/runtime/statement_allowance/ring.rs +++ b/rust/crates/truapi-server/src/runtime/statement_allowance/ring.rs @@ -327,6 +327,10 @@ enum MemberRingPosition { /// Reads the ring index `member` is included in for `collection`, from /// `Members.Members`, pinned to block `at`. Errors when the member has no /// record. Errors too when the member is not `Included` yet. +/// +/// TODO(#334): second reader of `Members.Members` next to the subxt-typed one +/// in `signing_host/ring_vrf.rs`; converge them (they also differ on +/// non-`Included` members — this errors, that one skips). pub async fn read_member_ring_index_at( rpc: &RpcClient, metadata: &Metadata, diff --git a/rust/crates/truapi-server/src/wasm.rs b/rust/crates/truapi-server/src/wasm.rs index 8f4405bbd..ed15997e6 100644 --- a/rust/crates/truapi-server/src/wasm.rs +++ b/rust/crates/truapi-server/src/wasm.rs @@ -515,7 +515,6 @@ fn signing_host_config_from_js(value: &JsValue) -> Result Result Date: Thu, 20 Aug 2026 10:57:41 +0300 Subject: [PATCH 18/19] chore: bump hosts/dotli to the head of dotli-community#180 Carries the runtime-config contract test for assetHub and the deepmerge-ts audit pin alongside the assetHub field itself. --- hosts/dotli | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hosts/dotli b/hosts/dotli index e42198c60..3592073c2 160000 --- a/hosts/dotli +++ b/hosts/dotli @@ -1 +1 @@ -Subproject commit e42198c607f971d77ded8c2d5cc48fd528d4e85f +Subproject commit 3592073c29121a8fdaabdfce2314dcded42e2a46 From 6b02377fb103e0ecfb51231afb895f4d588555d8 Mon Sep 17 00:00:00 2001 From: Filip Baciu Date: Fri, 21 Aug 2026 19:24:33 +0300 Subject: [PATCH 19/19] fix(server): screen usernames centrally and fail closed on availability MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review of #426 by TarikGul. - classify_labels accepts only canonical DNS labels (lowercase letters, digits, hyphens, at most 63 octets), so no unscreened contract string — oversized, control characters, markup, ANSI — reaches SessionUiInfo or any later caller; the bound lives in one place instead of per call site. - label_available reads DotnsRegistrar.exists(uint256), a total view: minted and unminted are both answers, and a revert is a broken deployment and an error, never "available". Covered offline for all four outcomes. --- .../src/host_logic/dotns_gateway.rs | 127 +++++++++++++++--- 1 file changed, 107 insertions(+), 20 deletions(-) diff --git a/rust/crates/truapi-server/src/host_logic/dotns_gateway.rs b/rust/crates/truapi-server/src/host_logic/dotns_gateway.rs index 11bfca985..fdde69cc0 100644 --- a/rust/crates/truapi-server/src/host_logic/dotns_gateway.rs +++ b/rust/crates/truapi-server/src/host_logic/dotns_gateway.rs @@ -427,9 +427,11 @@ pub struct DotnsIdentity { /// two digits, `StringUtils.isSingleDotLiteLabel`) into the flat label `stemNN` /// before minting, so a flat label whose last two characters are digits after a /// DNS-label stem is read back as a lite username and re-dotted (`alice01` → -/// `alice.01`). Everything else is a full username. First hit per slot wins. -/// Labels are expected bare: [`resolve_labels`] strips the network TLD, and any -/// label still carrying a dot (a subname such as `app.alice`) is skipped. +/// `alice.01`). Every other canonical DNS label is a full username; anything +/// that is not one — oversized, non-ASCII, control characters, markup, or a +/// dotted subname — is skipped, so no unscreened contract string becomes a +/// username. First hit per slot wins. Labels are expected bare: +/// [`resolve_labels`] strips the network TLD. /// /// Lite and public names share one namespace on the contract side, so a public /// name ending in two digits is indistinguishable from a flattened lite name @@ -448,7 +450,10 @@ where let mut identity = DotnsIdentity::default(); for label in labels { let label = label.as_ref(); - if !label.is_ascii() || label.contains('.') { + // Contract data is untrusted and these strings reach host UI + // (`SessionUiInfo`): anything that is not a canonical DNS label — + // oversized, control characters, markup, dots — is not a username. + if !is_dns_label(label) { continue; } let (stem, digits) = label.split_at(label.len().saturating_sub(2)); @@ -467,10 +472,15 @@ where identity } +/// RFC 1035 label bound the contracts enforce (`StringUtils.MAX_DNS_LABEL_OCTETS`). +const MAX_DNS_LABEL_LEN: usize = 63; + /// A canonical DNS label: lowercase ASCII letters, digits and hyphens, neither -/// starting nor ending with a hyphen (`StringUtils._isDnsLabel`). +/// starting nor ending with a hyphen, at most [`MAX_DNS_LABEL_LEN`] octets +/// (`StringUtils._isDnsLabel`). pub fn is_dns_label(value: &str) -> bool { !value.is_empty() + && value.len() <= MAX_DNS_LABEL_LEN && !value.starts_with('-') && !value.ends_with('-') && value @@ -749,10 +759,11 @@ pub fn namehash_under(parent: &[u8; 32], label: &str) -> [u8; 32] { } /// Whether the gateway could still mint `label` on this network: the name's -/// node under the network TLD (`namehash(label.tld)`, derived locally) has no -/// owner on the `DotnsRegistrar` (`ownerOf` reverts). A minted name is taken -/// whoever holds it, the name escrow included: the gateway's registration path -/// only mints fresh ids. +/// node under the network TLD (`namehash(label.tld)`, derived locally) does +/// not exist on the `DotnsRegistrar` (`exists(uint256)`, a total view, so a +/// revert is a broken deployment and an error, never "available"). A minted +/// name is taken whoever holds it, the name escrow included: the gateway's +/// registration path only mints fresh ids. /// /// A lite-name reservation carrying a `reserved_base_label` that is already /// registered can never be claimed, yet it holds the reservation queue for @@ -781,18 +792,13 @@ pub async fn label_available( .map_err(|err| format!("ProtocolRegistry.get(registrar): {err}"))?; let node = namehash_under(&tld_node(&tld), label); - match transport - .view(®istrar, call_bytes32("ownerOf(uint256)", &node)) + let output = transport + .view(®istrar, call_bytes32("exists(uint256)", &node)) .await - { - Ok(output) => decode_address(&output) - .map(|_owner| false) - .map_err(|err| format!("DotnsRegistrar.ownerOf({label}): {err}")), - Err(DotnsViewError::Reverted(_)) => Ok(true), - Err(DotnsViewError::Failed(reason)) => { - Err(format!("DotnsRegistrar.ownerOf({label}): {reason}")) - } - } + .map_err(|err| format!("DotnsRegistrar.exists({label}): {err}"))?; + decode_bool(&output) + .map(|exists| !exists) + .map_err(|err| format!("DotnsRegistrar.exists({label}): {err}")) } /// Gateway-minted labels of `user` still waiting for `claimLabelStore`, from @@ -992,6 +998,7 @@ mod tests { assert_eq!(hex::encode(selector("tld()")), "2d551432"); assert_eq!(hex::encode(selector("recordExists(bytes32)")), "f79fe538"); assert_eq!(hex::encode(selector("ownerOf(uint256)")), "6352211e"); + assert_eq!(hex::encode(selector("exists(uint256)")), "4f558e79"); } #[test] @@ -1203,6 +1210,21 @@ mod tests { classify_labels(Vec::::new()), DotnsIdentity::default() ); + + // Hostile store data never becomes a username: oversized labels, + // control characters, markup, ANSI escapes, interior NULs. + let identity = classify_labels([ + "a".repeat(5000), + "admin\r\nx".to_string(), + "a\0b".to_string(), + "\x1b[31mred\x1b[0m".to_string(), + "".to_string(), + "Upper".to_string(), + ]); + assert_eq!(identity, DotnsIdentity::default()); + // The 63-octet DNS bound is the cut-off. + assert!(classify_labels(["a".repeat(63)]).full_username.is_some()); + assert!(classify_labels(["a".repeat(64)]).full_username.is_none()); // One trailing digit is not lite format. let identity = classify_labels(["alice1"]); assert_eq!(identity.lite_username, None); @@ -1321,6 +1343,71 @@ mod tests { assert!(failed.is_err(), "{failed:?}"); } + /// The full availability walk, scripted: tld → get(registrar) → exists. + struct ScriptedAvailability { + exists: fn() -> Result, DotnsViewError>, + } + + #[truapi_platform::async_trait] + impl DotnsTransport for ScriptedAvailability { + async fn storage(&mut self, _key: Vec) -> Result>, String> { + Ok(None) + } + + async fn view( + &mut self, + _dest: &[u8; 20], + input: Vec, + ) -> Result, DotnsViewError> { + let sel: [u8; 4] = input[..4].try_into().expect("selector prefix; qed"); + if sel == selector("protocolRegistry()") { + let mut word = [0u8; 32]; + word[12..].copy_from_slice(&[0x9e; 20]); + Ok(word.to_vec()) + } else if sel == selector("tld()") { + Ok([abi_word(32).to_vec(), abi_string(".paseo")].concat()) + } else if sel == selector("get(bytes32)") { + let mut word = [0u8; 32]; + word[12..].copy_from_slice(&[0xd1; 20]); + Ok(word.to_vec()) + } else if sel == selector("exists(uint256)") { + assert_eq!( + &input[4..36], + &namehash_under(&tld_node(".paseo"), "george") + ); + (self.exists)() + } else { + panic!("unscripted view {}", hex::encode(sel)); + } + } + } + + /// `available` must fail closed: a registrar that reverts (a wrong or + /// undeployed entry) is an error, never "available", or the already-minted + /// guard would pass for every name. + #[test] + fn availability_fails_closed_when_the_registrar_does_not_answer() { + let controller = [0xc0; 20]; + let run = |exists: fn() -> Result, DotnsViewError>| { + futures::executor::block_on(label_available( + &mut ScriptedAvailability { exists }, + &controller, + "george", + )) + }; + + assert_eq!(run(|| Ok(abi_word(0).to_vec())), Ok(true)); + assert_eq!(run(|| Ok(abi_word(1).to_vec())), Ok(false)); + let reverted = run(|| { + Err(DotnsViewError::Reverted(DotnsContractError::Reverted { + detail: "(empty)".to_string(), + })) + }); + assert!(reverted.is_err(), "{reverted:?}"); + let failed = run(|| Err(DotnsViewError::Failed("timed out".to_string()))); + assert!(failed.is_err(), "{failed:?}"); + } + #[test] fn view_output_separates_reverts_from_failures() { let reverted = contract_result(&{