Skip to content

WAL-1868: Express endpoint to verify a TSS EdDSA key belongs to a wallet - #9873

Open
yashaanand wants to merge 1 commit into
masterfrom
yashaanand/WAL-1868-express-verify-key
Open

yashaanand wants to merge 1 commit into
masterfrom
yashaanand/WAL-1868-express-verify-key

Conversation

@yashaanand

Copy link
Copy Markdown

Description

Adds POST /api/v2/{coin}/wallet/{id}/verifyKey to BitGo Express, backed by a new SDK Wallet.verifyKey method: a caller that holds its own TSS EdDSA (MPCv1) user signing material can learn up front whether it belongs to a given wallet, instead of discovering a mismatch only when a real transaction fails to sign.

The caller passes the same prv string it would use to sign (e.g. on sendmany). Express recombines the shares locally and compares the result against the wallet's commonKeychain, returning { "match": true | false }. The key material never leaves the Express process, is never sent to the BitGo server, and is never echoed in any error message or response body.

Implementation layers:

  • sdk-core: pure parse/shape-check/recombine helpers in utils/tss/keyVerification (constant, non-leaking error messages); a per-algorithm hook on ITssUtils (verifyKey + supportsVerifyKey — the base default reports unsupported and EddsaUtils implements the EdDSA MPCv1 check), so later algorithm support is a one-class change; Wallet.verifyKey rejects unsupported wallet types before any network request and fetches only the user keychain.
  • express: typed route and handler for express.v2.wallet.verifyKey; errors with a meaningful HTTP status (e.g. the keychain fetch) surface with their real status instead of being masked as 400; transport-level failures surface as infrastructure errors. The shared decode-error formatter now redacts structured values, so key material sent with the wrong field type can never be echoed back (this also hardens existing routes with sensitive fields).

Release note

ITssUtils (sdk-core) gains two required members, verifyKey() and supportsVerifyKey(). TypeScript consumers that implement this interface may need to add them; in-repo implementers inherit the base defaults.

Issue Number

Ticket: WAL-1868

Type of change

  • New feature (non-breaking change which adds functionality)

How Has This Been Tested?

  • yarn lerna run build --include-dependencies --scope @bitgo/sdk-core --scope @bitgo/express — clean, on a branch rebased onto latest master
  • New unit tests, all passing:
    • sdk-core test/unit/bitgo/utils/tss/keyVerification.ts — 14 cases: parse/shape validation (missing/malformed/wrong-length shares), genuine material with the optional v absent, recombine true/false, constant-error boundary, and leak-rule assertions (no error message ever echoes the input)
    • sdk-core test/unit/bitgo/wallet/walletVerifyKey.ts — 7 cases: match true/false, rejection without any keychain request for on-chain / EdDSA MPCv2 / ECDSA wallets, request-tracer passthrough, missing-commonKeychain keychain
    • express test/unit/typedRoutes/verifyKey.ts — 9 cases: 200 true/false, 400 with the SDK message, missing-prv 400, upstream-status passthrough (404 wallet lookup, 429 from inside verifyKey), transport failure surfacing as 500, non-Error message, and non-string prv rejected without echoing the material
    • express test/unit/typedRoutes/formatValidationErrors.ts — redaction contract for structured values
  • yarn lint clean in both modules; yarn check-commits clean

Checklist:

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • My code compiles correctly for both Node and Browser environments
  • I have commented my code, particularly in hard-to-understand areas
  • My commits follow Conventional Commits and I have properly described any BREAKING CHANGES
  • The ticket or github issue was included in the commit message as a reference
  • I have made corresponding changes to the documentation and on any new/updated functions and/or methods
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes

Follow-ups (not blocking this PR)

  • Server-repo mirror route: per the note in express/src/clientRoutes.ts, every new v2 Express route must also be registered in the BitGo server's www/config/routesV2.js so the monolith returns a "call BitGo Express" error. That file lives in the private server repo and needs a corresponding change there; tracked on WAL-1868.
  • Open review item: the Day-1 check proves the submitted shares recombine to the wallet's commonKeychain; cryptographically binding the submitted user share to its seed (and making VSS verification mandatory) is an open review discussion tracked internally on WAL-1868.
  • Out of scope for Day 1 (later tickets): external-signer proxying, walletPassphrase support, on-chain multisig, TSS ECDSA, and TSS EdDSA MPCv2/Redpallas.

@linear-code

linear-code Bot commented Sep 30, 2026

Copy link
Copy Markdown
Contributor

WAL-1868

@github-actions

github-actions Bot commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

⚠️ Unit tests are failing on Node 26.x (Current release line, non-blocking). This is not an LTS version yet, so it does not block merge, but it signals an incompatibility to fix before Node 26.x becomes LTS.

