Skip to content

feat(express): verify created addresses against client-supplied trusted keychains#9109

Open
rajangarg047 wants to merge 1 commit into
masterfrom
rajangarg047/wcn-1055-express-auto-verify-create
Open

feat(express): verify created addresses against client-supplied trusted keychains#9109
rajangarg047 wants to merge 1 commit into
masterfrom
rajangarg047/wcn-1055-express-auto-verify-create

Conversation

@rajangarg047

Copy link
Copy Markdown
Contributor

Summary

Adds an opt-in "bring your own trusted keys" check to address creation. When a createAddress request includes trustedKeychains (public key material the caller holds independently), Express locally re-derives each newly created address via coin.deriveAddress and fails the request (400) if it doesn't match the address the service returned — an independent verification folded into the create call, no client-side round-trip.

Why this (and how it differs from the SDK's existing check)

wallet.createAddress already verifies the new address — but using keychains it fetches from the same service (an integrity check; circular). This feature verifies against caller-supplied keys, so a match is an independent trust guarantee.

Key properties:

  • Per-request opt-in. Requests without trustedKeychains are completely unchanged — so other clients/coins are unaffected and no server config/flag is needed.
  • Caller opted in, so an unsupported coin/wallet surfaces a clear 400 rather than silently skipping the requested verification.
  • Works wherever local derivation exists: BTC/UTXO, ETH (MPC + forwarder), SOL.

Scope note

Token (e.g. SPL) deposit addresses derive differently (associated token account) and are not verified here yet — that path depends on token derivation (WCN-1054, PR #9080) and is a fast-follow. Token-address creates (onToken) are skipped rather than mismatched.

Changes

  • express typedRoute: add optional trustedKeychains to the createAddress body (reuses the address/derive keychain union codec).
  • express handler: handleV2CreateAddress runs verifyCreatedAddressesWithTrustedKeys when the field is present.

Context

WCN-1055 (FR-465 Phase 2) — requested by Bullish. Chose the client-supplied-keys approach (vs an operator keystore) so it's a single transparent create call with the full independent-trust guarantee and no server-side key management.

Test plan

  • tsc clean (express src); eslint 0 errors
  • 5 new tests: match → 200 (asserts derive called with the trusted keychains), mismatch → 400, unsupported coin → 400, omitted → no derive call, onToken → skipped
  • OpenAPI regenerates; vacuum 0 errors / 0 warnings with the new field
  • CI

🤖 Generated with Claude Code

@rajangarg047 rajangarg047 requested review from a team as code owners June 24, 2026 17:57
@linear-code

linear-code Bot commented Jun 24, 2026

Copy link
Copy Markdown

WCN-1055

Comment thread modules/express/src/clientRoutes.ts Outdated
async function verifyCreatedAddressesWithTrustedKeys(
coin: ReturnType<BitGo['coin']>,
wallet: Wallet,
result: any,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there a better typing we can use or?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call — replaced result: any with a CreateAddressResult type (single address or { addresses: [...] }) whose fields reuse the DeriveAddressOptions types for the values we feed back into coin.deriveAddress. Done in d92773f.

Comment thread modules/express/src/clientRoutes.ts Outdated
}
let derived;
try {
derived = await coin.deriveAddress({

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need to try catch here? does the dervieAddress not return a good error messaging? If it does, we can avoid doing let since we're not reassigning the var

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The try/catch is intentional: the caller opted in by supplying trustedKeychains, so a derivation failure (e.g. an unsupported coin/wallet) must surface as a 400 client error rather than deriveAddress's default 500 — that's what the "unsupported coin → 400" test asserts.

I did drop the let though: the mismatch check now lives inside the try block so derived is a const, and the catch rethrows ApiResponseError unchanged so the 400 mapping only applies to genuine derivation errors. d92773f.

@rajangarg047 rajangarg047 force-pushed the rajangarg047/wcn-1055-express-auto-verify-create branch from d92773f to 16a192b Compare June 24, 2026 21:08
…ed keychains

Add an opt-in "bring your own trusted keys" check to address creation. When the
createAddress request includes trustedKeychains (public key material the caller
holds independently), Express locally re-derives each newly created address via
coin.deriveAddress and fails the request if it does not match what the service
returned — an independent verification folded into the create call, with no
client-side round-trip.

Unlike the SDK's built-in check (which verifies using keychains fetched from the
same service), this uses caller-supplied keys, so a match is an independent trust
guarantee. It is purely per-request opt-in: requests without trustedKeychains are
unchanged, so other clients/coins are unaffected, and no server config is needed.

The caller opted in, so an unsupported coin/wallet surfaces a clear 400 rather than
silently skipping. The createAddress result is modeled as a CreateAddressResult
type (reusing DeriveAddressOptions types) instead of any, and derivation failures
and mismatches alike map to 400.

Token (e.g. SPL) deposit addresses derive differently and are not verified here
yet (follows WCN-1054).

WCN-1055

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@rajangarg047 rajangarg047 force-pushed the rajangarg047/wcn-1055-express-auto-verify-create branch from 16a192b to e5129b5 Compare June 24, 2026 21:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants