Skip to content

feat(sdk-coin-near): MPCv2 signed hot recovery - #9485

Open
bitgo-ai-agent-dev[bot] wants to merge 1 commit into
masterfrom
WCI-1223-near-mpcv2-signed-hot-recovery
Open

feat(sdk-coin-near): MPCv2 signed hot recovery#9485
bitgo-ai-agent-dev[bot] wants to merge 1 commit into
masterfrom
WCI-1223-near-mpcv2-signed-hot-recovery

Conversation

@bitgo-ai-agent-dev

Copy link
Copy Markdown
Contributor

What

  • Import getEddsaSigningMaterial and signEddsaMpcV2RecoveryTx from @bitgo/sdk-core in near.ts
  • Add isMpcv2SigningMaterial() private method: decrypts the user keycard once and returns true when the plaintext is CBOR-encoded (MPCv2 Silence Labs format) vs JSON (MPCv1 Zengo format)
  • Refactor signRecoveryTransaction() to accept an isMpcV2 boolean: when true, routes to signEddsaMpcV2RecoveryTx (MPS DSG local signing) instead of the legacy EDDSAMethods.getTSSSignature path
  • Detect isMpcV2 once at the top of recover() and thread the flag through both the native NEAR transfer path and the NEP141 FT token recovery path (recoverNearToken)
  • Add three unit tests: native NEAR MPCv2 signed recovery, NEP141 FT token MPCv2 signed recovery, and bitgoKey/commonKeyChain mismatch error

Why

NEAR wallets provisioned with the new Silence Labs (MPCv2) key material cannot be recovered using the legacy getTSSSignature path because the keycard format is different (CBOR base64 reduced share vs JSON uShare/yShare). This adds the same MPCv2 detection + signing path introduced for SOL in WCI-398, enabling hot signed recovery for MPCv2 NEAR wallets without requiring any new caller-visible parameters. Detection is automatic based on keycard format.

Test plan

  • should route to MPCv2 path for native NEAR recovery when keycard is MPCv2 — verifies signed tx returned, getTSSSignature not called
  • should throw when MPCv2 commonKeyChain does not match bitgoKey — verifies mismatch error
  • should route to MPCv2 path for NEP141 FT token recovery when keycard is MPCv2 — verifies token recovery routes to MPCv2
  • All existing MPCv1 and unsigned sweep tests pass unchanged (165 total unit tests)

Ticket: WCI-1223

@linear-code

linear-code Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

WCI-1223

@ralph-bitgo
ralph-bitgo Bot force-pushed the WCI-1223-near-mpcv2-signed-hot-recovery branch 2 times, most recently from ed8bc24 to d062b1f Compare August 12, 2026 12:29
Add MPCv2 detection and signing to Near.recover() alongside the
existing MPCv1 path.

What changed:
- Import getEddsaSigningMaterial and signEddsaMpcV2RecoveryTx from
  @bitgo/sdk-core in near.ts
- Add isMpcv2SigningMaterial() private method that decrypts the user
  keycard once and returns true when the plaintext is CBOR (MPCv2)
- Refactor signRecoveryTransaction() to accept an isMpcV2 boolean; when
  true it calls signEddsaMpcV2RecoveryTx (MPS DSG) instead of the
  legacy EDDSAMethods.getTSSSignature path
- Call isMpcv2SigningMaterial() once at the top of recover() and thread
  the isMpcV2 flag into both the native NEAR and NEP141 FT token paths
- Add three new unit tests: native MPCv2 signed recovery, NEP141 FT
  token MPCv2 signed recovery, and bitgoKey/commonKeyChain mismatch

Why:
NEAR wallets provisioned with the new Silence Labs (MPCv2) key material
cannot be recovered with the Zengo-era getTSSSignature path because the
keycard format is different (CBOR base64 vs JSON uShare/yShare). This
adds the same MPCv2 detection+signing path that was introduced for SOL
in WCI-398, enabling hot recovery for MPCv2 NEAR wallets without any
new caller-visible parameters.

Ticket: WCI-1223
Session-Id: 8de2a998-4754-4499-82b5-49167b8d9fd6
Task-Id: 7e0eb924-0c33-4cc6-9402-c71587bb14a5
@bitgo-ai-agent-dev
bitgo-ai-agent-dev Bot force-pushed the WCI-1223-near-mpcv2-signed-hot-recovery branch from d062b1f to 62ceec2 Compare August 13, 2026 06:07
@ralph-bitgo
ralph-bitgo Bot force-pushed the WCI-1223-near-mpcv2-signed-hot-recovery branch from 62ceec2 to c4df52d Compare August 13, 2026 06:07
@bitgo-ai-agent-dev
bitgo-ai-agent-dev Bot force-pushed the WCI-1223-near-mpcv2-signed-hot-recovery branch from c4df52d to e2660bc Compare August 13, 2026 06:07
@Marzooqa
Marzooqa marked this pull request as ready for review August 13, 2026 10:21
@Marzooqa
Marzooqa requested a review from a team as a code owner August 13, 2026 10:21
@Marzooqa
Marzooqa requested a review from vibhavgo August 13, 2026 10:21

@vibhavgo vibhavgo 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.

Code review — local review against the MPCv2 signed hot recovery quality bar established across DOT/POLYX/SUI/ADA. Both helpers (getEddsaSigningMaterial, signEddsaMpcV2RecoveryTx) are used correctly from @bitgo/sdk-core.


1. isMpcV2 evaluated before isUnsignedSweep guard

const isUnsignedSweep = !params.userKey && !params.backupKey && !params.walletPassphrase;
const isMpcV2 = await this.isMpcv2SigningMaterial(...);  // called even on unsigned sweep path

If walletPassphrase is present but userKey is absent, the throw surfaces from inside the private isMpcv2SigningMaterial helper rather than from the main recover() guard. The error message is correct but the source is unexpected. Prefer gating the call behind !isUnsignedSweep, or add explicit guards in recover() before calling the helper.


2. No MPCv1 regression test

The describe('Recover Transactions (MPCv2):') block has no test asserting that getTSSSignature is called when an MPCv1 keycard is used. A false-positive v2 detection would silently fail with no test catching it. All six prior coins (DOT / POLYX / SUI / ADA / DOT-consolidations / SUI-consolidations) include this test.


3. No guard tests in the MPCv2 describe

Missing:

  • should throw 'missing userKey' when walletPassphrase is set but userKey is omitted
  • should throw 'missing backupKey' when walletPassphrase is set but backupKey is omitted

The existing guard tests live in the MPCv1 describe and exercise the MPCv1 code path only.


4. No cryptographic signature verification

The MPCv2 tests check result.hasOwnProperty('serializedTx') but do not verify that the produced signature bytes are a valid Ed25519 signature over the signable payload for the derived public key. tweetnacl is already in sdk-coin-near's package.json. The pattern used in prior PRs:

const rawSig = /* extract 64 bytes from result */;
const isValid = nacl.sign.detached.verify(
  new Uint8Array(signablePayload),
  new Uint8Array(rawSig),
  new Uint8Array(Buffer.from(accountId, 'hex'))
);
isValid.should.be.true();

Reviewed by Claude Code against the MPCv2 recovery PR series baseline.

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