Context
The hsm-blockchain crate (crates/blockchain) implements real, KAT-tested BIP-32/39/44 HD key derivation and Ethereum EIP-191/712 signing:
crates/blockchain/src/bip/{bip32.rs,bip39.rs,bip44.rs} — ExtendedPrivateKey::from_seed, derive_path, Mnemonic, AccountPath/CoinType (re-exported in crates/blockchain/src/lib.rs:206-228).
crates/blockchain/src/ethereum/eip191.rs — PersonalMessage, Eip191Message, RecoverableSignature with r()/s()/v()/v_eip155()/to_bytes() (eip191.rs:96-240).
crates/blockchain/src/ethereum/eip712.rs — Eip712Domain, Eip712TypedData, TypedDataHasher::hash, plus Eip712Signing::sign (eip712.rs:577). A canonical "Ether Mail" known-answer test was added in this hardening pass at eip712.rs:679-768 (test_eip712_mail_known_answer).
crates/blockchain/src/ethereum/address.rs — EthereumAddress::from_public_key / from_verifying_key / to_checksum_string (EIP-55).
During the 12-PR quality + hardening pass, the REST surface and UI for these features were deliberately deferred (gated/stubbed and honestly labelled), not regressed. This issue tracks completing that intentionally-deferred work.
Current state (verified)
No REST surface exists. crates/rest-api/src/routes.rs wires only /keys, /audit, /auth, /namespaces, /webhooks (routes.rs:76-82). There is no /blockchain nest and no blockchain handlers. The rest-api crate does not even depend on hsm-blockchain (verified: no hsm-blockchain entry in crates/rest-api/Cargo.toml).
The UI calls routes that 404. ui/lib/api.ts:167-205 defines listWallets/createWallet/deriveKey/signEip191/signEip712/getAddresses, all targeting ${API_BASE}/blockchain/* (e.g. /blockchain/wallets, /blockchain/wallets/{id}/derive, /blockchain/sign/eip191, /blockchain/sign/eip712, /blockchain/addresses). Request/response shapes already exist in ui/lib/types.ts:100-163 (Wallet, DeriveKeyRequest/Response, SignEip191Request, SignEip712Request, Eip712TypedData, SignatureResponse, AddressRequest/Response).
The UI pages are ComingSoon stubs. All five pages under ui/app/(protected)/blockchain/ render <ComingSoon .../>:
page.tsx — landing ("once the backend routes land").
wallets/page.tsx, derive/page.tsx, sign/page.tsx, addresses/page.tsx — each explicitly says it "will be enabled once the blockchain REST routes land".
Where wallet/seed state must live. Mnemonics/seeds are key material and must never leave the boundary. AppState (crates/rest-api/src/middleware.rs:23-46) holds key_manager: Arc<dyn KeyManager>, namespaces, acls, rbac, audit. The key manager already supports KeyType::Secp256k1 generation and import (crates/key-manager/src/lib.rs:433-438, :661-681) and the Ethereum curve, so derived per-path signing keys can be persisted as managed keys; the master seed itself should be stored encrypted (envelope-encrypted via master key) and never exported.
Experimental-chains gating must be respected. crates/blockchain/src/lib.rs:188-200 gates aptos/near/sui behind #[cfg(feature = "experimental-chains")] because their tx serialization is not chain-canonical. The default build (crates/blockchain/Cargo.toml default = []) excludes them. /blockchain/addresses and any chain enumeration must only expose chain-compatible chains by default; Aptos/Sui/NEAR stay behind the feature flag and must not be silently reachable.
What is needed
- Add
hsm-blockchain as a dependency of crates/rest-api.
- Design wallet/seed state management:
- Store mnemonic/seed as encrypted key material (envelope-encrypted under the master key via the storage/key-manager path); never expose plaintext seed/mnemonic in any response.
- Model a
Wallet record (id, name, created_at, derived_keys_count, owner namespace) with namespace isolation + ACL/RBAC enforcement consistent with /keys.
- Derived keys (
m/44'/60'/...) materialize as managed Secp256k1 keys so existing sign/audit paths apply.
- Add
/blockchain routes nested into authenticated_routes (so auth + per-client rate-limit + audit middleware apply) matching the UI client exactly:
GET /blockchain/wallets → list wallets
POST /blockchain/wallets → create wallet (accept optional mnemonic to import, else generate; return metadata only, never the seed)
POST /blockchain/wallets/{walletId}/derive → derive key at BIP-44 path, return key_id/public_key/path/chain_code?
POST /blockchain/sign/eip191 → sign personal message by key_id, return {signature, r, s, v}
POST /blockchain/sign/eip712 → sign typed data by key_id, return {signature, r, s, v}
POST /blockchain/addresses → derive addresses for a key_id across requested chains
- Map crate types ↔ REST DTOs (
RecoverableSignature/Eip712Signature → SignatureResponse; UI Eip712TypedData JSON → Eip712TypedData::new).
- Re-enable the UI: replace the five
ComingSoon stubs in ui/app/(protected)/blockchain/* with real pages driven by the existing ui/lib/api.ts functions and ui/lib/types.ts shapes.
- Respect experimental gating:
/blockchain/addresses and chain lists expose only chain-compatible chains by default; Aptos/Sui/NEAR remain behind experimental-chains and are rejected/hidden in default builds.
Acceptance criteria
Related (do not duplicate)
Context
The
hsm-blockchaincrate (crates/blockchain) implements real, KAT-tested BIP-32/39/44 HD key derivation and Ethereum EIP-191/712 signing:crates/blockchain/src/bip/{bip32.rs,bip39.rs,bip44.rs}—ExtendedPrivateKey::from_seed,derive_path,Mnemonic,AccountPath/CoinType(re-exported incrates/blockchain/src/lib.rs:206-228).crates/blockchain/src/ethereum/eip191.rs—PersonalMessage,Eip191Message,RecoverableSignaturewithr()/s()/v()/v_eip155()/to_bytes()(eip191.rs:96-240).crates/blockchain/src/ethereum/eip712.rs—Eip712Domain,Eip712TypedData,TypedDataHasher::hash, plusEip712Signing::sign(eip712.rs:577). A canonical "Ether Mail" known-answer test was added in this hardening pass ateip712.rs:679-768(test_eip712_mail_known_answer).crates/blockchain/src/ethereum/address.rs—EthereumAddress::from_public_key/from_verifying_key/to_checksum_string(EIP-55).During the 12-PR quality + hardening pass, the REST surface and UI for these features were deliberately deferred (gated/stubbed and honestly labelled), not regressed. This issue tracks completing that intentionally-deferred work.
Current state (verified)
No REST surface exists.
crates/rest-api/src/routes.rswires only/keys,/audit,/auth,/namespaces,/webhooks(routes.rs:76-82). There is no/blockchainnest and no blockchain handlers. Therest-apicrate does not even depend onhsm-blockchain(verified: nohsm-blockchainentry incrates/rest-api/Cargo.toml).The UI calls routes that 404.
ui/lib/api.ts:167-205defineslistWallets/createWallet/deriveKey/signEip191/signEip712/getAddresses, all targeting${API_BASE}/blockchain/*(e.g./blockchain/wallets,/blockchain/wallets/{id}/derive,/blockchain/sign/eip191,/blockchain/sign/eip712,/blockchain/addresses). Request/response shapes already exist inui/lib/types.ts:100-163(Wallet,DeriveKeyRequest/Response,SignEip191Request,SignEip712Request,Eip712TypedData,SignatureResponse,AddressRequest/Response).The UI pages are
ComingSoonstubs. All five pages underui/app/(protected)/blockchain/render<ComingSoon .../>:page.tsx— landing ("once the backend routes land").wallets/page.tsx,derive/page.tsx,sign/page.tsx,addresses/page.tsx— each explicitly says it "will be enabled once the blockchain REST routes land".Where wallet/seed state must live. Mnemonics/seeds are key material and must never leave the boundary.
AppState(crates/rest-api/src/middleware.rs:23-46) holdskey_manager: Arc<dyn KeyManager>,namespaces,acls,rbac,audit. The key manager already supportsKeyType::Secp256k1generation and import (crates/key-manager/src/lib.rs:433-438,:661-681) and the Ethereum curve, so derived per-path signing keys can be persisted as managed keys; the master seed itself should be stored encrypted (envelope-encrypted via master key) and never exported.Experimental-chains gating must be respected.
crates/blockchain/src/lib.rs:188-200gatesaptos/near/suibehind#[cfg(feature = "experimental-chains")]because their tx serialization is not chain-canonical. The default build (crates/blockchain/Cargo.tomldefault = []) excludes them./blockchain/addressesand any chain enumeration must only expose chain-compatible chains by default; Aptos/Sui/NEAR stay behind the feature flag and must not be silently reachable.What is needed
hsm-blockchainas a dependency ofcrates/rest-api.Walletrecord (id, name, created_at, derived_keys_count, owner namespace) with namespace isolation + ACL/RBAC enforcement consistent with/keys.m/44'/60'/...) materialize as managedSecp256k1keys so existing sign/audit paths apply./blockchainroutes nested intoauthenticated_routes(so auth + per-client rate-limit + audit middleware apply) matching the UI client exactly:GET /blockchain/wallets→ list walletsPOST /blockchain/wallets→ create wallet (accept optional mnemonic to import, else generate; return metadata only, never the seed)POST /blockchain/wallets/{walletId}/derive→ derive key at BIP-44 path, returnkey_id/public_key/path/chain_code?POST /blockchain/sign/eip191→ sign personal message bykey_id, return{signature, r, s, v}POST /blockchain/sign/eip712→ sign typed data bykey_id, return{signature, r, s, v}POST /blockchain/addresses→ derive addresses for akey_idacross requested chainsRecoverableSignature/Eip712Signature→SignatureResponse; UIEip712TypedDataJSON →Eip712TypedData::new).ComingSoonstubs inui/app/(protected)/blockchain/*with real pages driven by the existingui/lib/api.tsfunctions andui/lib/types.tsshapes./blockchain/addressesand chain lists expose only chain-compatible chains by default; Aptos/Sui/NEAR remain behindexperimental-chainsand are rejected/hidden in default builds.Acceptance criteria
crates/rest-api/Cargo.tomldepends onhsm-blockchain./blockchainroutes are nested underauthenticated_routesincrates/rest-api/src/routes.rsand pass through auth, per-client rate-limit, and audit middleware.ui/lib/api.ts:167-205paths/verbs andui/lib/types.ts:100-163request/response shapes./keys, and emit audit-log entries (fail-closed) for create/derive/sign.{signature, r, s, v}and a round-trip/KAT test verifies output against the crate's existing KATs (incl. the EIP-712 "Ether Mail" vector atcrates/blockchain/src/ethereum/eip712.rs:679-768)./blockchain/addressesreturns EIP-55 checksummed Ethereum addresses and only chain-compatible chains by default; Aptos/Sui/NEAR are rejected/absent unlessexperimental-chainsis enabled (test covers default-build rejection).ui/app/(protected)/blockchain/*pages no longer renderComingSoon; they perform the corresponding API calls and render results.ui/lib/api.tscalls no longer 404 against a runninghsm-server.cargo check --all,cargo clippy --all -- -D warnings, andcargo test --all(skipping slow tests) pass; UI builds.Related (do not duplicate)