Skip to content

Commit cc08d65

Browse files
committed
feat(sdk-core): add attachPasskeyToWallet function
- fetch wallet to infer coin, keychainId, enterpriseId - verify encryptedPrv exists before PRF assertion - derive enterprise salt and re-encrypt prv with PRF-derived password - PUT webauthnInfo to keychain endpoint Ticket: WCN-189
1 parent 94da3fc commit cc08d65

6 files changed

Lines changed: 377 additions & 3 deletions

File tree

Dockerfile

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,10 @@ COPY --from=builder /tmp/bitgo/modules/express /var/bitgo-express/
4343
#COPY_START
4444
COPY --from=builder /tmp/bitgo/modules/abstract-lightning /var/modules/abstract-lightning/
4545
COPY --from=builder /tmp/bitgo/modules/sdk-core /var/modules/sdk-core/
46+
COPY --from=builder /tmp/bitgo/modules/passkey-crypto /var/modules/passkey-crypto/
47+
COPY --from=builder /tmp/bitgo/modules/sjcl /var/modules/sjcl/
4648
COPY --from=builder /tmp/bitgo/modules/sdk-lib-mpc /var/modules/sdk-lib-mpc/
4749
COPY --from=builder /tmp/bitgo/modules/sdk-opensslbytes /var/modules/sdk-opensslbytes/
48-
COPY --from=builder /tmp/bitgo/modules/sjcl /var/modules/sjcl/
4950
COPY --from=builder /tmp/bitgo/modules/secp256k1 /var/modules/secp256k1/
5051
COPY --from=builder /tmp/bitgo/modules/statics /var/modules/statics/
5152
COPY --from=builder /tmp/bitgo/modules/utxo-lib /var/modules/utxo-lib/
@@ -145,9 +146,10 @@ COPY --from=builder /tmp/bitgo/modules/sdk-coin-zec /var/modules/sdk-coin-zec/
145146

146147
RUN cd /var/modules/abstract-lightning && yarn link && \
147148
cd /var/modules/sdk-core && yarn link && \
149+
cd /var/modules/passkey-crypto && yarn link && \
150+
cd /var/modules/sjcl && yarn link && \
148151
cd /var/modules/sdk-lib-mpc && yarn link && \
149152
cd /var/modules/sdk-opensslbytes && yarn link && \
150-
cd /var/modules/sjcl && yarn link && \
151153
cd /var/modules/secp256k1 && yarn link && \
152154
cd /var/modules/statics && yarn link && \
153155
cd /var/modules/utxo-lib && yarn link && \
@@ -250,9 +252,10 @@ cd /var/modules/sdk-coin-zec && yarn link
250252
RUN cd /var/bitgo-express && \
251253
yarn link @bitgo/abstract-lightning && \
252254
yarn link @bitgo/sdk-core && \
255+
yarn link @bitgo/passkey-crypto && \
256+
yarn link @bitgo/sjcl && \
253257
yarn link @bitgo/sdk-lib-mpc && \
254258
yarn link @bitgo/sdk-opensslbytes && \
255-
yarn link @bitgo/sjcl && \
256259
yarn link @bitgo/secp256k1 && \
257260
yarn link @bitgo/statics && \
258261
yarn link @bitgo/utxo-lib && \

modules/sdk-core/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
]
4141
},
4242
"dependencies": {
43+
"@bitgo/passkey-crypto": "^0.1.0",
4344
"@bitgo/public-types": "5.97.0",
4445
"@bitgo/sdk-lib-mpc": "^10.11.1",
4546
"@bitgo/secp256k1": "^1.11.0",
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import { BitGoBase } from '../bitgoBase';
2+
import { Keychain } from '../keychain';
3+
import { deriveEnterpriseSalt, derivePassword } from '@bitgo/passkey-crypto';
4+
import { WebAuthnOtpDevice, WebAuthnProvider } from './types';
5+
6+
export async function attachPasskeyToWallet(params: {
7+
bitgo: BitGoBase;
8+
walletId: string;
9+
device: WebAuthnOtpDevice;
10+
existingPassphrase: string;
11+
provider: WebAuthnProvider;
12+
}): Promise<Keychain> {
13+
const { bitgo, walletId, device, existingPassphrase, provider } = params;
14+
15+
// Throw early if PRF extension is not supported
16+
if (!device.prfSalt) {
17+
throw new Error('PRF extension not supported by this device. Please use a different passkey.');
18+
}
19+
20+
// Fetch wallet to infer coin, keychainId, enterpriseId
21+
const walletData = await bitgo.get(bitgo.url(`/wallet/${walletId}`, 2)).result();
22+
23+
const coin = walletData.coin;
24+
if (!coin || typeof coin !== 'string') {
25+
throw new Error(`Wallet ${walletId} has no coin type.`);
26+
}
27+
28+
const keys = walletData.keys as string[] | undefined;
29+
if (!keys || keys.length === 0) {
30+
throw new Error(`Wallet ${walletId} has no keys.`);
31+
}
32+
const keychainId = keys[0];
33+
34+
const enterpriseId = walletData.enterprise as string | undefined;
35+
if (!enterpriseId) {
36+
throw new Error(`Wallet ${walletId} has no enterprise.`);
37+
}
38+
39+
// Fetch user keychain
40+
const keychain = await bitgo.get(bitgo.url(`/${coin}/key/${keychainId}`, 2)).result();
41+
42+
if (!keychain.encryptedPrv) {
43+
throw new Error(
44+
`Keychain ${keychainId} has no encryptedPrv. Cannot attach passkey without an existing encrypted private key.`
45+
);
46+
}
47+
48+
// Derive enterprise-scoped salt
49+
const enterpriseSalt = deriveEnterpriseSalt(device.prfSalt, enterpriseId);
50+
51+
// Decrypt private key with existing passphrase
52+
const privateKey = bitgo.decrypt({ password: existingPassphrase, input: keychain.encryptedPrv });
53+
54+
// PRF assertion — evalByCredential maps this device's credentialId to its enterprise salt
55+
const authResult = await provider.get({
56+
publicKey: {} as PublicKeyCredentialRequestOptions,
57+
evalByCredential: { [device.credentialId]: enterpriseSalt },
58+
});
59+
60+
if (!authResult.prfResult) {
61+
throw new Error('PRF assertion did not return a result.');
62+
}
63+
64+
// Derive password from PRF output and re-encrypt
65+
const prfPassword = derivePassword(authResult.prfResult);
66+
const encryptedPrv = bitgo.encrypt({ password: prfPassword, input: privateKey });
67+
68+
// PUT webauthnInfo to keychain endpoint
69+
const updatedKeychain = await bitgo
70+
.put(bitgo.url(`/${coin}/key/${keychainId}`, 2))
71+
.send({
72+
webauthnInfo: {
73+
prfSalt: enterpriseSalt,
74+
otpDeviceId: device.id,
75+
encryptedPrv,
76+
},
77+
})
78+
.result();
79+
80+
return updatedKeychain as Keychain;
81+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from './attachPasskeyToWallet';
2+
export * from './types';
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export type { WebAuthnOtpDevice, WebAuthnProvider, PasskeyAuthResult, PasskeyGetOptions } from '@bitgo/passkey-crypto';

0 commit comments

Comments
 (0)