View run

@yashaanand

Copy link
Copy Markdown
Author

@claude review this PR? are there any critical blockers?

@github-actions

github-actions Bot commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Claude finished @yashaanand's task —— View job


PR Review Analysis

Reviewing PR for critical blockers and security issues...

Todo List:

  • Analyze core implementation files
  • Review security handling of key material
  • Check error handling and validation
  • Review test coverage
  • Assess API design and interfaces
  • Identify any critical blockers

✅ No Critical Blockers Found

This PR implements a secure TSS EdDSA key verification feature with excellent security practices. Here's my analysis:

🔒 Security Review - EXCELLENT

Key Material Protection:

  • Private keys never leave the Express process (modules/sdk-core/src/bitgo/utils/tss/keyVerification.ts:78-103)
  • No key material is ever sent to BitGo servers
  • Constant, non-leaking error messages protect against side-channel attacks (keyVerification.ts:54-56)
  • Structured validation errors are redacted to prevent key leakage (express/src/typedRoutes/utils.ts:28-30)
  • Input validation occurs before any cryptographic operations

Error Handling:

  • JSON parsing errors are caught and rethrown with safe messages to prevent Node.js raw input leakage
  • Cryptographic failures surface as constant messages (keyVerification.ts:100)
  • All error paths avoid interpolating user input

🏗️ Architecture Review - SOLID

Clean Separation of Concerns:

  • keyVerification.ts: Pure crypto functions with defensive programming
  • EddsaUtils: Algorithm-specific implementation with extensible interface
  • Express handler: Transport layer with proper HTTP status mapping

Extensible Design:

  • ITssUtils interface allows future algorithm support via supportsVerifyKey() + verifyKey() methods
  • Base class provides safe defaults for unsupported algorithms

🧪 Test Coverage - COMPREHENSIVE

Security-First Testing:

  • 14 unit tests covering input validation, parsing failures, and leak prevention
  • Explicit verification that error messages never echo input material
  • Both positive and negative cryptographic cases tested
  • Express route tests cover all error scenarios with proper HTTP status codes

Edge Cases Covered:

  • Malformed JSON, missing fields, wrong types
  • Optional v field handling in Y shares
  • Transport failures surface as infrastructure errors (500) vs client errors (400)

🚀 Implementation Quality - HIGH

Defensive Programming:

  • Comprehensive input validation before any crypto operations
  • Shape validation for all share types with specific field requirements
  • Hex string validation with proper length checks (keyVerification.ts:7-11)

Performance:

  • Efficient validation pipeline
  • Minimal crypto operations (only when needed)
  • Local key recombination without network calls

📝 Documentation & Standards - GOOD

  • Clear JSDoc comments explaining security constraints
  • Proper TypeScript interfaces
  • Follows conventional commit standards
  • Breaking changes properly documented in release notes

Minor Observations (Non-blocking):

  1. Node 26.x Test Failure: As noted in CI, there's a test failure on Node 26.x, but this is expected for non-LTS versions and doesn't block the PR.

  2. Future Server Route: The PR notes a follow-up task to mirror this route in the server repo - this is tracked appropriately.

🎯 Recommendation: APPROVE

This PR demonstrates exemplary security practices for handling cryptographic material. The implementation is robust, well-tested, and follows all security best practices. No critical issues identified.


Job Run | Branch: yashaanand/WAL-1868-express-verify-key

@yashaanand
yashaanand marked this pull request as ready for review October 1, 2026 14:14
@yashaanand
yashaanand requested review from a team as code owners October 1, 2026 14:14
Comment thread modules/express/src/typedRoutes/utils.ts Outdated
Comment thread modules/sdk-core/src/bitgo/utils/tss/baseTSSUtils.ts Outdated
Comment thread modules/sdk-core/src/bitgo/utils/tss/keyVerification.ts Outdated
Comment thread modules/sdk-core/src/bitgo/utils/tss/keyVerification.ts
Comment thread modules/sdk-core/src/bitgo/utils/tss/keyVerification.ts Outdated
@yashaanand
yashaanand force-pushed the yashaanand/WAL-1868-express-verify-key branch from 7380885 to 30b6909 Compare October 1, 2026 19:10
@yashaanand

Copy link
Copy Markdown
Author

@claude have the previous suggestions been implemented? is the PR ready?

@github-actions

github-actions Bot commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Claude finished @yashaanand's task —— View job


PR Readiness Assessment ✅

I've reviewed the current state of the PR against the previous reviewer suggestions. Here's my analysis:

Todo List:

  • Review current state of files mentioned in previous feedback
  • Check if io-ts codec suggestions were implemented
  • Verify MethodNotImplemented error usage
  • Check sensitive fields validation improvements
  • Assess overall PR readiness
  • Provide final recommendation

🎯 Assessment Result: SUGGESTIONS HAVE BEEN IMPLEMENTED ✅

The PR author has successfully addressed all 5 review suggestions from @mohammadalfaiyazbitgo:

✅ 1. Better Sensitive Fields Validation

Suggestion: "can't we do some better validation and only filter the sensitive fields?"

✅ IMPLEMENTED: The code now uses a dedicated SENSITIVE_REQUEST_KEYS set (modules/express/src/sensitiveRequestKeys.ts:5-13) that specifically lists sensitive keys like prv, privatekey, encryptedprv, etc. The validation logic in utils.ts:32 checks if any path segment matches these sensitive keys and redacts only those values.

✅ 2. MethodNotImplemented Error Usage

Suggestion: "I think MethodNotImplemented makes more semantic sense here?"

✅ IMPLEMENTED: The base class now properly uses MethodNotImplementedError() at baseTSSUtils.ts:270 instead of a generic Error.

✅ 3-5. IO-TS Codec Implementation

Suggestions: Multiple comments about using io-ts codecs for key validation and parsing.

✅ IMPLEMENTED: The key verification module (keyVerification.ts) now implements comprehensive io-ts codecs:

  • Hex32Bytes codec for 32-byte hex validation (keyVerification.ts:11-16)
  • UShareCodec for U-share structure validation (keyVerification.ts:18-25)
  • YShareCodec for Y-share structure validation with optional VSS (keyVerification.ts:32-41)
  • UserSigningMaterialCodec for complete signing material validation (keyVerification.ts:43-47)
  • Robust error handling that prevents key material leakage (keyVerification.ts:54-69)

🔒 Security Improvements Maintained

  • All key material protection mechanisms remain intact
  • Constant-time error messages preserved
  • No sensitive data leakage in error responses

🏗️ Code Quality Enhancements

  • Type safety improved with proper io-ts validation
  • Better separation of concerns with dedicated sensitive keys module
  • More semantic error types for unsupported operations

📋 Recommendation: READY TO MERGE ✅

The PR has successfully implemented all reviewer feedback while maintaining the excellent security posture. The implementation is now more robust with proper type validation and better error handling semantics.


Job Run | Branch: yashaanand/WAL-1868-express-verify-key

@pranavjain97 pranavjain97 left a comment

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.

clean otherwise. blocked on verifyKey returning match: true for a wrong seed

const MPC = await Eddsa.initialize();
let combinedKey: KeyCombine;
try {
combinedKey = MPC.keyCombine(signingMaterial.uShare, [signingMaterial.bitgoYShare, signingMaterial.backupYShare]);

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.

a wrong uShare.seed with correct public fields returns match: true. keyCombine only uses the seed to derive u, which is never compared (repro'd locally). regenerate y from the seed with keyShare and compare it to uShare.y. same gap for bitgoYShare.u when v is absent.

Adds a boolean key-verification path so a caller that holds its own TSS
user signing material can learn up front whether it belongs to a wallet,
instead of discovering a mismatch only when a signature fails, and exposes
it as an Express route so REST callers get the same answer.

- sdk-core: pure helpers in utils/tss/keyVerification parse and shape-check
  user signing material with constant, non-leaking errors (the shape check
  keeps malformed input away from the combine routine, whose raw errors
  interpolate submitter-controlled fields) and recombine it to compare
  against a commonKeychain
- sdk-core: ITssUtils gains verifyKey and supportsVerifyKey; the base
  implementation reports unsupported and EddsaUtils implements the EdDSA
  MPCv1 check, so ECDSA/MPCv2/on-chain wallets reject without a keychain
  request; TypeScript consumers implementing ITssUtils may need to add the
  new members; Wallet.verifyKey fetches only the user keychain and returns
  the result object verbatim for the Express response body; walletPassphrase
  is deliberately not accepted
- express: typed route and handler for POST /api/v2/:coin/wallet/:id/verifyKey,
  with 400s for unsupported wallet types and malformed material; errors with
  an HTTP status or a transport-level code keep their semantics instead of
  being masked as a 400; the decode-error formatter now redacts structured
  values so key material sent with the wrong field type cannot be echoed
  back; the corresponding server-side mirror route is a follow-up in the
  server repo

Ticket: WAL-1868
@yashaanand
yashaanand force-pushed the yashaanand/WAL-1868-express-verify-key branch from 30b6909 to 081c90d Compare October 1, 2026 20:07
@yashaanand

Copy link
Copy Markdown
Author

@claude was pranav's change request implemented? any blockers?

This branch has not been deployed

No deployments
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.

3 participants