From f9b64cab837abaa89e0ef9c439901b9ebe0d8376 Mon Sep 17 00:00:00 2001 From: Daniel Peng Date: Mon, 7 Sep 2026 14:22:48 -0400 Subject: [PATCH] fix: align child derivation path field with WP Ticket: WCN-2599 --- modules/bitgo/test/v2/unit/keychains.ts | 10 +++++----- .../sdk-core/src/bitgo/keychain/iKeychains.ts | 4 ++-- .../sdk-core/src/bitgo/keychain/keychains.ts | 4 ++-- modules/sdk-core/src/bitgo/safe/safe.ts | 2 +- .../sdk-core/src/bitgo/wallet/safeKeychain.ts | 12 ++++++------ .../test/unit/bitgo/keychain/keychains.ts | 19 +++++++++++++++++++ modules/sdk-core/test/unit/bitgo/safe/safe.ts | 6 +++--- .../test/unit/bitgo/wallet/safeGetUserPrv.ts | 18 +++++++++--------- .../test/unit/bitgo/wallet/safeShareWallet.ts | 4 ++-- 9 files changed, 49 insertions(+), 30 deletions(-) diff --git a/modules/bitgo/test/v2/unit/keychains.ts b/modules/bitgo/test/v2/unit/keychains.ts index dd336d2035..1847d0a43e 100644 --- a/modules/bitgo/test/v2/unit/keychains.ts +++ b/modules/bitgo/test/v2/unit/keychains.ts @@ -43,25 +43,25 @@ describe('V2 Keychains', function () { scope.done(); }); - it('should add a safe child keychain with derivedFromParentWithHardenedPath', async function () { + it('should add a safe child keychain with derivedFromParentWithPath', async function () { const scope = nock(bgUrl) .post('/api/v2/tltc/key', function (body) { body.pub.should.equal('pub'); body.parent.should.equal('parent-key-id'); body.safeId.should.equal('safe-id'); - body.derivedFromParentWithHardenedPath.should.equal("m/7'"); + body.derivedFromParentWithPath.should.equal("m/7'"); should.equal(body.path, undefined); should.equal(body.derivedFromParentWithSeed, undefined); return true; }) - .reply(200, { id: 'child-key-id', derivedFromParentWithHardenedPath: "m/7'", path: '/0/0' }); + .reply(200, { id: 'child-key-id', derivedFromParentWithPath: "m/7'", path: '/0/0' }); const result = await keychains.add({ pub: 'pub', parent: 'parent-key-id', safeId: 'safe-id', - derivedFromParentWithHardenedPath: "m/7'", + derivedFromParentWithPath: "m/7'", }); - result.derivedFromParentWithHardenedPath.should.equal("m/7'"); + result.derivedFromParentWithPath.should.equal("m/7'"); scope.done(); }); }); diff --git a/modules/sdk-core/src/bitgo/keychain/iKeychains.ts b/modules/sdk-core/src/bitgo/keychain/iKeychains.ts index aabb3088db..bfc7038cc5 100644 --- a/modules/sdk-core/src/bitgo/keychain/iKeychains.ts +++ b/modules/sdk-core/src/bitgo/keychain/iKeychains.ts @@ -50,7 +50,7 @@ export interface Keychain { derivationPath?: string; derivedFromParentWithSeed?: string; /** Hardened path from the safe parent (`m/'`). @experimental */ - derivedFromParentWithHardenedPath?: string; + derivedFromParentWithPath?: string; /** Safe root key id this child key was derived from (WCN-1172). */ parent?: string; commonPub?: string; @@ -149,7 +149,7 @@ export interface AddKeychainOptions { enterprise?: string; derivedFromParentWithSeed?: string; /** Hardened path from the safe parent (`m/'`). @experimental */ - derivedFromParentWithHardenedPath?: string; + derivedFromParentWithPath?: string; /** Safe user-root key id this child was derived from. @experimental */ parent?: string; disableKRSEmail?: boolean; diff --git a/modules/sdk-core/src/bitgo/keychain/keychains.ts b/modules/sdk-core/src/bitgo/keychain/keychains.ts index 26b5177fb0..ca78a8c88a 100644 --- a/modules/sdk-core/src/bitgo/keychain/keychains.ts +++ b/modules/sdk-core/src/bitgo/keychain/keychains.ts @@ -267,7 +267,7 @@ export class Keychains implements IKeychains { 'originalPasscodeEncryptionCode', 'enterprise', 'derivedFromParentWithSeed', - 'derivedFromParentWithHardenedPath', + 'derivedFromParentWithPath', 'parent', 'safeId', ] @@ -297,7 +297,7 @@ export class Keychains implements IKeychains { originalPasscodeEncryptionCode: params.originalPasscodeEncryptionCode, enterprise: params.enterprise, derivedFromParentWithSeed: params.derivedFromParentWithSeed, - derivedFromParentWithHardenedPath: params.derivedFromParentWithHardenedPath, + derivedFromParentWithPath: params.derivedFromParentWithPath, parent: params.parent, disableKRSEmail: params.disableKRSEmail, krsSpecific: params.krsSpecific, diff --git a/modules/sdk-core/src/bitgo/safe/safe.ts b/modules/sdk-core/src/bitgo/safe/safe.ts index 409922aad6..a1b198c7df 100644 --- a/modules/sdk-core/src/bitgo/safe/safe.ts +++ b/modules/sdk-core/src/bitgo/safe/safe.ts @@ -157,7 +157,7 @@ export class Safe implements ISafe { keyType: 'independent', parent: userRootId, safeId: this.id(), - derivedFromParentWithHardenedPath, + derivedFromParentWithPath: derivedFromParentWithHardenedPath, }); const childId = child.id; if (childId.length === 0) { diff --git a/modules/sdk-core/src/bitgo/wallet/safeKeychain.ts b/modules/sdk-core/src/bitgo/wallet/safeKeychain.ts index 717efe9bab..13017d0649 100644 --- a/modules/sdk-core/src/bitgo/wallet/safeKeychain.ts +++ b/modules/sdk-core/src/bitgo/wallet/safeKeychain.ts @@ -109,7 +109,7 @@ type ResolveSafeKeyMaterialParams = SafeKeyMaterialBaseParams & { * Shared core that resolves safe key material for a pub-only safe child. Returns the CHILD * `{prv, pub}` only — never the root — so the root can never leak into a share document. * - * Onchain secp256k1: decrypt root, hardened-derive at `derivedFromParentWithHardenedPath` + * Onchain secp256k1: decrypt root, hardened-derive at `derivedFromParentWithPath` * (`m/'`), and verify the registered pub. TSS and ed25519 onchain throw via * `makeNotImplementedError` — the caller constructs its own error class + message, so the * guard set stays shared while signing/sharing report their own errors. @@ -142,13 +142,13 @@ async function resolveSafeKeyMaterial(params: ResolveSafeKeyMaterialParams): Pro if (!childKeychain.pub) { throw new Error(`Safe wallet ${walletId}: child keychain is missing pub for pre-sign verification`); } - if (childKeychain.derivedFromParentWithHardenedPath === undefined) { - throw new Error(`Safe wallet ${walletId}: child keychain is missing derivedFromParentWithHardenedPath`); + if (childKeychain.derivedFromParentWithPath === undefined) { + throw new Error(`Safe wallet ${walletId}: child keychain is missing derivedFromParentWithPath`); } const derived = deriveSafeChildHardenedFromXprv( rootPrv, - parseDerivedFromParentWithHardenedPath(childKeychain.derivedFromParentWithHardenedPath) + parseDerivedFromParentWithHardenedPath(childKeychain.derivedFromParentWithPath) ); if (derived.pub !== childKeychain.pub) { throw new SafeDerivedPublicKeyMismatchError(walletId, childKeychain.pub, derived.pub); @@ -163,7 +163,7 @@ async function resolveSafeKeyMaterial(params: ResolveSafeKeyMaterialParams): Pro /** * Resolve signing material for a safe owner (child key has no encryptedPrv). * - * Onchain secp256k1: decrypt root, hardened-derive at `derivedFromParentWithHardenedPath` + * Onchain secp256k1: decrypt root, hardened-derive at `derivedFromParentWithPath` * (`m/'`), and verify the registered pub. * TSS and ed25519 onchain: throw — do not return root material or BIP32-derive the wrong curve. * @@ -205,7 +205,7 @@ function safeShareSlotDetail(slot: SafeKeyMaterialSlot, params: ResolveSafeChild /** * Resolve sharing material for a safe owner (child key has no encryptedPrv). * - * Onchain secp256k1: decrypt root, hardened-derive at `derivedFromParentWithHardenedPath` + * Onchain secp256k1: decrypt root, hardened-derive at `derivedFromParentWithPath` * (`m/'`), verify the registered pub, and return the CHILD `{prv, pub}` — never the root. * TSS and ed25519 onchain: throw `SafeShareNotImplementedError` naming the slot + blocker. * diff --git a/modules/sdk-core/test/unit/bitgo/keychain/keychains.ts b/modules/sdk-core/test/unit/bitgo/keychain/keychains.ts index 120ba51def..7b4f7959c4 100644 --- a/modules/sdk-core/test/unit/bitgo/keychain/keychains.ts +++ b/modules/sdk-core/test/unit/bitgo/keychain/keychains.ts @@ -43,6 +43,25 @@ describe('Keychains.createBackup', function () { keychains = buildKeychains(); }); + describe('safe child key registration', function () { + it('serializes the hardened derivation path', async function () { + await keychains.add({ + pub: XPUB, + source: 'user', + keyType: 'independent', + parent: 'user-root-id', + safeId: SAFE_ID, + derivedFromParentWithPath: "m/7'", + }); + + const derivedPath = sentBody().derivedFromParentWithPath; + if (typeof derivedPath !== 'string') { + throw new Error('expected derivedFromParentWithPath to be serialized'); + } + derivedPath.should.equal("m/7'"); + }); + }); + describe('safe ed25519Multisig root (slot ④)', function () { it('posts a 108-char composite pub built from the generated key', async function () { await keychains.createBackup({ passphrase: 'pw', safeId: SAFE_ID }); diff --git a/modules/sdk-core/test/unit/bitgo/safe/safe.ts b/modules/sdk-core/test/unit/bitgo/safe/safe.ts index bf7ca46cd6..94aedde4c3 100644 --- a/modules/sdk-core/test/unit/bitgo/safe/safe.ts +++ b/modules/sdk-core/test/unit/bitgo/safe/safe.ts @@ -192,7 +192,7 @@ describe('Safe', function () { keyType: 'independent', parent: 'user-root-id', safeId: 'test-safe-id', - derivedFromParentWithHardenedPath: "m/0'", + derivedFromParentWithPath: "m/0'", }); addArgs.should.not.have.property('encryptedPrv'); addArgs.should.not.have.property('derivedFromParentWithSeed'); @@ -214,7 +214,7 @@ describe('Safe', function () { mintedSafeId.should.equal('test-safe-id'); }); - it('registers the child with derivedFromParentWithHardenedPath at a non-zero mint index', async function () { + it('registers the child with derivedFromParentWithPath at a non-zero mint index', async function () { derivationQuery.returns({ result: sinon.stub().resolves({ slot: 'secp256k1Multisig', index: 7 }), }); @@ -224,7 +224,7 @@ describe('Safe', function () { const addArgs = keychainsAdd.firstCall.args[0]; addArgs.pub.should.equal(childAt7.pub); - addArgs.derivedFromParentWithHardenedPath.should.equal("m/7'"); + addArgs.derivedFromParentWithPath.should.equal("m/7'"); addArgs.should.not.have.property('path'); addArgs.should.not.have.property('derivedFromParentWithSeed'); }); diff --git a/modules/sdk-core/test/unit/bitgo/wallet/safeGetUserPrv.ts b/modules/sdk-core/test/unit/bitgo/wallet/safeGetUserPrv.ts index f84d51857a..17338f3a63 100644 --- a/modules/sdk-core/test/unit/bitgo/wallet/safeGetUserPrv.ts +++ b/modules/sdk-core/test/unit/bitgo/wallet/safeGetUserPrv.ts @@ -121,7 +121,7 @@ describe('WCN-1200 safe child getUserPrv root-fetch detour', function () { hardened.prv.should.not.eql(softDerivedPrv); }); - it('parses derivedFromParentWithHardenedPath as m/ primed', function () { + it('parses the hardened derivation path as m/ primed', function () { parseDerivedFromParentWithHardenedPath("m/0'").should.eql(0); parseDerivedFromParentWithHardenedPath("m/123'").should.eql(123); (() => parseDerivedFromParentWithHardenedPath('m/0')).should.throw(/derivedFromParentWithHardenedPath/); @@ -256,7 +256,7 @@ describe('WCN-1200 safe child getUserPrv root-fetch detour', function () { pub: hardened.pub, type: 'independent', parent: rootKeyId, - derivedFromParentWithHardenedPath: "m/123'", + derivedFromParentWithPath: "m/123'", }, walletPassphrase: passphrase, }); @@ -267,7 +267,7 @@ describe('WCN-1200 safe child getUserPrv root-fetch detour', function () { mockBaseCoin.deriveKeyWithSeed.notCalled.should.be.true(); }); - it('requires derivedFromParentWithHardenedPath on the child keychain', async function () { + it('requires derivedFromParentWithPath on the child keychain', async function () { const wallet = makeWallet({ safe: 'safe-id-1' }); keychainsGetStub.resolves({ id: rootKeyId, @@ -287,10 +287,10 @@ describe('WCN-1200 safe child getUserPrv root-fetch detour', function () { }, walletPassphrase: passphrase, }) - .should.be.rejectedWith(/missing derivedFromParentWithHardenedPath/); + .should.be.rejectedWith(/missing derivedFromParentWithPath/); }); - it('fails closed when derivedFromParentWithHardenedPath does not match the registered pub', async function () { + it('fails closed when derivedFromParentWithPath does not match the registered pub', async function () { const wallet = makeWallet({ safe: 'safe-id-1' }); keychainsGetStub.resolves({ id: rootKeyId, @@ -307,14 +307,14 @@ describe('WCN-1200 safe child getUserPrv root-fetch detour', function () { pub: hardened.pub, type: 'independent', parent: rootKeyId, - derivedFromParentWithHardenedPath: "m/0'", + derivedFromParentWithPath: "m/0'", }, walletPassphrase: passphrase, }) .should.be.rejectedWith(SafeDerivedPublicKeyMismatchError); }); - it('rejects a malformed derivedFromParentWithHardenedPath', async function () { + it('rejects a malformed derivedFromParentWithPath', async function () { const wallet = makeWallet({ safe: 'safe-id-1' }); keychainsGetStub.resolves({ id: rootKeyId, @@ -331,7 +331,7 @@ describe('WCN-1200 safe child getUserPrv root-fetch detour', function () { pub: hardened.pub, type: 'independent', parent: rootKeyId, - derivedFromParentWithHardenedPath: "not-a-path'", + derivedFromParentWithPath: "not-a-path'", }, walletPassphrase: passphrase, }) @@ -395,7 +395,7 @@ describe('WCN-1200 safe child getUserPrv root-fetch detour', function () { pub: 'xpub-wrong-registered-key', type: 'independent', parent: rootKeyId, - derivedFromParentWithHardenedPath: "m/123'", + derivedFromParentWithPath: "m/123'", }, walletPassphrase: passphrase, }) diff --git a/modules/sdk-core/test/unit/bitgo/wallet/safeShareWallet.ts b/modules/sdk-core/test/unit/bitgo/wallet/safeShareWallet.ts index cf25a59fc0..098dd22c55 100644 --- a/modules/sdk-core/test/unit/bitgo/wallet/safeShareWallet.ts +++ b/modules/sdk-core/test/unit/bitgo/wallet/safeShareWallet.ts @@ -50,7 +50,7 @@ describe('Safe wallet spend sharing', function () { pub: hardened.pub, type: 'independent' as const, parent: rootKeyId, - derivedFromParentWithHardenedPath: "m/123'", + derivedFromParentWithPath: "m/123'", }); const publicOnlyKeychain = (id: string) => ({ id, pub: 'pub-' + id, type: 'independent' as const }); const rootKeychain = { @@ -189,7 +189,7 @@ describe('Safe wallet spend sharing', function () { id === rootKeyId ? rootKeychain : id === 'user-key' - ? { ...childKeychain(id), pub: 'wrong-child-pub', derivedFromParentWithHardenedPath: "m/123'" } + ? { ...childKeychain(id), pub: 'wrong-child-pub', derivedFromParentWithPath: "m/123'" } : publicOnlyKeychain(id) ) );