Skip to content

Commit c8a1310

Browse files
authored
fix(sdk-core): persist passkey for onchain generateWallet
2 parents 97ea33a + cb63106 commit c8a1310

2 files changed

Lines changed: 27 additions & 22 deletions

File tree

modules/sdk-core/src/bitgo/wallet/wallets.ts

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -606,18 +606,19 @@ export class Wallets implements IWallets {
606606

607607
// If WebAuthn info is provided, store an additional copy of the private key encrypted
608608
// with the PRF-derived passphrase so the authenticator can later decrypt it.
609+
// Send it as `webauthnInfo` (single object, with enterpriseId) so the atomic
610+
// POST /key persists the passkey — the backend ignores the deprecated `webauthnDevices`.
609611
if (params.webauthnInfo && userKeychain.prv) {
610-
userKeychainParams.webauthnDevices = [
611-
{
612-
otpDeviceId: params.webauthnInfo.otpDeviceId,
613-
prfSalt: params.webauthnInfo.prfSalt,
614-
encryptedPrv: await this.bitgo.encryptAsync({
615-
password: params.webauthnInfo.passphrase,
616-
input: userKeychain.prv,
617-
encryptionVersion: params.encryptionVersion,
618-
}),
619-
},
620-
];
612+
userKeychainParams.webauthnInfo = {
613+
otpDeviceId: params.webauthnInfo.otpDeviceId,
614+
prfSalt: params.webauthnInfo.prfSalt,
615+
enterpriseId: params.enterprise,
616+
encryptedPrv: await this.bitgo.encryptAsync({
617+
password: params.webauthnInfo.passphrase,
618+
input: userKeychain.prv,
619+
encryptionVersion: params.encryptionVersion,
620+
}),
621+
};
621622
}
622623
}
623624

modules/sdk-core/test/unit/bitgo/wallet/walletsWebauthn.ts

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ describe('Wallets - WebAuthn wallet creation', function () {
6565
});
6666

6767
describe('generateWallet with webauthnInfo', function () {
68-
it('should add webauthnDevices to keychain params when webauthnInfo is provided', async function () {
68+
it('should add webauthnInfo (with enterpriseId) to keychain params when webauthnInfo is provided', async function () {
6969
const webauthnInfo = {
7070
otpDeviceId: 'device-123',
7171
prfSalt: 'salt-abc',
@@ -75,16 +75,18 @@ describe('Wallets - WebAuthn wallet creation', function () {
7575
await wallets.generateWallet({
7676
label: 'Test Wallet',
7777
passphrase: 'wallet-passphrase',
78+
enterprise: 'enterprise-123',
7879
webauthnInfo,
7980
});
8081

8182
assert.strictEqual(mockKeychains.add.calledOnce, true);
8283
const addParams = mockKeychains.add.firstCall.args[0];
83-
addParams.should.have.property('webauthnDevices');
84-
addParams.webauthnDevices.should.have.length(1);
85-
addParams.webauthnDevices[0].should.have.property('otpDeviceId', webauthnInfo.otpDeviceId);
86-
addParams.webauthnDevices[0].should.have.property('prfSalt', webauthnInfo.prfSalt);
87-
addParams.webauthnDevices[0].should.have.property('encryptedPrv');
84+
addParams.should.not.have.property('webauthnDevices');
85+
addParams.should.have.property('webauthnInfo');
86+
addParams.webauthnInfo.should.have.property('otpDeviceId', webauthnInfo.otpDeviceId);
87+
addParams.webauthnInfo.should.have.property('prfSalt', webauthnInfo.prfSalt);
88+
addParams.webauthnInfo.should.have.property('enterpriseId', 'enterprise-123');
89+
addParams.webauthnInfo.should.have.property('encryptedPrv');
8890
});
8991

9092
it('should encrypt user private key with the webauthn passphrase', async function () {
@@ -102,7 +104,7 @@ describe('Wallets - WebAuthn wallet creation', function () {
102104

103105
const addParams = mockKeychains.add.firstCall.args[0];
104106
const expectedEncryptedPrv = `encrypted:${webauthnPassphrase}:${userPrv}`;
105-
addParams.webauthnDevices[0].should.have.property('encryptedPrv', expectedEncryptedPrv);
107+
addParams.webauthnInfo.should.have.property('encryptedPrv', expectedEncryptedPrv);
106108
});
107109

108110
it('should also encrypt user private key with wallet passphrase when webauthnInfo is provided', async function () {
@@ -143,19 +145,20 @@ describe('Wallets - WebAuthn wallet creation', function () {
143145
passwordsUsed.should.containEql(webauthnPassphrase);
144146
});
145147

146-
it('should not add webauthnDevices when webauthnInfo is not provided', async function () {
148+
it('should not add webauthnInfo when webauthnInfo is not provided', async function () {
147149
await wallets.generateWallet({
148150
label: 'Test Wallet',
149151
passphrase: 'wallet-passphrase',
150152
});
151153

152154
assert.strictEqual(mockKeychains.add.calledOnce, true);
153155
const addParams = mockKeychains.add.firstCall.args[0];
156+
addParams.should.not.have.property('webauthnInfo');
154157
addParams.should.not.have.property('webauthnDevices');
155158
});
156159

157-
it('should not add webauthnDevices when userKey is explicitly provided (no prv available)', async function () {
158-
// When a user-provided public key is used, there is no private key to encrypt, so webauthnDevices is skipped
160+
it('should not add webauthnInfo when userKey is explicitly provided (no prv available)', async function () {
161+
// When a user-provided public key is used, there is no private key to encrypt, so webauthnInfo is skipped
159162
await wallets.generateWallet({
160163
label: 'Test Wallet',
161164
userKey: userPub,
@@ -167,10 +170,11 @@ describe('Wallets - WebAuthn wallet creation', function () {
167170
},
168171
});
169172

170-
// add is called for both user keychain (pub-only) and backup keychain - neither should have webauthnDevices
173+
// add is called for both user keychain (pub-only) and backup keychain - neither should have webauthnInfo
171174
const allAddCalls = mockKeychains.add.getCalls();
172175
assert.ok(allAddCalls.length > 0, 'expected keychains().add to be called at least once');
173176
for (const call of allAddCalls) {
177+
call.args[0].should.not.have.property('webauthnInfo');
174178
call.args[0].should.not.have.property('webauthnDevices');
175179
}
176180
});

0 commit comments

Comments
 (0)