diff --git a/modules/abstract-lightning/package.json b/modules/abstract-lightning/package.json index d116d23813..cd41ce46e6 100644 --- a/modules/abstract-lightning/package.json +++ b/modules/abstract-lightning/package.json @@ -39,7 +39,7 @@ ] }, "dependencies": { - "@bitgo/public-types": "6.48.0", + "@bitgo/public-types": "6.50.0", "@bitgo/sdk-core": "^38.4.0", "@bitgo/statics": "^59.0.0", "@bitgo/utxo-lib": "^11.24.1", diff --git a/modules/bitgo/package.json b/modules/bitgo/package.json index 9ee5543846..3c5d238232 100644 --- a/modules/bitgo/package.json +++ b/modules/bitgo/package.json @@ -142,7 +142,7 @@ "superagent": "^9.0.1" }, "devDependencies": { - "@bitgo/public-types": "6.48.0", + "@bitgo/public-types": "6.50.0", "@bitgo/sdk-opensslbytes": "^2.1.0", "@bitgo/sdk-test": "^9.1.61", "@openpgp/web-stream-tools": "0.0.14", diff --git a/modules/express/package.json b/modules/express/package.json index 043c8f5386..cd7e9ed69a 100644 --- a/modules/express/package.json +++ b/modules/express/package.json @@ -60,7 +60,7 @@ "superagent": "^9.0.1" }, "devDependencies": { - "@bitgo/public-types": "6.48.0", + "@bitgo/public-types": "6.50.0", "@bitgo/sdk-lib-mpc": "^10.15.0", "@bitgo/sdk-test": "^9.1.61", "@types/argparse": "^1.0.36", diff --git a/modules/sdk-coin-flrp/package.json b/modules/sdk-coin-flrp/package.json index f441def25e..15e679c52c 100644 --- a/modules/sdk-coin-flrp/package.json +++ b/modules/sdk-coin-flrp/package.json @@ -48,7 +48,7 @@ "nock": "^13.3.1" }, "dependencies": { - "@bitgo/public-types": "6.48.0", + "@bitgo/public-types": "6.50.0", "@bitgo/sdk-core": "^38.4.0", "@bitgo/secp256k1": "^1.11.0", "@bitgo/statics": "^59.0.0", diff --git a/modules/sdk-coin-sol/package.json b/modules/sdk-coin-sol/package.json index 5459ec2b80..5ac3b1a9e2 100644 --- a/modules/sdk-coin-sol/package.json +++ b/modules/sdk-coin-sol/package.json @@ -57,7 +57,7 @@ }, "dependencies": { "@bitgo/logger": "^1.4.0", - "@bitgo/public-types": "6.48.0", + "@bitgo/public-types": "6.50.0", "@bitgo/sdk-core": "^38.4.0", "@bitgo/sdk-lib-mpc": "^10.15.0", "@bitgo/statics": "^59.0.0", diff --git a/modules/sdk-core/package.json b/modules/sdk-core/package.json index 40b006dfed..a40fe30d6f 100644 --- a/modules/sdk-core/package.json +++ b/modules/sdk-core/package.json @@ -40,7 +40,7 @@ ] }, "dependencies": { - "@bitgo/public-types": "6.48.0", + "@bitgo/public-types": "6.50.0", "@bitgo/sdk-lib-mpc": "^10.15.0", "@bitgo/secp256k1": "^1.11.0", "@bitgo/sjcl": "^1.1.0", diff --git a/modules/sdk-core/src/bitgo/safe/iSafes.ts b/modules/sdk-core/src/bitgo/safe/iSafes.ts index 7a0212b8cd..8ad684c2c3 100644 --- a/modules/sdk-core/src/bitgo/safe/iSafes.ts +++ b/modules/sdk-core/src/bitgo/safe/iSafes.ts @@ -24,10 +24,14 @@ export interface CreateSafeOptions { /** * Handle returned by `initializeSafe`, threaded into the key ceremonies and finalize. + * + * `enabledRootSlots` is the server-decided (Flipt, evaluated once at initialize) set of root-key + * slots to generate; absent (older WP) falls back to all 4 slots. * @experimental */ export interface SafeCreationHandle { safeId: string; + enabledRootSlots?: InitializeSafeResponse['enabledRootSlots']; } /** diff --git a/modules/sdk-core/src/bitgo/safe/safes.ts b/modules/sdk-core/src/bitgo/safe/safes.ts index ff499c40de..709c892793 100644 --- a/modules/sdk-core/src/bitgo/safe/safes.ts +++ b/modules/sdk-core/src/bitgo/safe/safes.ts @@ -94,7 +94,11 @@ export class Safes implements ISafes { */ async generateSafe(params: CreateSafeOptions): Promise { const safe = await this.initializeSafe({ label: params.label }); - const rootKeys = await this.createSafeKeys({ ...params, safeId: safe.id }); + const rootKeys = await this.createSafeKeys({ + ...params, + safeId: safe.id, + enabledRootSlots: safe.enabledRootSlots, + }); return await this.finalizeSafe(safe.id, rootKeys); } @@ -123,20 +127,24 @@ export class Safes implements ISafes { * @experimental */ async createSafeKeys(params: CreateSafeOptions & SafeCreationHandle): Promise { - const { safeId, passphrase } = params; + const { safeId, passphrase, enabledRootSlots } = params; const enterprise = this.enterpriseId; + // Absent `enabledRootSlots` (older WP, pre-gating) ⇒ all 4, preserving current behavior. // `slots` MUST stay index-aligned with the Promise.allSettled array below. Ordered by scheme: // the two multisig roots first, then the two MPC roots. - const slots: RootKeyType[] = ['secp256k1Multisig', 'ed25519Multisig', 'ecdsaMpc', 'eddsaMpc']; - const results = await Promise.allSettled([ + const allSlots: RootKeyType[] = ['secp256k1Multisig', 'ed25519Multisig', 'ecdsaMpc', 'eddsaMpc']; + const enabled = new Set(enabledRootSlots ?? allSlots); + const slots = allSlots.filter((slot) => enabled.has(slot)); + const ceremonies: Record Promise> = { // Phase 2.1 — multisig roots (①④): local user/backup keypairs + BitGo key, all safeId-tagged. - this.createMultisigRoot('secp256k1Multisig', safeId, passphrase, enterprise), - this.createMultisigRoot('ed25519Multisig', safeId, passphrase, enterprise), + secp256k1Multisig: () => this.createMultisigRoot('secp256k1Multisig', safeId, passphrase, enterprise), + ed25519Multisig: () => this.createMultisigRoot('ed25519Multisig', safeId, passphrase, enterprise), // Phase 2.2 — MPC roots (②③): the existing DKLS (②) and EdDSA (③) ceremonies, safeId threaded. - this.createMpcRoot('ecdsaMpc', safeId, passphrase, enterprise), - this.createMpcRoot('eddsaMpc', safeId, passphrase, enterprise), - ]); + ecdsaMpc: () => this.createMpcRoot('ecdsaMpc', safeId, passphrase, enterprise), + eddsaMpc: () => this.createMpcRoot('eddsaMpc', safeId, passphrase, enterprise), + }; + const results = await Promise.allSettled(slots.map((slot) => ceremonies[slot]())); // Single pass over the settled results: `status === 'fulfilled'` narrows `.value` to a // RootKeyTriplet (no cast needed), and rejections are collected per-slot for the error below. diff --git a/modules/sdk-core/test/unit/bitgo/safe/safes.ts b/modules/sdk-core/test/unit/bitgo/safe/safes.ts index 941c340265..faae2b5b97 100644 --- a/modules/sdk-core/test/unit/bitgo/safe/safes.ts +++ b/modules/sdk-core/test/unit/bitgo/safe/safes.ts @@ -164,6 +164,26 @@ describe('Safes', function () { started.should.equal(4); }); + it('runs only the enabled ceremonies when enabledRootSlots is a subset', async function () { + const result = await safes.createSafeKeys({ + label: 'my safe', + passphrase: 'pw', + safeId: 'safe-1', + enabledRootSlots: ['ecdsaMpc', 'eddsaMpc'], + }); + + result.should.deepEqual({ + rootKeys: { + hot: { + ecdsaMpc: ['hteth-user', 'hteth-backup', 'hteth-bitgo'], + eddsaMpc: ['tsol-user', 'tsol-backup', 'tsol-bitgo'], + }, + }, + }); + keychainsByCoin.should.not.have.property('tbtc'); + keychainsByCoin.should.not.have.property('txlm'); + }); + it('archives the safe and throws listing every failed ceremony', async function () { // Two ceremonies fail (an MPC and a multisig root). keychainsByCoin['hteth'] = makeKeychains('hteth'); @@ -236,8 +256,12 @@ describe('Safes', function () { }); describe('generateSafe', function () { - it('chains initialize → createSafeKeys → finalize, threading the safeId', async function () { - const initializing = { id: 'test-safe-id', status: 'initializing' as const }; + it('chains initialize → createSafeKeys → finalize, threading the safeId and enabledRootSlots', async function () { + const initializing = { + id: 'test-safe-id', + status: 'initializing' as const, + enabledRootSlots: ['ecdsaMpc', 'eddsaMpc'] as ('ecdsaMpc' | 'eddsaMpc')[], + }; const rootKeys = { rootKeys: { hot: {} } } as any; const initStub = sinon.stub(safes, 'initializeSafe').resolves(initializing); const keysStub = sinon.stub(safes, 'createSafeKeys').resolves(rootKeys); @@ -246,7 +270,12 @@ describe('Safes', function () { const result = await safes.generateSafe({ label: 'my safe', passphrase: 'pw' }); sinon.assert.calledWithMatch(initStub, { label: 'my safe' }); - sinon.assert.calledWithMatch(keysStub, { label: 'my safe', passphrase: 'pw', safeId: 'test-safe-id' }); + sinon.assert.calledWithMatch(keysStub, { + label: 'my safe', + passphrase: 'pw', + safeId: 'test-safe-id', + enabledRootSlots: ['ecdsaMpc', 'eddsaMpc'], + }); sinon.assert.calledWith(finalizeStub, 'test-safe-id', rootKeys); sinon.assert.callOrder(initStub, keysStub, finalizeStub); result.status().should.equal('active'); diff --git a/yarn.lock b/yarn.lock index ac0b1c4b14..2ff1ffdf63 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1032,10 +1032,10 @@ monocle-ts "^2.3.13" newtype-ts "^0.3.5" -"@bitgo/public-types@6.48.0": - version "6.48.0" - resolved "https://registry.npmjs.org/@bitgo/public-types/-/public-types-6.48.0.tgz#acad90d78e91e92c7a31677403b77a8b2e44b40a" - integrity sha512-G39FD9Ul5TFReKN07fQ8jeKEY0vkkV0YJuu2pQMWFQ7EEdqByEF1PYr28Jly54X8J8/kxCziTQ56ogwm0VWhng== +"@bitgo/public-types@6.50.0": + version "6.50.0" + resolved "https://registry.npmjs.org/@bitgo/public-types/-/public-types-6.50.0.tgz#48268ab9dbf92e9556f7c77956c18bf39fdc9048" + integrity sha512-t7rlSqeX0b6XSCvuu5XutzcxlpNWlsOD1JBdeTSSDCVbOZvHRPJcyL0/8vkfaK2bVnbpsm31O8sssnwtiugxyA== dependencies: fp-ts "^2.0.0" io-ts "npm:@bitgo-forks/io-ts@2.1.4"