@dynamic-labs-wallet/node-btc 0.0.0 → 0.0.255
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.cjs.d.ts +1 -0
- package/index.cjs.js +636 -0
- package/index.esm.d.ts +1 -0
- package/index.esm.js +608 -0
- package/package.json +43 -1
- package/src/client/client.d.ts +165 -0
- package/src/client/client.d.ts.map +1 -0
- package/src/client/constants.d.ts +7 -0
- package/src/client/constants.d.ts.map +1 -0
- package/src/client/index.d.ts +3 -0
- package/src/client/index.d.ts.map +1 -0
- package/src/index.d.ts +2 -0
- package/src/index.d.ts.map +1 -0
- package/src/services/logger.d.ts +10 -0
- package/src/services/logger.d.ts.map +1 -0
package/index.cjs.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./src/index";
|
package/index.cjs.js
ADDED
|
@@ -0,0 +1,636 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var node = require('@dynamic-labs-wallet/node');
|
|
4
|
+
var core = require('@dynamic-labs-wallet/core');
|
|
5
|
+
var btcUtils = require('@dynamic-labs-wallet/btc-utils');
|
|
6
|
+
var bitcoin = require('bitcoinjs-lib');
|
|
7
|
+
var ecc = require('tiny-secp256k1');
|
|
8
|
+
var logger$1 = require('@dynamic-labs/logger');
|
|
9
|
+
var axios = require('axios');
|
|
10
|
+
|
|
11
|
+
function _interopNamespaceDefault(e) {
|
|
12
|
+
var n = Object.create(null);
|
|
13
|
+
if (e) {
|
|
14
|
+
Object.keys(e).forEach(function (k) {
|
|
15
|
+
if (k !== 'default') {
|
|
16
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
17
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
18
|
+
enumerable: true,
|
|
19
|
+
get: function () { return e[k]; }
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
n.default = e;
|
|
25
|
+
return Object.freeze(n);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
var bitcoin__namespace = /*#__PURE__*/_interopNamespaceDefault(bitcoin);
|
|
29
|
+
var ecc__namespace = /*#__PURE__*/_interopNamespaceDefault(ecc);
|
|
30
|
+
|
|
31
|
+
function _extends() {
|
|
32
|
+
_extends = Object.assign || function assign(target) {
|
|
33
|
+
for(var i = 1; i < arguments.length; i++){
|
|
34
|
+
var source = arguments[i];
|
|
35
|
+
for(var key in source)if (Object.prototype.hasOwnProperty.call(source, key)) target[key] = source[key];
|
|
36
|
+
}
|
|
37
|
+
return target;
|
|
38
|
+
};
|
|
39
|
+
return _extends.apply(this, arguments);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const logger = new logger$1.Logger('DynamicWaasWalletClient');
|
|
43
|
+
const logError = ({ message, error, context })=>{
|
|
44
|
+
if (error instanceof axios.AxiosError) {
|
|
45
|
+
core.handleAxiosError(error, message, context, logger);
|
|
46
|
+
}
|
|
47
|
+
logger.error('[DynamicWaasWalletClient] Error in node-btc client', {
|
|
48
|
+
error: error instanceof Error ? error.message : String(error),
|
|
49
|
+
context
|
|
50
|
+
});
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const ERROR_CREATE_WALLET_ACCOUNT = 'Error creating wallet account';
|
|
54
|
+
const ERROR_ACCOUNT_ADDRESS_REQUIRED = 'Account address is required';
|
|
55
|
+
const ERROR_SIGN_MESSAGE = 'Error signing message';
|
|
56
|
+
const ERROR_SIGN_TRANSACTION = 'Error signing transaction';
|
|
57
|
+
const ERROR_IMPORT_PRIVATE_KEY = 'Error importing private key';
|
|
58
|
+
const ERROR_KEYGEN_FAILED = 'Error with keygen';
|
|
59
|
+
|
|
60
|
+
// Initialize ECC library with tiny-secp256k1 for Node.js
|
|
61
|
+
btcUtils.initEccLib(ecc__namespace);
|
|
62
|
+
class DynamicBtcWalletClient extends node.DynamicWalletClient {
|
|
63
|
+
/**
|
|
64
|
+
* Derives the Bitcoin account address
|
|
65
|
+
* - BIP340 keys (32 bytes x-only): Only for Taproot addresses
|
|
66
|
+
* - ECDSA keys (33/65 bytes): For all other address types (Legacy, SegWit, Native SegWit)
|
|
67
|
+
* - Algorithm selection is automatic based on addressType
|
|
68
|
+
* @param rawPublicKey - The raw public key to derive the account address from
|
|
69
|
+
* @param addressType - The address type to derive the account address for
|
|
70
|
+
* @param network - The network to derive the account address for
|
|
71
|
+
* @returns The account address
|
|
72
|
+
*/ deriveAccountAddress({ rawPublicKey, addressType, network }) {
|
|
73
|
+
// Derive address based on the chosen address type and network
|
|
74
|
+
const normalizedKey = btcUtils.normalizePublicKey(rawPublicKey, addressType);
|
|
75
|
+
const accountAddress = btcUtils.publicKeyToBitcoinAddress(normalizedKey, addressType, network);
|
|
76
|
+
return {
|
|
77
|
+
accountAddress
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Gets wallet properties and derivation info for a given account address
|
|
82
|
+
* @param accountAddress - The account address
|
|
83
|
+
* @returns The wallet properties, derivation path, and address type
|
|
84
|
+
*/ getWalletDerivationInfo(accountAddress) {
|
|
85
|
+
const walletProperties = this.walletMap[accountAddress];
|
|
86
|
+
if (!walletProperties) {
|
|
87
|
+
throw new Error('Wallet not found in walletMap');
|
|
88
|
+
}
|
|
89
|
+
const derivationPath = walletProperties.derivationPath;
|
|
90
|
+
if (!derivationPath) {
|
|
91
|
+
throw new Error('Derivation path missing in walletMap');
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
walletProperties,
|
|
95
|
+
derivationPath,
|
|
96
|
+
addressType: btcUtils.getAddressTypeFromDerivationPath(derivationPath)
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Verifies that the derived address matches the expected address
|
|
101
|
+
* @param rawPublicKey - The raw public key
|
|
102
|
+
* @param addressType - The address type
|
|
103
|
+
* @param network - The network
|
|
104
|
+
* @param expectedAddress - The expected address
|
|
105
|
+
*/ verifyWalletAddress(rawPublicKey, addressType, network, expectedAddress) {
|
|
106
|
+
const normalizedKey = btcUtils.normalizePublicKey(rawPublicKey, addressType);
|
|
107
|
+
const derivedAddress = btcUtils.publicKeyToBitcoinAddress(normalizedKey, addressType, network);
|
|
108
|
+
if (derivedAddress !== expectedAddress) {
|
|
109
|
+
throw new Error(`Address verification failed: expected ${expectedAddress}, got ${derivedAddress}`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Creates a new wallet account and stores the key shares in the wallet map.
|
|
114
|
+
* @param thresholdSignatureScheme - The threshold signature scheme to use for the wallet.
|
|
115
|
+
* @param password - The password to use for the wallet.
|
|
116
|
+
* @param onError - The function to call if an error occurs.
|
|
117
|
+
* @param backUpToClientShareService - Whether to back up the external server key shares to the client share service. By default, it is false.
|
|
118
|
+
* @param bitcoinConfig - Bitcoin configuration (addressType, network)
|
|
119
|
+
* @returns The account address, public key, raw public key, external server key shares, and wallet id.
|
|
120
|
+
*/ async createWalletAccount({ thresholdSignatureScheme, password = undefined, onError, backUpToClientShareService = false, bitcoinConfig }) {
|
|
121
|
+
try {
|
|
122
|
+
const { addressType, network = core.BitcoinNetwork.MAINNET } = bitcoinConfig;
|
|
123
|
+
if (!addressType) {
|
|
124
|
+
throw new Error('addressType is required for BTC');
|
|
125
|
+
}
|
|
126
|
+
let ceremonyCeremonyCompleteResolver;
|
|
127
|
+
let ceremonyAccountAddress;
|
|
128
|
+
const ceremonyCompletePromise = new Promise((resolve)=>{
|
|
129
|
+
ceremonyCeremonyCompleteResolver = resolve;
|
|
130
|
+
});
|
|
131
|
+
// Generate key shares for given threshold signature scheme (TSS)
|
|
132
|
+
const { rawPublicKey, externalServerKeyShares } = await this.keyGen({
|
|
133
|
+
chainName: this.chainName,
|
|
134
|
+
thresholdSignatureScheme,
|
|
135
|
+
skipLock: true,
|
|
136
|
+
bitcoinConfig,
|
|
137
|
+
onError,
|
|
138
|
+
onCeremonyComplete: (accountAddress, walletId)=>{
|
|
139
|
+
// Store the ceremony account address to ensure consistency
|
|
140
|
+
ceremonyAccountAddress = accountAddress;
|
|
141
|
+
// update wallet map
|
|
142
|
+
const chainConfig = node.getMPCChainConfig(this.chainName, bitcoinConfig);
|
|
143
|
+
this.walletMap[accountAddress] = _extends({}, this.walletMap[accountAddress], {
|
|
144
|
+
accountAddress,
|
|
145
|
+
walletId,
|
|
146
|
+
chainName: this.chainName,
|
|
147
|
+
thresholdSignatureScheme,
|
|
148
|
+
derivationPath: JSON.stringify(Object.fromEntries(chainConfig.derivationPath.map((value, index)=>[
|
|
149
|
+
index,
|
|
150
|
+
value
|
|
151
|
+
]))),
|
|
152
|
+
externalServerKeySharesBackupInfo: node.getExternalServerKeyShareBackupInfo()
|
|
153
|
+
});
|
|
154
|
+
this.logger.debug('walletMap updated for wallet', {
|
|
155
|
+
context: {
|
|
156
|
+
accountAddress,
|
|
157
|
+
walletId,
|
|
158
|
+
walletMap: this.walletMap
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
ceremonyCeremonyCompleteResolver(undefined);
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
await ceremonyCompletePromise;
|
|
165
|
+
if (!rawPublicKey || !externalServerKeyShares) {
|
|
166
|
+
throw new Error(ERROR_KEYGEN_FAILED);
|
|
167
|
+
}
|
|
168
|
+
const accountAddress = ceremonyAccountAddress || this.deriveAccountAddress({
|
|
169
|
+
rawPublicKey,
|
|
170
|
+
addressType,
|
|
171
|
+
network
|
|
172
|
+
}).accountAddress;
|
|
173
|
+
await this.storeEncryptedBackupByWalletWithRetry({
|
|
174
|
+
accountAddress,
|
|
175
|
+
externalServerKeyShares,
|
|
176
|
+
password,
|
|
177
|
+
backUpToClientShareService
|
|
178
|
+
});
|
|
179
|
+
const publicKeyHex = btcUtils.extractPublicKeyHex(rawPublicKey);
|
|
180
|
+
return {
|
|
181
|
+
accountAddress,
|
|
182
|
+
rawPublicKey,
|
|
183
|
+
publicKeyHex,
|
|
184
|
+
externalServerKeyShares
|
|
185
|
+
};
|
|
186
|
+
} catch (error) {
|
|
187
|
+
logError({
|
|
188
|
+
message: ERROR_CREATE_WALLET_ACCOUNT,
|
|
189
|
+
error: error,
|
|
190
|
+
context: {}
|
|
191
|
+
});
|
|
192
|
+
throw new Error(ERROR_CREATE_WALLET_ACCOUNT);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Signs a message for BTC using BIP-322 format
|
|
197
|
+
*
|
|
198
|
+
* The address type is automatically derived from the wallet's derivation path.
|
|
199
|
+
* Supports both TAPROOT (BIP-86) and NATIVE_SEGWIT (BIP-84) address types.
|
|
200
|
+
*
|
|
201
|
+
* @param message - The message to sign
|
|
202
|
+
* @param accountAddress - The account address
|
|
203
|
+
* @param network - The Bitcoin network (MAINNET or TESTNET)
|
|
204
|
+
* @param password - Optional password for encrypted backup shares
|
|
205
|
+
* @param externalServerKeyShares - Optional external server key shares
|
|
206
|
+
* @param context - Optional. Sign message context for API tracking
|
|
207
|
+
* @param onError - Optional. Error callback function
|
|
208
|
+
* @returns Promise resolving to the BIP-322 signature as a base64 string
|
|
209
|
+
* @throws Error if required parameters are missing, derivation path is invalid, or signing fails
|
|
210
|
+
*/ async signMessage({ message, accountAddress, network, password = undefined, externalServerKeyShares, context, onError }) {
|
|
211
|
+
try {
|
|
212
|
+
var _this_walletMap_accountAddress;
|
|
213
|
+
if (!accountAddress) {
|
|
214
|
+
throw new Error(ERROR_ACCOUNT_ADDRESS_REQUIRED);
|
|
215
|
+
}
|
|
216
|
+
const { derivationPath, addressType } = this.getWalletDerivationInfo(accountAddress);
|
|
217
|
+
// Attempt to recover key shares from backup if not provided
|
|
218
|
+
await this.ensureKeySharesRecovered({
|
|
219
|
+
accountAddress,
|
|
220
|
+
password,
|
|
221
|
+
walletOperation: node.WalletOperation.SIGN_MESSAGE,
|
|
222
|
+
externalServerKeyShares,
|
|
223
|
+
errorMessage: 'External server key shares are required to sign a message. No backup shares available for recovery.'
|
|
224
|
+
});
|
|
225
|
+
const resolvedExternalServerKeyShares = externalServerKeyShares != null ? externalServerKeyShares : (_this_walletMap_accountAddress = this.walletMap[accountAddress]) == null ? void 0 : _this_walletMap_accountAddress.externalServerKeyShares;
|
|
226
|
+
if (!resolvedExternalServerKeyShares || resolvedExternalServerKeyShares.length === 0) {
|
|
227
|
+
throw new Error('External server key shares are required to sign a message');
|
|
228
|
+
}
|
|
229
|
+
const bitcoinConfig = {
|
|
230
|
+
addressType,
|
|
231
|
+
network
|
|
232
|
+
};
|
|
233
|
+
const derivedPublicKey = await this.derivePublicKey({
|
|
234
|
+
chainName: this.chainName,
|
|
235
|
+
keyShare: resolvedExternalServerKeyShares[0],
|
|
236
|
+
derivationPath: new Uint32Array(Object.values(JSON.parse(derivationPath))),
|
|
237
|
+
bitcoinConfig
|
|
238
|
+
});
|
|
239
|
+
if (!derivedPublicKey) {
|
|
240
|
+
throw new Error('Failed to derive public key');
|
|
241
|
+
}
|
|
242
|
+
const pubKey = btcUtils.normalizePublicKey(derivedPublicKey, addressType);
|
|
243
|
+
this.verifyWalletAddress(derivedPublicKey, addressType, network, accountAddress);
|
|
244
|
+
// Prepare BIP-322 Transactions and calculate hash
|
|
245
|
+
const { formattedMessage, toSignPsbt } = btcUtils.calculateBip322Hash(message, pubKey, addressType, network, ecc__namespace);
|
|
246
|
+
// Prepare tweak for Taproot in case of BIP340
|
|
247
|
+
let tweak;
|
|
248
|
+
if (addressType === core.BitcoinAddressType.TAPROOT) {
|
|
249
|
+
tweak = btcUtils.calculateTaprootTweak(pubKey);
|
|
250
|
+
}
|
|
251
|
+
// Build complete bitcoinConfig with addressType and tweak
|
|
252
|
+
const completeBitcoinConfig = _extends({}, bitcoinConfig, {
|
|
253
|
+
addressType,
|
|
254
|
+
tweak
|
|
255
|
+
});
|
|
256
|
+
// Sign the message using MPC
|
|
257
|
+
const signature = await this.sign({
|
|
258
|
+
message: formattedMessage,
|
|
259
|
+
accountAddress,
|
|
260
|
+
chainName: this.chainName,
|
|
261
|
+
password,
|
|
262
|
+
externalServerKeyShares: resolvedExternalServerKeyShares,
|
|
263
|
+
isFormatted: true,
|
|
264
|
+
context: context != null ? context : {
|
|
265
|
+
btcMessage: message
|
|
266
|
+
},
|
|
267
|
+
bitcoinConfig: completeBitcoinConfig,
|
|
268
|
+
onError
|
|
269
|
+
});
|
|
270
|
+
const bip322Signature = btcUtils.encodeBip322Signature(toSignPsbt, pubKey, signature, addressType);
|
|
271
|
+
return bip322Signature;
|
|
272
|
+
} catch (error) {
|
|
273
|
+
logError({
|
|
274
|
+
message: ERROR_SIGN_MESSAGE,
|
|
275
|
+
error: error,
|
|
276
|
+
context: {
|
|
277
|
+
accountAddress
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
if (onError) {
|
|
281
|
+
onError(error);
|
|
282
|
+
}
|
|
283
|
+
throw new Error(ERROR_SIGN_MESSAGE);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Signs a Bitcoin transaction (PSBT)
|
|
288
|
+
*
|
|
289
|
+
* The address type is automatically derived from the wallet's derivation path.
|
|
290
|
+
* Only inputs belonging to the sender address are signed. Supports both TAPROOT
|
|
291
|
+
* (BIP-341) and Native SegWit (BIP-143) signing methods.
|
|
292
|
+
*
|
|
293
|
+
* @param transaction - The PSBT to sign as a base64 string
|
|
294
|
+
* @param senderAddress - The sender address (must match inputs to be signed)
|
|
295
|
+
* @param network - The Bitcoin network (MAINNET or TESTNET)
|
|
296
|
+
* @param password - Optional password for encrypted backup shares
|
|
297
|
+
* @param externalServerKeyShares - Optional external server key shares
|
|
298
|
+
* @param context - Optional. Sign message context for API tracking
|
|
299
|
+
* @param onError - Optional. Error callback function
|
|
300
|
+
* @returns Promise resolving to the signed PSBT as a base64 string
|
|
301
|
+
* @throws Error if required parameters are missing, no inputs belong to sender address, or signing fails
|
|
302
|
+
*/ async signTransaction({ transaction, senderAddress, network, password = undefined, externalServerKeyShares, context, onError }) {
|
|
303
|
+
try {
|
|
304
|
+
await this.verifyPassword({
|
|
305
|
+
accountAddress: senderAddress,
|
|
306
|
+
password,
|
|
307
|
+
walletOperation: node.WalletOperation.SIGN_TRANSACTION
|
|
308
|
+
});
|
|
309
|
+
if (!senderAddress) {
|
|
310
|
+
throw new Error(ERROR_ACCOUNT_ADDRESS_REQUIRED);
|
|
311
|
+
}
|
|
312
|
+
const { derivationPath, addressType } = this.getWalletDerivationInfo(senderAddress);
|
|
313
|
+
// Attempt to recover key shares from backup if not provided
|
|
314
|
+
await this.ensureKeySharesRecovered({
|
|
315
|
+
accountAddress: senderAddress,
|
|
316
|
+
password,
|
|
317
|
+
walletOperation: node.WalletOperation.SIGN_TRANSACTION,
|
|
318
|
+
externalServerKeyShares,
|
|
319
|
+
errorMessage: 'External server key shares are required to sign transaction. No backup shares available for recovery.'
|
|
320
|
+
});
|
|
321
|
+
const updatedWalletProperties = this.walletMap[senderAddress];
|
|
322
|
+
if (!updatedWalletProperties) {
|
|
323
|
+
throw new Error('Wallet not found in walletMap after key share recovery');
|
|
324
|
+
}
|
|
325
|
+
const resolvedExternalServerKeyShares = externalServerKeyShares || updatedWalletProperties.externalServerKeyShares;
|
|
326
|
+
if (!resolvedExternalServerKeyShares || resolvedExternalServerKeyShares.length === 0) {
|
|
327
|
+
throw new Error('External server key shares are required to sign transaction. No backup shares available for recovery.');
|
|
328
|
+
}
|
|
329
|
+
const bitcoinConfig = {
|
|
330
|
+
addressType,
|
|
331
|
+
network
|
|
332
|
+
};
|
|
333
|
+
const psbt = bitcoin__namespace.Psbt.fromBase64(transaction);
|
|
334
|
+
const derivedPublicKey = await this.derivePublicKey({
|
|
335
|
+
chainName: this.chainName,
|
|
336
|
+
keyShare: resolvedExternalServerKeyShares[0],
|
|
337
|
+
derivationPath: new Uint32Array(Object.values(JSON.parse(derivationPath))),
|
|
338
|
+
bitcoinConfig
|
|
339
|
+
});
|
|
340
|
+
if (!derivedPublicKey) {
|
|
341
|
+
throw new Error('Failed to derive public key');
|
|
342
|
+
}
|
|
343
|
+
const pubKey = btcUtils.normalizePublicKey(derivedPublicKey, addressType);
|
|
344
|
+
const tx = psbt.__CACHE.__TX;
|
|
345
|
+
// Filter inputs to only sign those that belong to the current address
|
|
346
|
+
const inputsToSign = psbt.data.inputs.map((input, i)=>({
|
|
347
|
+
input,
|
|
348
|
+
index: i
|
|
349
|
+
})).filter(({ input })=>btcUtils.doesInputBelongToAddress(input, senderAddress, network));
|
|
350
|
+
if (inputsToSign.length === 0) {
|
|
351
|
+
throw new Error('No inputs found that belong to the sender address');
|
|
352
|
+
}
|
|
353
|
+
if (addressType === core.BitcoinAddressType.TAPROOT) {
|
|
354
|
+
const tweak = btcUtils.calculateTaprootTweak(pubKey);
|
|
355
|
+
const completeBitcoinConfig = _extends({}, bitcoinConfig, {
|
|
356
|
+
addressType,
|
|
357
|
+
tweak
|
|
358
|
+
});
|
|
359
|
+
const { prevOutScripts, values } = btcUtils.collectPSBTInputData(psbt);
|
|
360
|
+
await Promise.all(inputsToSign.map(async ({ input, index: i })=>{
|
|
361
|
+
if (!input.witnessUtxo) {
|
|
362
|
+
throw new Error(`Input ${i} missing witnessUtxo`);
|
|
363
|
+
}
|
|
364
|
+
const hash = Buffer.from(tx.hashForWitnessV1(i, prevOutScripts, values, bitcoin__namespace.Transaction.SIGHASH_DEFAULT));
|
|
365
|
+
const signature = await this.sign({
|
|
366
|
+
message: new Uint8Array(hash),
|
|
367
|
+
accountAddress: senderAddress,
|
|
368
|
+
chainName: this.chainName,
|
|
369
|
+
password,
|
|
370
|
+
externalServerKeyShares: resolvedExternalServerKeyShares,
|
|
371
|
+
isFormatted: true,
|
|
372
|
+
context,
|
|
373
|
+
bitcoinConfig: completeBitcoinConfig,
|
|
374
|
+
onError
|
|
375
|
+
});
|
|
376
|
+
const sigBuffer = btcUtils.convertSignatureToTaprootBuffer(signature);
|
|
377
|
+
psbt.updateInput(i, {
|
|
378
|
+
tapKeySig: sigBuffer
|
|
379
|
+
});
|
|
380
|
+
}));
|
|
381
|
+
} else {
|
|
382
|
+
// Native SegWit (P2WPKH) or other ECDSA-based signing
|
|
383
|
+
const completeBitcoinConfig = _extends({}, bitcoinConfig, {
|
|
384
|
+
addressType
|
|
385
|
+
});
|
|
386
|
+
await Promise.all(inputsToSign.map(async ({ input, index: i })=>{
|
|
387
|
+
if (!input.witnessUtxo) {
|
|
388
|
+
throw new Error(`Input ${i} missing witnessUtxo`);
|
|
389
|
+
}
|
|
390
|
+
const { script, value } = input.witnessUtxo;
|
|
391
|
+
const p2pkh = bitcoin__namespace.payments.p2pkh({
|
|
392
|
+
hash: script.slice(2),
|
|
393
|
+
network: btcUtils.getBitcoinNetwork(network)
|
|
394
|
+
});
|
|
395
|
+
const scriptCode = p2pkh.output;
|
|
396
|
+
if (!scriptCode) {
|
|
397
|
+
throw new Error('Failed to generate scriptCode');
|
|
398
|
+
}
|
|
399
|
+
const hash = tx.hashForWitnessV0(i, scriptCode, value, bitcoin__namespace.Transaction.SIGHASH_ALL);
|
|
400
|
+
const signature = await this.sign({
|
|
401
|
+
message: new Uint8Array(hash),
|
|
402
|
+
accountAddress: senderAddress,
|
|
403
|
+
chainName: this.chainName,
|
|
404
|
+
password,
|
|
405
|
+
externalServerKeyShares: resolvedExternalServerKeyShares,
|
|
406
|
+
isFormatted: true,
|
|
407
|
+
context,
|
|
408
|
+
bitcoinConfig: completeBitcoinConfig,
|
|
409
|
+
onError
|
|
410
|
+
});
|
|
411
|
+
const derSignature = btcUtils.convertSignatureToDER(signature);
|
|
412
|
+
psbt.updateInput(i, {
|
|
413
|
+
partialSig: [
|
|
414
|
+
{
|
|
415
|
+
pubkey: pubKey,
|
|
416
|
+
signature: new Uint8Array(derSignature)
|
|
417
|
+
}
|
|
418
|
+
]
|
|
419
|
+
});
|
|
420
|
+
}));
|
|
421
|
+
}
|
|
422
|
+
// Return signed PSBT in base64 format (not finalized)
|
|
423
|
+
return psbt.toBase64();
|
|
424
|
+
} catch (error) {
|
|
425
|
+
logError({
|
|
426
|
+
message: ERROR_SIGN_TRANSACTION,
|
|
427
|
+
error: error,
|
|
428
|
+
context: {
|
|
429
|
+
senderAddress
|
|
430
|
+
}
|
|
431
|
+
});
|
|
432
|
+
if (onError) {
|
|
433
|
+
onError(error);
|
|
434
|
+
}
|
|
435
|
+
throw error;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
/**
|
|
439
|
+
* Exports the private key for a wallet
|
|
440
|
+
* @param accountAddress - The account address to export the private key for
|
|
441
|
+
* @param password - The password for encrypted backup shares
|
|
442
|
+
* @param externalServerKeyShares - Optional external server key shares
|
|
443
|
+
* @returns The private key in WIF format
|
|
444
|
+
*/ async exportPrivateKey({ accountAddress, password = undefined, externalServerKeyShares }) {
|
|
445
|
+
await this.verifyPassword({
|
|
446
|
+
accountAddress,
|
|
447
|
+
password,
|
|
448
|
+
walletOperation: node.WalletOperation.EXPORT_PRIVATE_KEY
|
|
449
|
+
});
|
|
450
|
+
// Attempt to recover key shares from backup if not provided
|
|
451
|
+
await this.ensureKeySharesRecovered({
|
|
452
|
+
accountAddress,
|
|
453
|
+
password,
|
|
454
|
+
walletOperation: node.WalletOperation.EXPORT_PRIVATE_KEY,
|
|
455
|
+
externalServerKeyShares,
|
|
456
|
+
errorMessage: 'External server key shares are required to export private key. No backup shares available for recovery.'
|
|
457
|
+
});
|
|
458
|
+
// Re-read wallet from map after recovery (it may have been updated with recovered shares)
|
|
459
|
+
const updatedWalletProperties = this.walletMap[accountAddress];
|
|
460
|
+
if (!updatedWalletProperties) {
|
|
461
|
+
throw new Error('Wallet not found in walletMap after recovery');
|
|
462
|
+
}
|
|
463
|
+
const derivationPath = updatedWalletProperties.derivationPath;
|
|
464
|
+
if (!derivationPath) {
|
|
465
|
+
throw new Error('Derivation path missing in walletMap');
|
|
466
|
+
}
|
|
467
|
+
// Derive address type from derivation path for bitcoinConfig
|
|
468
|
+
const addressType = btcUtils.getAddressTypeFromDerivationPath(derivationPath);
|
|
469
|
+
const network = core.BitcoinNetwork.MAINNET; // Default to mainnet, could be derived from wallet
|
|
470
|
+
const bitcoinConfig = {
|
|
471
|
+
addressType,
|
|
472
|
+
network
|
|
473
|
+
};
|
|
474
|
+
// Get key shares from wallet map if not provided (recovered from backup)
|
|
475
|
+
const resolvedExternalServerKeyShares = externalServerKeyShares != null ? externalServerKeyShares : updatedWalletProperties.externalServerKeyShares;
|
|
476
|
+
const { derivedPrivateKey } = await this.exportKey({
|
|
477
|
+
accountAddress,
|
|
478
|
+
chainName: this.chainName,
|
|
479
|
+
password,
|
|
480
|
+
externalServerKeyShares: resolvedExternalServerKeyShares,
|
|
481
|
+
bitcoinConfig
|
|
482
|
+
});
|
|
483
|
+
if (!derivedPrivateKey) {
|
|
484
|
+
throw new Error('Derived private key is undefined');
|
|
485
|
+
}
|
|
486
|
+
// Convert private key to WIF format
|
|
487
|
+
const wifPrivateKey = btcUtils.privateKeyToWIF(derivedPrivateKey, network);
|
|
488
|
+
return wifPrivateKey;
|
|
489
|
+
}
|
|
490
|
+
/**
|
|
491
|
+
* Exports the private key for a given account address offline using key shares
|
|
492
|
+
* @param keyShares - The key shares to export the private key for
|
|
493
|
+
* @param derivationPath - Optional derivation path
|
|
494
|
+
* @param bitcoinConfig - Bitcoin configuration (address type, network)
|
|
495
|
+
* @returns The private key in WIF format
|
|
496
|
+
*/ async offlineExportPrivateKey({ keyShares, derivationPath, bitcoinConfig }) {
|
|
497
|
+
const { derivedPrivateKey } = await this.offlineExportKey({
|
|
498
|
+
chainName: this.chainName,
|
|
499
|
+
keyShares,
|
|
500
|
+
derivationPath
|
|
501
|
+
});
|
|
502
|
+
if (!derivedPrivateKey) {
|
|
503
|
+
throw new Error('Derived private key is undefined');
|
|
504
|
+
}
|
|
505
|
+
const network = (bitcoinConfig == null ? void 0 : bitcoinConfig.network) || core.BitcoinNetwork.MAINNET;
|
|
506
|
+
// Convert private key to WIF format
|
|
507
|
+
const wifPrivateKey = btcUtils.privateKeyToWIF(derivedPrivateKey, network);
|
|
508
|
+
return wifPrivateKey;
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* Imports a private key and stores the key shares in the wallet map.
|
|
512
|
+
* @param privateKey - The private key to import (WIF format)
|
|
513
|
+
* @param chainName - The chain name to use for the wallet
|
|
514
|
+
* @param thresholdSignatureScheme - The threshold signature scheme to use for the wallet
|
|
515
|
+
* @param password - The password to use for the wallet
|
|
516
|
+
* @param onError - The function to call if an error occurs
|
|
517
|
+
* @param backUpToClientShareService - Whether to back up the external server key shares to the client share service. By default, it is false.
|
|
518
|
+
* @param bitcoinConfig - Bitcoin configuration (addressType, network)
|
|
519
|
+
* @returns The account address, public key, raw public key, external server key shares
|
|
520
|
+
*/ async importPrivateKey({ privateKey, chainName, thresholdSignatureScheme, password = undefined, backUpToClientShareService = false, onError, bitcoinConfig }) {
|
|
521
|
+
try {
|
|
522
|
+
const { addressType, network = core.BitcoinNetwork.MAINNET } = bitcoinConfig;
|
|
523
|
+
if (!addressType) {
|
|
524
|
+
throw new Error('addressType is required for BTC importPrivateKey');
|
|
525
|
+
}
|
|
526
|
+
if (addressType !== core.BitcoinAddressType.NATIVE_SEGWIT && addressType !== core.BitcoinAddressType.TAPROOT) {
|
|
527
|
+
throw new Error(`Invalid addressType: ${addressType}. Must be one of: ${core.BitcoinAddressType.NATIVE_SEGWIT}, ${core.BitcoinAddressType.TAPROOT}`);
|
|
528
|
+
}
|
|
529
|
+
let ceremonyCeremonyCompleteResolver;
|
|
530
|
+
let ceremonyAccountAddress;
|
|
531
|
+
const ceremonyCompletePromise = new Promise((resolve)=>{
|
|
532
|
+
ceremonyCeremonyCompleteResolver = resolve;
|
|
533
|
+
});
|
|
534
|
+
// Convert WIF to private key hex
|
|
535
|
+
const formattedPrivateKey = btcUtils.wifToPrivateKey(privateKey, network);
|
|
536
|
+
// Get public key from private key to verify address
|
|
537
|
+
const derivedPublicKey = await btcUtils.getPublicKeyFromPrivateKey(privateKey, addressType, ecc__namespace, network);
|
|
538
|
+
const { accountAddress: expectedAddress } = this.deriveAccountAddress({
|
|
539
|
+
rawPublicKey: derivedPublicKey,
|
|
540
|
+
addressType,
|
|
541
|
+
network
|
|
542
|
+
});
|
|
543
|
+
const { rawPublicKey, externalServerKeyShares } = await this.importRawPrivateKey({
|
|
544
|
+
chainName,
|
|
545
|
+
privateKey: formattedPrivateKey,
|
|
546
|
+
thresholdSignatureScheme,
|
|
547
|
+
bitcoinConfig,
|
|
548
|
+
onError,
|
|
549
|
+
onCeremonyComplete: (accountAddress, walletId)=>{
|
|
550
|
+
// Store the ceremony account address to ensure consistency
|
|
551
|
+
ceremonyAccountAddress = accountAddress;
|
|
552
|
+
// Verify address matches
|
|
553
|
+
if (accountAddress !== expectedAddress) {
|
|
554
|
+
throw new Error(`Public address mismatch: derived address ${accountAddress} !== expected address ${expectedAddress}`);
|
|
555
|
+
}
|
|
556
|
+
// update wallet map
|
|
557
|
+
const chainConfig = node.getMPCChainConfig(this.chainName, bitcoinConfig);
|
|
558
|
+
this.walletMap[accountAddress] = _extends({}, this.walletMap[accountAddress], {
|
|
559
|
+
accountAddress,
|
|
560
|
+
walletId,
|
|
561
|
+
chainName: this.chainName,
|
|
562
|
+
thresholdSignatureScheme,
|
|
563
|
+
derivationPath: JSON.stringify(Object.fromEntries(chainConfig.derivationPath.map((value, index)=>[
|
|
564
|
+
index,
|
|
565
|
+
value
|
|
566
|
+
]))),
|
|
567
|
+
externalServerKeySharesBackupInfo: node.getExternalServerKeyShareBackupInfo()
|
|
568
|
+
});
|
|
569
|
+
ceremonyCeremonyCompleteResolver(undefined);
|
|
570
|
+
}
|
|
571
|
+
});
|
|
572
|
+
// Wait for the ceremony to complete before proceeding
|
|
573
|
+
await ceremonyCompletePromise;
|
|
574
|
+
if (!rawPublicKey || !externalServerKeyShares) {
|
|
575
|
+
throw new Error('Error creating wallet account');
|
|
576
|
+
}
|
|
577
|
+
// Use the ceremony account address if available, otherwise derive it from raw public key
|
|
578
|
+
const accountAddress = ceremonyAccountAddress || this.deriveAccountAddress({
|
|
579
|
+
rawPublicKey,
|
|
580
|
+
addressType,
|
|
581
|
+
network
|
|
582
|
+
}).accountAddress;
|
|
583
|
+
if (accountAddress !== expectedAddress) {
|
|
584
|
+
throw new Error(`Public address mismatch: derived address ${accountAddress} !== expected address ${expectedAddress}`);
|
|
585
|
+
}
|
|
586
|
+
await this.storeEncryptedBackupByWalletWithRetry({
|
|
587
|
+
accountAddress,
|
|
588
|
+
externalServerKeyShares,
|
|
589
|
+
password,
|
|
590
|
+
backUpToClientShareService
|
|
591
|
+
});
|
|
592
|
+
const publicKeyHex = btcUtils.extractPublicKeyHex(rawPublicKey);
|
|
593
|
+
return {
|
|
594
|
+
accountAddress,
|
|
595
|
+
rawPublicKey,
|
|
596
|
+
publicKeyHex,
|
|
597
|
+
externalServerKeyShares
|
|
598
|
+
};
|
|
599
|
+
} catch (error) {
|
|
600
|
+
logError({
|
|
601
|
+
message: ERROR_IMPORT_PRIVATE_KEY,
|
|
602
|
+
error: error,
|
|
603
|
+
context: {}
|
|
604
|
+
});
|
|
605
|
+
throw new Error(ERROR_IMPORT_PRIVATE_KEY);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
/**
|
|
609
|
+
* Gets the Bitcoin wallets
|
|
610
|
+
* @returns The Bitcoin wallets
|
|
611
|
+
*/ async getBitcoinWallets() {
|
|
612
|
+
const wallets = await this.getWallets(); // NOSONAR
|
|
613
|
+
const btcWallets = wallets.filter((wallet)=>{
|
|
614
|
+
var _wallet_chainName;
|
|
615
|
+
const chainName = (_wallet_chainName = wallet.chainName) == null ? void 0 : _wallet_chainName.toLowerCase();
|
|
616
|
+
return chainName === 'btc' || chainName === 'bip122';
|
|
617
|
+
});
|
|
618
|
+
return btcWallets;
|
|
619
|
+
}
|
|
620
|
+
constructor({ environmentId, baseApiUrl, baseMPCRelayApiUrl, enableMPCAccelerator }){
|
|
621
|
+
super({
|
|
622
|
+
environmentId,
|
|
623
|
+
baseApiUrl,
|
|
624
|
+
baseMPCRelayApiUrl,
|
|
625
|
+
enableMPCAccelerator
|
|
626
|
+
}), this.chainName = 'BTC';
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
exports.DynamicBtcWalletClient = DynamicBtcWalletClient;
|
|
631
|
+
exports.ERROR_ACCOUNT_ADDRESS_REQUIRED = ERROR_ACCOUNT_ADDRESS_REQUIRED;
|
|
632
|
+
exports.ERROR_CREATE_WALLET_ACCOUNT = ERROR_CREATE_WALLET_ACCOUNT;
|
|
633
|
+
exports.ERROR_IMPORT_PRIVATE_KEY = ERROR_IMPORT_PRIVATE_KEY;
|
|
634
|
+
exports.ERROR_KEYGEN_FAILED = ERROR_KEYGEN_FAILED;
|
|
635
|
+
exports.ERROR_SIGN_MESSAGE = ERROR_SIGN_MESSAGE;
|
|
636
|
+
exports.ERROR_SIGN_TRANSACTION = ERROR_SIGN_TRANSACTION;
|
package/index.esm.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./src/index";
|