Skip to content

Implement /blockchain REST routes (HD wallets + EIP-191/712 signing + address derivation) and re-enable the UI #24

Description

@stxkxs

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.rsPersonalMessage, Eip191Message, RecoverableSignature with r()/s()/v()/v_eip155()/to_bytes() (eip191.rs:96-240).
  • crates/blockchain/src/ethereum/eip712.rsEip712Domain, 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.rsEthereumAddress::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

  1. Add hsm-blockchain as a dependency of crates/rest-api.
  2. 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.
  3. 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
  4. Map crate types ↔ REST DTOs (RecoverableSignature/Eip712SignatureSignatureResponse; UI Eip712TypedData JSON → Eip712TypedData::new).
  5. 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.
  6. 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

  • crates/rest-api/Cargo.toml depends on hsm-blockchain.
  • /blockchain routes are nested under authenticated_routes in crates/rest-api/src/routes.rs and pass through auth, per-client rate-limit, and audit middleware.
  • All six endpoints implemented to match ui/lib/api.ts:167-205 paths/verbs and ui/lib/types.ts:100-163 request/response shapes.
  • Wallet seed/mnemonic is persisted as encrypted key material and is never returned in any response (no plaintext seed export); enforced by a test.
  • Wallet and derive operations enforce namespace isolation + RBAC/ACL identically to /keys, and emit audit-log entries (fail-closed) for create/derive/sign.
  • EIP-191 and EIP-712 sign endpoints return {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 at crates/blockchain/src/ethereum/eip712.rs:679-768).
  • /blockchain/addresses returns EIP-55 checksummed Ethereum addresses and only chain-compatible chains by default; Aptos/Sui/NEAR are rejected/absent unless experimental-chains is enabled (test covers default-build rejection).
  • The five ui/app/(protected)/blockchain/* pages no longer render ComingSoon; they perform the corresponding API calls and render results. ui/lib/api.ts calls no longer 404 against a running hsm-server.
  • cargo check --all, cargo clippy --all -- -D warnings, and cargo test --all (skipping slow tests) pass; UI builds.

Related (do not duplicate)

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requesthelp wantedExtra attention is needed

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions