@dynamic-labs-wallet/btc 0.0.253 → 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.js +31 -763
- package/index.esm.js +10 -743
- package/package.json +4 -6
- package/src/client/client.d.ts +0 -6
- package/src/client/client.d.ts.map +1 -1
- package/src/utils/index.d.ts +0 -17
- package/src/utils/index.d.ts.map +1 -1
- package/src/utils/calculateBip322Hash/calculateBip322Hash.d.ts +0 -16
- package/src/utils/calculateBip322Hash/calculateBip322Hash.d.ts.map +0 -1
- package/src/utils/calculateBip322Hash/index.d.ts +0 -2
- package/src/utils/calculateBip322Hash/index.d.ts.map +0 -1
- package/src/utils/calculateTaprootTweak/calculateTaprootTweak.d.ts +0 -7
- package/src/utils/calculateTaprootTweak/calculateTaprootTweak.d.ts.map +0 -1
- package/src/utils/calculateTaprootTweak/index.d.ts +0 -2
- package/src/utils/calculateTaprootTweak/index.d.ts.map +0 -1
- package/src/utils/collectPSBTInputData/collectPSBTInputData.d.ts +0 -13
- package/src/utils/collectPSBTInputData/collectPSBTInputData.d.ts.map +0 -1
- package/src/utils/collectPSBTInputData/index.d.ts +0 -2
- package/src/utils/collectPSBTInputData/index.d.ts.map +0 -1
- package/src/utils/convertPublicKeyToBuffer/convertPublicKeyToBuffer.d.ts +0 -10
- package/src/utils/convertPublicKeyToBuffer/convertPublicKeyToBuffer.d.ts.map +0 -1
- package/src/utils/convertPublicKeyToBuffer/index.d.ts +0 -2
- package/src/utils/convertPublicKeyToBuffer/index.d.ts.map +0 -1
- package/src/utils/convertSignatureToDER/convertSignatureToDER.d.ts +0 -9
- package/src/utils/convertSignatureToDER/convertSignatureToDER.d.ts.map +0 -1
- package/src/utils/convertSignatureToDER/index.d.ts +0 -2
- package/src/utils/convertSignatureToDER/index.d.ts.map +0 -1
- package/src/utils/convertSignatureToTaprootBuffer/convertSignatureToTaprootBuffer.d.ts +0 -9
- package/src/utils/convertSignatureToTaprootBuffer/convertSignatureToTaprootBuffer.d.ts.map +0 -1
- package/src/utils/convertSignatureToTaprootBuffer/index.d.ts +0 -2
- package/src/utils/convertSignatureToTaprootBuffer/index.d.ts.map +0 -1
- package/src/utils/createLegacyAddress/createLegacyAddress.d.ts +0 -10
- package/src/utils/createLegacyAddress/createLegacyAddress.d.ts.map +0 -1
- package/src/utils/createLegacyAddress/index.d.ts +0 -2
- package/src/utils/createLegacyAddress/index.d.ts.map +0 -1
- package/src/utils/createNativeSegWitAddress/createNativeSegWitAddress.d.ts +0 -10
- package/src/utils/createNativeSegWitAddress/createNativeSegWitAddress.d.ts.map +0 -1
- package/src/utils/createNativeSegWitAddress/index.d.ts +0 -2
- package/src/utils/createNativeSegWitAddress/index.d.ts.map +0 -1
- package/src/utils/createSegWitAddress/createSegWitAddress.d.ts +0 -10
- package/src/utils/createSegWitAddress/createSegWitAddress.d.ts.map +0 -1
- package/src/utils/createSegWitAddress/index.d.ts +0 -2
- package/src/utils/createSegWitAddress/index.d.ts.map +0 -1
- package/src/utils/createTaprootAddress/createTaprootAddress.d.ts +0 -10
- package/src/utils/createTaprootAddress/createTaprootAddress.d.ts.map +0 -1
- package/src/utils/createTaprootAddress/index.d.ts +0 -2
- package/src/utils/createTaprootAddress/index.d.ts.map +0 -1
- package/src/utils/doesInputBelongToAddress/doesInputBelongToAddress.d.ts +0 -11
- package/src/utils/doesInputBelongToAddress/doesInputBelongToAddress.d.ts.map +0 -1
- package/src/utils/doesInputBelongToAddress/index.d.ts +0 -2
- package/src/utils/doesInputBelongToAddress/index.d.ts.map +0 -1
- package/src/utils/encodeBip322Signature/encodeBip322Signature.d.ts +0 -13
- package/src/utils/encodeBip322Signature/encodeBip322Signature.d.ts.map +0 -1
- package/src/utils/encodeBip322Signature/index.d.ts +0 -2
- package/src/utils/encodeBip322Signature/index.d.ts.map +0 -1
- package/src/utils/getAddressTypeFromDerivationPath/getAddressTypeFromDerivationPath.d.ts +0 -9
- package/src/utils/getAddressTypeFromDerivationPath/getAddressTypeFromDerivationPath.d.ts.map +0 -1
- package/src/utils/getAddressTypeFromDerivationPath/index.d.ts +0 -2
- package/src/utils/getAddressTypeFromDerivationPath/index.d.ts.map +0 -1
- package/src/utils/getBitcoinNetwork/getBitcoinNetwork.d.ts +0 -10
- package/src/utils/getBitcoinNetwork/getBitcoinNetwork.d.ts.map +0 -1
- package/src/utils/getBitcoinNetwork/index.d.ts +0 -2
- package/src/utils/getBitcoinNetwork/index.d.ts.map +0 -1
- package/src/utils/getDefaultRpcUrl/getDefaultRpcUrl.d.ts +0 -9
- package/src/utils/getDefaultRpcUrl/getDefaultRpcUrl.d.ts.map +0 -1
- package/src/utils/getDefaultRpcUrl/index.d.ts +0 -2
- package/src/utils/getDefaultRpcUrl/index.d.ts.map +0 -1
- package/src/utils/getFeeRates/getFeeRates.d.ts +0 -18
- package/src/utils/getFeeRates/getFeeRates.d.ts.map +0 -1
- package/src/utils/getFeeRates/index.d.ts +0 -2
- package/src/utils/getFeeRates/index.d.ts.map +0 -1
- package/src/utils/getPublicKeyFromPrivateKey/getPublicKeyFromPrivateKey.d.ts +0 -16
- package/src/utils/getPublicKeyFromPrivateKey/getPublicKeyFromPrivateKey.d.ts.map +0 -1
- package/src/utils/getPublicKeyFromPrivateKey/index.d.ts +0 -2
- package/src/utils/getPublicKeyFromPrivateKey/index.d.ts.map +0 -1
- package/src/utils/getUTXOs/getUTXOs.d.ts +0 -15
- package/src/utils/getUTXOs/getUTXOs.d.ts.map +0 -1
- package/src/utils/getUTXOs/index.d.ts +0 -2
- package/src/utils/getUTXOs/index.d.ts.map +0 -1
- package/src/utils/initEccLib/index.d.ts +0 -2
- package/src/utils/initEccLib/index.d.ts.map +0 -1
- package/src/utils/initEccLib/initEccLib.d.ts +0 -5
- package/src/utils/initEccLib/initEccLib.d.ts.map +0 -1
- package/src/utils/normalizeForCompressed/index.d.ts +0 -2
- package/src/utils/normalizeForCompressed/index.d.ts.map +0 -1
- package/src/utils/normalizeForCompressed/normalizeForCompressed.d.ts +0 -9
- package/src/utils/normalizeForCompressed/normalizeForCompressed.d.ts.map +0 -1
- package/src/utils/normalizeForTaproot/index.d.ts +0 -2
- package/src/utils/normalizeForTaproot/index.d.ts.map +0 -1
- package/src/utils/normalizeForTaproot/normalizeForTaproot.d.ts +0 -9
- package/src/utils/normalizeForTaproot/normalizeForTaproot.d.ts.map +0 -1
- package/src/utils/normalizePublicKey/index.d.ts +0 -2
- package/src/utils/normalizePublicKey/index.d.ts.map +0 -1
- package/src/utils/normalizePublicKey/normalizePublicKey.d.ts +0 -13
- package/src/utils/normalizePublicKey/normalizePublicKey.d.ts.map +0 -1
- package/src/utils/privateKeyToWIF/index.d.ts +0 -2
- package/src/utils/privateKeyToWIF/index.d.ts.map +0 -1
- package/src/utils/privateKeyToWIF/privateKeyToWIF.d.ts +0 -24
- package/src/utils/privateKeyToWIF/privateKeyToWIF.d.ts.map +0 -1
- package/src/utils/publicKeyToBitcoinAddress/index.d.ts +0 -2
- package/src/utils/publicKeyToBitcoinAddress/index.d.ts.map +0 -1
- package/src/utils/publicKeyToBitcoinAddress/publicKeyToBitcoinAddress.d.ts +0 -12
- package/src/utils/publicKeyToBitcoinAddress/publicKeyToBitcoinAddress.d.ts.map +0 -1
- package/src/utils/selectUTXOs/index.d.ts +0 -2
- package/src/utils/selectUTXOs/index.d.ts.map +0 -1
- package/src/utils/selectUTXOs/selectUTXOs.d.ts +0 -19
- package/src/utils/selectUTXOs/selectUTXOs.d.ts.map +0 -1
- package/src/utils/toBuffer/index.d.ts +0 -2
- package/src/utils/toBuffer/index.d.ts.map +0 -1
- package/src/utils/toBuffer/toBuffer.d.ts +0 -8
- package/src/utils/toBuffer/toBuffer.d.ts.map +0 -1
- package/src/utils/wifToPrivateKey/index.d.ts +0 -2
- package/src/utils/wifToPrivateKey/index.d.ts.map +0 -1
- package/src/utils/wifToPrivateKey/wifToPrivateKey.d.ts +0 -19
- package/src/utils/wifToPrivateKey/wifToPrivateKey.d.ts.map +0 -1
package/index.cjs.js
CHANGED
|
@@ -3,9 +3,7 @@
|
|
|
3
3
|
var browser = require('@dynamic-labs-wallet/browser');
|
|
4
4
|
var bitcoin = require('bitcoinjs-lib');
|
|
5
5
|
var ecc = require('@bitcoinerlab/secp256k1');
|
|
6
|
-
var
|
|
7
|
-
var bs58 = require('bs58');
|
|
8
|
-
var sha256 = require('@noble/hashes/sha256');
|
|
6
|
+
var btcUtils = require('@dynamic-labs-wallet/btc-utils');
|
|
9
7
|
|
|
10
8
|
function _interopNamespaceDefault(e) {
|
|
11
9
|
var n = Object.create(null);
|
|
@@ -37,722 +35,6 @@ function _extends() {
|
|
|
37
35
|
return _extends.apply(this, arguments);
|
|
38
36
|
}
|
|
39
37
|
|
|
40
|
-
/**
|
|
41
|
-
* Maps a BitcoinNetwork string to a bitcoinjs-lib Network object.
|
|
42
|
-
*
|
|
43
|
-
* @param network - The network identifier ('mainnet' or 'testnet').
|
|
44
|
-
* @returns The corresponding bitcoinjs-lib Network object. Defaults to bitcoin mainnet if the network is not recognized.
|
|
45
|
-
*/ function getBitcoinNetwork(network) {
|
|
46
|
-
switch(network){
|
|
47
|
-
case 'mainnet':
|
|
48
|
-
return bitcoin__namespace.networks.bitcoin;
|
|
49
|
-
case 'testnet':
|
|
50
|
-
return bitcoin__namespace.networks.testnet;
|
|
51
|
-
default:
|
|
52
|
-
return bitcoin__namespace.networks.bitcoin;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Helper to handle Buffer/Uint8Array compatibility issues
|
|
58
|
-
*
|
|
59
|
-
* @param data - The data to convert to a Buffer
|
|
60
|
-
* @returns The Buffer representation of the data
|
|
61
|
-
*/ const toBuffer = (data)=>{
|
|
62
|
-
if (Buffer.isBuffer(data)) return data;
|
|
63
|
-
return Buffer.from(data);
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Creates a native SegWit P2WPKH address
|
|
68
|
-
*
|
|
69
|
-
* @param pubKey - The public key
|
|
70
|
-
* @param networkName - The network name
|
|
71
|
-
* @returns The native SegWit address
|
|
72
|
-
*/ const createNativeSegWitAddress = (pubKey, networkName)=>{
|
|
73
|
-
const network = getBitcoinNetwork(networkName);
|
|
74
|
-
// Convert to Uint8Array
|
|
75
|
-
let pubKeyArray = Uint8Array.from(pubKey);
|
|
76
|
-
// If it's an uncompressed key (65 bytes), compress it (33 bytes)
|
|
77
|
-
if (pubKeyArray.length === 65) {
|
|
78
|
-
// Uncompressed format: 0x04 || x (32 bytes) || y (32 bytes)
|
|
79
|
-
// Compressed format: 0x02 or 0x03 || x (32 bytes)
|
|
80
|
-
const y = pubKeyArray[64];
|
|
81
|
-
const compressedX = pubKeyArray.slice(1, 33); // Skip the 0x04 prefix, take x (32 bytes)
|
|
82
|
-
const compressed = new Uint8Array(33);
|
|
83
|
-
compressed[0] = y % 2 === 0 ? 0x02 : 0x03; // Even y -> 0x02, Odd y -> 0x03
|
|
84
|
-
compressed.set(compressedX, 1);
|
|
85
|
-
pubKeyArray = compressed;
|
|
86
|
-
}
|
|
87
|
-
const { address } = bitcoin.payments.p2wpkh({
|
|
88
|
-
pubkey: toBuffer(pubKeyArray),
|
|
89
|
-
network
|
|
90
|
-
});
|
|
91
|
-
return address || '';
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
let isInitialized = false;
|
|
95
|
-
/**
|
|
96
|
-
* Initializes the ECC library if not already initialized
|
|
97
|
-
*/ const initEccLib = ()=>{
|
|
98
|
-
if (isInitialized) return;
|
|
99
|
-
bitcoin.initEccLib(ecc);
|
|
100
|
-
isInitialized = true;
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Creates a Taproot P2TR address
|
|
105
|
-
*
|
|
106
|
-
* @param pubKey - The public key
|
|
107
|
-
* @param networkName - The network name
|
|
108
|
-
* @returns The Taproot address
|
|
109
|
-
*/ const createTaprootAddress = (pubKey, networkName)=>{
|
|
110
|
-
initEccLib();
|
|
111
|
-
const network = getBitcoinNetwork(networkName);
|
|
112
|
-
// For Taproot, we need the 32-byte x-only public key
|
|
113
|
-
const xOnlyPubKey = pubKey.subarray(-32);
|
|
114
|
-
const xOnlyPubKeyArray = Uint8Array.from(xOnlyPubKey);
|
|
115
|
-
const { address } = bitcoin.payments.p2tr({
|
|
116
|
-
internalPubkey: toBuffer(xOnlyPubKeyArray),
|
|
117
|
-
network
|
|
118
|
-
});
|
|
119
|
-
if (!address) {
|
|
120
|
-
throw new Error('Failed to create Taproot address');
|
|
121
|
-
}
|
|
122
|
-
return address;
|
|
123
|
-
};
|
|
124
|
-
|
|
125
|
-
// eslint-disable-next-line @nx/enforce-module-boundaries
|
|
126
|
-
/**
|
|
127
|
-
* Converts a public key to a Bitcoin address
|
|
128
|
-
*
|
|
129
|
-
* @param publicKey - The public key
|
|
130
|
-
* @param addressType - The address type (default: TAPROOT)
|
|
131
|
-
* @param network - The Bitcoin network (default: 'mainnet')
|
|
132
|
-
* @returns The Bitcoin address
|
|
133
|
-
*/ const publicKeyToBitcoinAddress = (publicKey, addressType = browser.BitcoinAddressType.TAPROOT, network = 'mainnet')=>{
|
|
134
|
-
initEccLib();
|
|
135
|
-
// Convert to Uint8Array to Buffer if needed
|
|
136
|
-
const pubKeyBuffer = toBuffer(publicKey);
|
|
137
|
-
switch(addressType){
|
|
138
|
-
case browser.BitcoinAddressType.NATIVE_SEGWIT:
|
|
139
|
-
return createNativeSegWitAddress(pubKeyBuffer, network);
|
|
140
|
-
case browser.BitcoinAddressType.TAPROOT:
|
|
141
|
-
return createTaprootAddress(pubKeyBuffer, network);
|
|
142
|
-
default:
|
|
143
|
-
throw new Error(`Unsupported address type: ${addressType}`);
|
|
144
|
-
}
|
|
145
|
-
};
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* Converts a public key input (various formats) to a Buffer.
|
|
149
|
-
* Handles objects with `pubkey` property, numeric key maps, `serializeUncompressed` methods, etc.
|
|
150
|
-
*
|
|
151
|
-
* @param pubkey - The raw public key input
|
|
152
|
-
* @returns The public key as a Buffer
|
|
153
|
-
*/ const convertPublicKeyToBuffer = (pubkey)=>{
|
|
154
|
-
const actualPubkey = pubkey;
|
|
155
|
-
if (Buffer.isBuffer(actualPubkey)) {
|
|
156
|
-
return actualPubkey;
|
|
157
|
-
}
|
|
158
|
-
if (actualPubkey instanceof Uint8Array) {
|
|
159
|
-
return Buffer.from(actualPubkey);
|
|
160
|
-
}
|
|
161
|
-
if (typeof actualPubkey === 'string') {
|
|
162
|
-
const result = Buffer.from(actualPubkey, 'hex');
|
|
163
|
-
if (result.length === 0) {
|
|
164
|
-
throw new Error('Invalid public key format');
|
|
165
|
-
}
|
|
166
|
-
return result;
|
|
167
|
-
}
|
|
168
|
-
if (typeof actualPubkey === 'object' && actualPubkey !== null) {
|
|
169
|
-
return convertObjectToBuffer(actualPubkey);
|
|
170
|
-
}
|
|
171
|
-
throw new Error('Invalid public key format');
|
|
172
|
-
};
|
|
173
|
-
const convertObjectToBuffer = (actualPubkey)=>{
|
|
174
|
-
if ('pubkey' in actualPubkey) {
|
|
175
|
-
return convertPublicKeyToBuffer(actualPubkey.pubkey);
|
|
176
|
-
}
|
|
177
|
-
if ('serializeUncompressed' in actualPubkey && typeof actualPubkey.serializeUncompressed === 'function') {
|
|
178
|
-
return Buffer.from(actualPubkey.serializeUncompressed());
|
|
179
|
-
}
|
|
180
|
-
const keys = Object.keys(actualPubkey).map(Number).filter((k)=>!Number.isNaN(k)).sort((a, b)=>a - b);
|
|
181
|
-
if (keys.length > 0) {
|
|
182
|
-
return Buffer.from(keys.map((k)=>actualPubkey[k]));
|
|
183
|
-
}
|
|
184
|
-
throw new Error('Invalid public key object');
|
|
185
|
-
};
|
|
186
|
-
|
|
187
|
-
/**
|
|
188
|
-
* Normalizes a public key buffer for Taproot (P2TR).
|
|
189
|
-
* Ensures the result is a 32-byte x-only key.
|
|
190
|
-
*
|
|
191
|
-
* @param buffer - The public key buffer
|
|
192
|
-
* @returns The 32-byte x-only public key buffer
|
|
193
|
-
*/ const normalizeForTaproot = (buffer)=>{
|
|
194
|
-
if (buffer.length === 32) return buffer;
|
|
195
|
-
if (buffer.length === 33) return buffer.subarray(1, 33);
|
|
196
|
-
if (buffer.length === 65) return buffer.subarray(1, 33);
|
|
197
|
-
if (buffer.length === 64) return buffer.subarray(0, 32);
|
|
198
|
-
throw new Error(`Unexpected public key length for Taproot: ${buffer.length}`);
|
|
199
|
-
};
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* Normalizes a public key buffer for Compressed formats (Legacy, SegWit, Native SegWit).
|
|
203
|
-
* Ensures the result is a 33-byte compressed key.
|
|
204
|
-
*
|
|
205
|
-
* @param buffer - The public key buffer
|
|
206
|
-
* @returns The 33-byte compressed public key buffer
|
|
207
|
-
*/ const normalizeForCompressed = (buffer)=>{
|
|
208
|
-
if (buffer.length === 33) {
|
|
209
|
-
return buffer;
|
|
210
|
-
}
|
|
211
|
-
if (buffer.length === 65 && buffer[0] === 0x04) {
|
|
212
|
-
// Uncompressed standard (04 + X + Y)
|
|
213
|
-
const x = buffer.subarray(1, 33);
|
|
214
|
-
const yLastByte = buffer[64];
|
|
215
|
-
return compressPublicKey(x, yLastByte);
|
|
216
|
-
}
|
|
217
|
-
if (buffer.length === 64) {
|
|
218
|
-
// Raw X + Y (missing 04 prefix)
|
|
219
|
-
const x = buffer.subarray(0, 32);
|
|
220
|
-
const yLastByte = buffer[63];
|
|
221
|
-
return compressPublicKey(x, yLastByte);
|
|
222
|
-
}
|
|
223
|
-
throw new Error(`Unexpected public key length: ${buffer.length}`);
|
|
224
|
-
};
|
|
225
|
-
const compressPublicKey = (x, yLastByte)=>{
|
|
226
|
-
const prefix = yLastByte % 2 === 0 ? 0x02 : 0x03;
|
|
227
|
-
return Buffer.concat([
|
|
228
|
-
Buffer.from([
|
|
229
|
-
prefix
|
|
230
|
-
]),
|
|
231
|
-
x
|
|
232
|
-
]);
|
|
233
|
-
};
|
|
234
|
-
|
|
235
|
-
// eslint-disable @nx/enforce-module-boundaries
|
|
236
|
-
/**
|
|
237
|
-
* Normalizes a public key to a standard Buffer format
|
|
238
|
-
* Handles various input types (Uint8Array, Buffer, object with pubKeyAsHex, etc.)
|
|
239
|
-
* and formats (32-byte x-only, 33-byte compressed, 65-byte uncompressed)
|
|
240
|
-
* based on the target address type.
|
|
241
|
-
*
|
|
242
|
-
* @param pubkey - The raw public key input
|
|
243
|
-
* @param addressType - The target Bitcoin address type
|
|
244
|
-
* @returns The normalized public key as a Buffer
|
|
245
|
-
*/ const normalizePublicKey = (pubkey, addressType)=>{
|
|
246
|
-
const buffer = convertPublicKeyToBuffer(pubkey);
|
|
247
|
-
if (addressType === browser.BitcoinAddressType.TAPROOT) {
|
|
248
|
-
return normalizeForTaproot(buffer);
|
|
249
|
-
}
|
|
250
|
-
return normalizeForCompressed(buffer);
|
|
251
|
-
};
|
|
252
|
-
|
|
253
|
-
// eslint-disable-next-line @nx/enforce-module-boundaries
|
|
254
|
-
/**
|
|
255
|
-
* Infers the Bitcoin address type from a BIP-44 derivation path
|
|
256
|
-
*
|
|
257
|
-
* @param derivationPathStr - The derivation path string (e.g. "m/44'/0'/0'/0/0" as JSON or string)
|
|
258
|
-
* @returns The inferred BitcoinAddressType
|
|
259
|
-
*/ const getAddressTypeFromDerivationPath = (derivationPathStr)=>{
|
|
260
|
-
const derivationPathObj = JSON.parse(derivationPathStr);
|
|
261
|
-
const path = Object.values(derivationPathObj).map(Number);
|
|
262
|
-
if (path.length < 2) throw new Error('Invalid derivation path length');
|
|
263
|
-
const purpose = path[0];
|
|
264
|
-
// Handle both raw and hardened just in case, or just raw if strictly non-hardened
|
|
265
|
-
// 44 = 0x2C (Legacy), 49 = 0x31 (SegWit P2SH), 84 = 0x54 (Native SegWit), 86 = 0x56 (Taproot)
|
|
266
|
-
const purposeIndex = purpose >= 0x80000000 ? purpose - 0x80000000 : purpose;
|
|
267
|
-
let addressType;
|
|
268
|
-
switch(purposeIndex){
|
|
269
|
-
case 84:
|
|
270
|
-
addressType = browser.BitcoinAddressType.NATIVE_SEGWIT;
|
|
271
|
-
break;
|
|
272
|
-
case 86:
|
|
273
|
-
addressType = browser.BitcoinAddressType.TAPROOT;
|
|
274
|
-
break;
|
|
275
|
-
default:
|
|
276
|
-
throw new Error(`Unknown derivation path purpose: ${purposeIndex}`);
|
|
277
|
-
}
|
|
278
|
-
return addressType;
|
|
279
|
-
};
|
|
280
|
-
|
|
281
|
-
/**
|
|
282
|
-
* Calculates the hash to be signed for a BIP-322 message verification
|
|
283
|
-
*
|
|
284
|
-
* @param message - The message to sign
|
|
285
|
-
* @param pubKey - The public key of the signer
|
|
286
|
-
* @param addressType - The address type (e.g. TAPROOT, SEGWIT)
|
|
287
|
-
* @param network - The Bitcoin network
|
|
288
|
-
* @returns The formatted message hash and the PSBT to be signed
|
|
289
|
-
*/ const calculateBip322Hash = (message, pubKey, addressType, network)=>{
|
|
290
|
-
// Normalize key and derive address
|
|
291
|
-
const normalizedKey = normalizePublicKey(pubKey, addressType);
|
|
292
|
-
const address = publicKeyToBitcoinAddress(normalizedKey, addressType, network);
|
|
293
|
-
if (!address) {
|
|
294
|
-
throw new Error('Failed to generate address for BIP-322');
|
|
295
|
-
}
|
|
296
|
-
const scriptPubKey = bip322Js.Address.convertAdressToScriptPubkey(address);
|
|
297
|
-
const toSpendTx = bip322Js.BIP322.buildToSpendTx(message, scriptPubKey);
|
|
298
|
-
const toSignPsbt = bip322Js.BIP322.buildToSignTx(toSpendTx.getId(), scriptPubKey);
|
|
299
|
-
// Calculate Hash to Sign
|
|
300
|
-
const txToSign = toSignPsbt.__CACHE.__TX; // Access underlying TX like prototype
|
|
301
|
-
let hashToSign;
|
|
302
|
-
if (addressType === browser.BitcoinAddressType.TAPROOT) {
|
|
303
|
-
// Taproot (BIP-341) Signing
|
|
304
|
-
const prevOutScripts = [
|
|
305
|
-
scriptPubKey
|
|
306
|
-
];
|
|
307
|
-
const values = [
|
|
308
|
-
0
|
|
309
|
-
]; // BIP-322 value
|
|
310
|
-
// Cast to any to avoid type issues if definition is missing
|
|
311
|
-
hashToSign = Buffer.from(txToSign.hashForWitnessV1(0, prevOutScripts, values, bitcoin__namespace.Transaction.SIGHASH_DEFAULT // Default sighash for Taproot
|
|
312
|
-
));
|
|
313
|
-
} else {
|
|
314
|
-
// SegWit (BIP-143) Signing (P2WPKH)
|
|
315
|
-
const txToSignSegwit = new bitcoin__namespace.Transaction();
|
|
316
|
-
txToSignSegwit.version = 0;
|
|
317
|
-
txToSignSegwit.locktime = 0;
|
|
318
|
-
const prevTxId = toSpendTx.getId();
|
|
319
|
-
const prevHash = Buffer.from(prevTxId, 'hex').reverse();
|
|
320
|
-
txToSignSegwit.addInput(prevHash, 0, 0);
|
|
321
|
-
txToSignSegwit.addOutput(Buffer.from([
|
|
322
|
-
0x6a
|
|
323
|
-
]), BigInt(0));
|
|
324
|
-
const p2pkh = bitcoin__namespace.payments.p2pkh({
|
|
325
|
-
pubkey: normalizedKey,
|
|
326
|
-
network: network === 'testnet' ? bitcoin__namespace.networks.testnet : bitcoin__namespace.networks.bitcoin
|
|
327
|
-
});
|
|
328
|
-
const scriptCode = p2pkh.output;
|
|
329
|
-
if (!scriptCode) throw new Error('Failed to generate scriptCode');
|
|
330
|
-
hashToSign = txToSignSegwit.hashForWitnessV0(0, scriptCode, BigInt(0), bitcoin__namespace.Transaction.SIGHASH_ALL);
|
|
331
|
-
}
|
|
332
|
-
return {
|
|
333
|
-
formattedMessage: new Uint8Array(hashToSign),
|
|
334
|
-
toSignPsbt
|
|
335
|
-
};
|
|
336
|
-
};
|
|
337
|
-
|
|
338
|
-
/**
|
|
339
|
-
* Encodes the signature into the BIP-322 witness format
|
|
340
|
-
*
|
|
341
|
-
* @param toSignPsbt - The PSBT that was signed
|
|
342
|
-
* @param pubKey - The public key of the signer
|
|
343
|
-
* @param signature - The signature produced by the signer
|
|
344
|
-
* @param addressType - The address type
|
|
345
|
-
* @returns The base64 encoded BIP-322 signature
|
|
346
|
-
*/ const encodeBip322Signature = (toSignPsbt, pubKey, signature, addressType)=>{
|
|
347
|
-
const normalizedKey = normalizePublicKey(pubKey, addressType);
|
|
348
|
-
if (addressType === browser.BitcoinAddressType.TAPROOT) {
|
|
349
|
-
// Prototype logic: direct update
|
|
350
|
-
// Signature from MPC should be 64 bytes (r|s)
|
|
351
|
-
let sigBuffer;
|
|
352
|
-
if (signature instanceof Uint8Array || Buffer.isBuffer(signature)) {
|
|
353
|
-
sigBuffer = Buffer.from(signature);
|
|
354
|
-
} else {
|
|
355
|
-
// ECDSA signature object? Should be raw bytes for Schnorr/BIP340
|
|
356
|
-
// If it's EcdsaSignature (r,s), concat them.
|
|
357
|
-
const r = signature.r;
|
|
358
|
-
const s = signature.s;
|
|
359
|
-
const rBuf = Buffer.isBuffer(r) ? r : Buffer.from(r);
|
|
360
|
-
const sBuf = Buffer.isBuffer(s) ? s : Buffer.from(s);
|
|
361
|
-
sigBuffer = Buffer.concat([
|
|
362
|
-
rBuf,
|
|
363
|
-
sBuf
|
|
364
|
-
]);
|
|
365
|
-
}
|
|
366
|
-
toSignPsbt.updateInput(0, {
|
|
367
|
-
tapKeySig: sigBuffer
|
|
368
|
-
});
|
|
369
|
-
} else {
|
|
370
|
-
// Serialize the signature (COMPACT SIGNATURE)
|
|
371
|
-
const r = signature.r;
|
|
372
|
-
const s = signature.s;
|
|
373
|
-
const compactSignature = Buffer.concat([
|
|
374
|
-
Buffer.from(r),
|
|
375
|
-
Buffer.from(s)
|
|
376
|
-
]);
|
|
377
|
-
// Apply Signature
|
|
378
|
-
const mpcSignerForPsbt = {
|
|
379
|
-
publicKey: normalizedKey,
|
|
380
|
-
sign: (_hash)=>{
|
|
381
|
-
return compactSignature;
|
|
382
|
-
}
|
|
383
|
-
};
|
|
384
|
-
// Sign the input
|
|
385
|
-
toSignPsbt.signInput(0, mpcSignerForPsbt);
|
|
386
|
-
}
|
|
387
|
-
// Finalize the inputs
|
|
388
|
-
toSignPsbt.finalizeAllInputs();
|
|
389
|
-
// Encode Final Result
|
|
390
|
-
return bip322Js.BIP322.encodeWitness(toSignPsbt);
|
|
391
|
-
};
|
|
392
|
-
|
|
393
|
-
initEccLib();
|
|
394
|
-
/**
|
|
395
|
-
* Converts a private key (hex string) to Bitcoin WIF (Wallet Import Format)
|
|
396
|
-
* WIF is the standard format that Bitcoin wallets expect for importing private keys
|
|
397
|
-
*
|
|
398
|
-
* WIF encoding steps:
|
|
399
|
-
* 1. Add network prefix (0x80 for mainnet, 0xef for testnet/regtest)
|
|
400
|
-
* 2. Add compression flag (0x01) for compressed public keys (used for SegWit)
|
|
401
|
-
* 3. Calculate checksum (double SHA-256, take first 4 bytes)
|
|
402
|
-
* 4. Base58 encode the result
|
|
403
|
-
*
|
|
404
|
-
* @param privateKey - Private key as hex string (with or without 0x prefix)
|
|
405
|
-
* @param network - Bitcoin network (mainnet, testnet)
|
|
406
|
-
* @param options - Optional configuration
|
|
407
|
-
* @param options.compressed - Whether to use compressed format (default: true for SegWit compatibility)
|
|
408
|
-
* @param options.onWarning - Optional callback for warnings (e.g., unexpected key length)
|
|
409
|
-
* @returns WIF-encoded private key string
|
|
410
|
-
* @throws Error if conversion fails
|
|
411
|
-
*/ const privateKeyToWIF = (privateKey, network = 'mainnet', options)=>{
|
|
412
|
-
const { compressed = true, onWarning } = options || {};
|
|
413
|
-
try {
|
|
414
|
-
// Remove 0x prefix if present
|
|
415
|
-
const cleanPrivateKey = privateKey.startsWith('0x') ? privateKey.slice(2) : privateKey;
|
|
416
|
-
// Validate hex string length (should be 64 characters = 32 bytes)
|
|
417
|
-
if (cleanPrivateKey.length !== 64) {
|
|
418
|
-
onWarning == null ? void 0 : onWarning('Unexpected private key length', {
|
|
419
|
-
length: cleanPrivateKey.length,
|
|
420
|
-
expected: 64
|
|
421
|
-
});
|
|
422
|
-
}
|
|
423
|
-
// Convert hex to Uint8Array
|
|
424
|
-
const privateKeyBytes = new Uint8Array(cleanPrivateKey.match(/.{1,2}/g).map((byte)=>Number.parseInt(byte, 16)));
|
|
425
|
-
// Step 1: Get network prefix from bitcoinjs-lib network
|
|
426
|
-
const networkConfig = getBitcoinNetwork(network);
|
|
427
|
-
const networkPrefix = networkConfig.wif;
|
|
428
|
-
// Step 2: Build extended key with network prefix and optional compression flag
|
|
429
|
-
// For Native SegWit and SegWit, we use compressed format (0x01 flag)
|
|
430
|
-
const extendedKeyLength = compressed ? 34 : 33; // 1 byte prefix + 32 bytes key + (optional) 1 byte compression
|
|
431
|
-
const extendedKey = new Uint8Array(extendedKeyLength);
|
|
432
|
-
extendedKey[0] = networkPrefix;
|
|
433
|
-
extendedKey.set(privateKeyBytes, 1);
|
|
434
|
-
if (compressed) {
|
|
435
|
-
extendedKey[33] = 0x01; // Compression flag for SegWit addresses
|
|
436
|
-
}
|
|
437
|
-
// Step 3: Calculate checksum (double SHA-256, take first 4 bytes)
|
|
438
|
-
const firstHash = sha256.sha256(extendedKey);
|
|
439
|
-
const secondHash = sha256.sha256(firstHash);
|
|
440
|
-
const checksum = secondHash.slice(0, 4);
|
|
441
|
-
// Step 4: Append checksum and Base58 encode
|
|
442
|
-
const wifBytes = new Uint8Array(extendedKey.length + checksum.length);
|
|
443
|
-
wifBytes.set(extendedKey, 0);
|
|
444
|
-
wifBytes.set(checksum, extendedKey.length);
|
|
445
|
-
const wif = bs58.encode(wifBytes);
|
|
446
|
-
return wif;
|
|
447
|
-
} catch (error) {
|
|
448
|
-
// Re-throw with more context
|
|
449
|
-
throw new Error(`Failed to convert private key to WIF: ${error instanceof Error ? error.message : String(error)}`);
|
|
450
|
-
}
|
|
451
|
-
};
|
|
452
|
-
|
|
453
|
-
/**
|
|
454
|
-
* Converts a Bitcoin WIF (Wallet Import Format) private key to hex format
|
|
455
|
-
* This is the reverse operation of `privateKeyToWIF`
|
|
456
|
-
*
|
|
457
|
-
* WIF decoding steps:
|
|
458
|
-
* 1. Base58 decode the WIF string
|
|
459
|
-
* 2. Validate network prefix
|
|
460
|
-
* 3. Validate checksum (double SHA-256, first 4 bytes)
|
|
461
|
-
* 4. Extract private key bytes (skip prefix, compression flag, and checksum)
|
|
462
|
-
* 5. Convert to hex string
|
|
463
|
-
*
|
|
464
|
-
* @param wif - Private key in WIF format (Base58 encoded)
|
|
465
|
-
* @param network - Bitcoin network (mainnet, testnet)
|
|
466
|
-
* @returns Private key as hex string (64 characters, 32 bytes)
|
|
467
|
-
* @throws Error if WIF format is invalid
|
|
468
|
-
*/ const wifToPrivateKey = (wif, network = 'mainnet')=>{
|
|
469
|
-
try {
|
|
470
|
-
// 1. Decode Base58
|
|
471
|
-
const decoded = bs58.decode(wif);
|
|
472
|
-
// 2. Validate length (should be 37 bytes: 1 prefix + 32 key + 1 compression + 4 checksum)
|
|
473
|
-
// or 38 bytes (with compression flag)
|
|
474
|
-
if (decoded.length !== 37 && decoded.length !== 38) {
|
|
475
|
-
throw new Error(`Invalid WIF length: ${decoded.length}, expected 37 or 38 bytes`);
|
|
476
|
-
}
|
|
477
|
-
// 3. Get network config to validate prefix
|
|
478
|
-
const networkConfig = getBitcoinNetwork(network);
|
|
479
|
-
const expectedPrefix = networkConfig.wif;
|
|
480
|
-
// 4. Validate network prefix
|
|
481
|
-
if (decoded[0] !== expectedPrefix) {
|
|
482
|
-
throw new Error(`Invalid WIF network prefix: expected ${expectedPrefix}, got ${decoded[0]}`);
|
|
483
|
-
}
|
|
484
|
-
// 5. Validate checksum
|
|
485
|
-
const payload = decoded.slice(0, -4);
|
|
486
|
-
const checksum = decoded.slice(-4);
|
|
487
|
-
const firstHash = sha256.sha256(payload);
|
|
488
|
-
const secondHash = sha256.sha256(firstHash);
|
|
489
|
-
const calculatedChecksum = secondHash.slice(0, 4);
|
|
490
|
-
if (!calculatedChecksum.every((byte, index)=>byte === checksum[index])) {
|
|
491
|
-
throw new Error('Invalid WIF checksum');
|
|
492
|
-
}
|
|
493
|
-
// 6. Extract private key bytes (skip prefix, skip compression flag if present, skip checksum)
|
|
494
|
-
// Private key is always 32 bytes, starting at index 1
|
|
495
|
-
const privateKeyStart = 1;
|
|
496
|
-
const privateKeyEnd = 33; // 32 bytes of private key
|
|
497
|
-
const privateKeyBytes = decoded.slice(privateKeyStart, privateKeyEnd);
|
|
498
|
-
// 7. Convert to hex string
|
|
499
|
-
return Buffer.from(privateKeyBytes).toString('hex');
|
|
500
|
-
} catch (error) {
|
|
501
|
-
throw new Error(`Invalid WIF format: ${error instanceof Error ? error.message : String(error)}. Only WIF format is supported.`);
|
|
502
|
-
}
|
|
503
|
-
};
|
|
504
|
-
|
|
505
|
-
/**
|
|
506
|
-
* Derives a public key from a private key (WIF format) based on address type
|
|
507
|
-
*
|
|
508
|
-
* - For NATIVE_SEGWIT (ECDSA): Returns compressed public key (33 bytes)
|
|
509
|
-
* - For TAPROOT (BIP340): Returns x-only public key (32 bytes)
|
|
510
|
-
*
|
|
511
|
-
* @param privateKey - Private key in WIF format (Base58 encoded)
|
|
512
|
-
* @param addressType - Bitcoin address type (NATIVE_SEGWIT or TAPROOT)
|
|
513
|
-
* @param network - Bitcoin network (mainnet, testnet)
|
|
514
|
-
* @returns Public key as Buffer (33 bytes for NATIVE_SEGWIT, 32 bytes for TAPROOT)
|
|
515
|
-
* @throws Error if private key is invalid or derivation fails
|
|
516
|
-
*/ const getPublicKeyFromPrivateKey = async (privateKey, addressType, network = 'mainnet')=>{
|
|
517
|
-
initEccLib();
|
|
518
|
-
try {
|
|
519
|
-
// 1. Decode WIF to hex
|
|
520
|
-
const privateKeyHex = wifToPrivateKey(privateKey, network);
|
|
521
|
-
const privateKeyBytes = new Uint8Array(Buffer.from(privateKeyHex, 'hex'));
|
|
522
|
-
// 2. Import secp256k1 library
|
|
523
|
-
const ecc = await import('@bitcoinerlab/secp256k1');
|
|
524
|
-
// 3. Derive public key based on address type
|
|
525
|
-
if (addressType === 'taproot') {
|
|
526
|
-
// BIP340/Taproot: Derive x-only public key (32 bytes)
|
|
527
|
-
// Get the public key point
|
|
528
|
-
const publicKeyPoint = ecc.pointFromScalar(privateKeyBytes);
|
|
529
|
-
if (!publicKeyPoint) {
|
|
530
|
-
throw new Error('Failed to derive public key from private key');
|
|
531
|
-
}
|
|
532
|
-
// For Taproot, we need the 32-byte x-only public key
|
|
533
|
-
// publicKeyPoint is 33 bytes (0x02/0x03 + 32 bytes x), we take the last 32 bytes
|
|
534
|
-
return Buffer.from(publicKeyPoint.subarray(1)); // Skip prefix, get x-coordinate
|
|
535
|
-
} else {
|
|
536
|
-
// NATIVE_SEGWIT (ECDSA): Derive compressed public key (33 bytes)
|
|
537
|
-
const publicKeyPoint = ecc.pointFromScalar(privateKeyBytes, true); // compressed = true
|
|
538
|
-
if (!publicKeyPoint) {
|
|
539
|
-
throw new Error('Failed to derive public key from private key');
|
|
540
|
-
}
|
|
541
|
-
return Buffer.from(publicKeyPoint); // 33 bytes compressed
|
|
542
|
-
}
|
|
543
|
-
} catch (error) {
|
|
544
|
-
throw new Error(`Failed to derive public key from private key: ${error instanceof Error ? error.message : String(error)}`);
|
|
545
|
-
}
|
|
546
|
-
};
|
|
547
|
-
|
|
548
|
-
/**
|
|
549
|
-
* Converts an ECDSA signature to DER format
|
|
550
|
-
*
|
|
551
|
-
* @param signature - The ECDSA signature
|
|
552
|
-
* @returns The DER encoded signature
|
|
553
|
-
*/ const convertSignatureToDER = (signature)=>{
|
|
554
|
-
// Construct raw 64-byte signature (r + s) using Uint8Array to ensure compatibility
|
|
555
|
-
const r = signature.r instanceof Uint8Array ? signature.r : new Uint8Array(signature.r);
|
|
556
|
-
const s = signature.s instanceof Uint8Array ? signature.s : new Uint8Array(signature.s);
|
|
557
|
-
// Enforce 32-byte length (padding or slicing if needed)
|
|
558
|
-
const r32 = new Uint8Array(32);
|
|
559
|
-
const rSource = r.length > 32 ? r.slice(-32) : r;
|
|
560
|
-
r32.set(rSource, 32 - rSource.length);
|
|
561
|
-
const s32 = new Uint8Array(32);
|
|
562
|
-
const sSource = s.length > 32 ? s.slice(-32) : s;
|
|
563
|
-
s32.set(sSource, 32 - sSource.length);
|
|
564
|
-
const rawSignature = new Uint8Array(64);
|
|
565
|
-
rawSignature.set(r32, 0);
|
|
566
|
-
rawSignature.set(s32, 32);
|
|
567
|
-
const derSignature = bitcoin__namespace.script.signature.encode(rawSignature, bitcoin__namespace.Transaction.SIGHASH_ALL);
|
|
568
|
-
return derSignature;
|
|
569
|
-
};
|
|
570
|
-
|
|
571
|
-
/**
|
|
572
|
-
* Helper to get the default RPC URL
|
|
573
|
-
*
|
|
574
|
-
* @param network - The Bitcoin network
|
|
575
|
-
* @returns The default RPC URL
|
|
576
|
-
*/ const getDefaultRpcUrl = (network)=>{
|
|
577
|
-
return network === 'mainnet' ? 'https://mempool.space/api' : 'https://mempool.space/testnet/api';
|
|
578
|
-
};
|
|
579
|
-
|
|
580
|
-
/**
|
|
581
|
-
* Fetches UTXOs for a given address using mempool.space API
|
|
582
|
-
*
|
|
583
|
-
* @param address - The address to fetch UTXOs for
|
|
584
|
-
* @param network - The Bitcoin network (default: 'mainnet')
|
|
585
|
-
* @param rpcUrl - Optional custom RPC URL
|
|
586
|
-
* @returns The list of UTXOs
|
|
587
|
-
*/ const getUTXOs = async ({ address, network = 'mainnet', rpcUrl })=>{
|
|
588
|
-
const baseUrl = rpcUrl || getDefaultRpcUrl(network);
|
|
589
|
-
try {
|
|
590
|
-
const response = await fetch(`${baseUrl}/address/${address}/utxo`);
|
|
591
|
-
if (!response.ok) {
|
|
592
|
-
throw new Error(`Failed to fetch UTXOs: ${response.statusText}`);
|
|
593
|
-
}
|
|
594
|
-
const utxos = await response.json();
|
|
595
|
-
return utxos.map((utxo)=>({
|
|
596
|
-
txid: utxo.txid,
|
|
597
|
-
vout: utxo.vout,
|
|
598
|
-
value: BigInt(utxo.value)
|
|
599
|
-
}));
|
|
600
|
-
} catch (error) {
|
|
601
|
-
throw new Error(`Error fetching UTXOs: ${error}`);
|
|
602
|
-
}
|
|
603
|
-
};
|
|
604
|
-
|
|
605
|
-
/**
|
|
606
|
-
* Simple greedy coin selection
|
|
607
|
-
*
|
|
608
|
-
* @param utxos - The list of UTXOs to select from
|
|
609
|
-
* @param amount - The amount to spend
|
|
610
|
-
* @param feeRate - The fee rate in sat/vByte
|
|
611
|
-
* @returns The selected inputs, change amount, and fee
|
|
612
|
-
*/ const selectUTXOs = ({ utxos, amount, feeRate })=>{
|
|
613
|
-
// Sort by value descending
|
|
614
|
-
const sorted = [
|
|
615
|
-
...utxos
|
|
616
|
-
].sort((a, b)=>Number(b.value - a.value));
|
|
617
|
-
const inputs = [];
|
|
618
|
-
let totalInput = 0n;
|
|
619
|
-
// Basic fee estimation constants
|
|
620
|
-
// Optimized for Native SegWit (P2WPKH) only
|
|
621
|
-
// P2WPKH: ~68 vbytes per input, ~31 vbytes per output
|
|
622
|
-
// Overhead: ~10 vbytes
|
|
623
|
-
const INPUT_SIZE = 68;
|
|
624
|
-
const OUTPUT_SIZE = 31;
|
|
625
|
-
const OVERHEAD = 10;
|
|
626
|
-
for (const utxo of sorted){
|
|
627
|
-
inputs.push(utxo);
|
|
628
|
-
totalInput += utxo.value;
|
|
629
|
-
// Calculate fee for current set + 2 outputs (recipient + change)
|
|
630
|
-
const vbytes = OVERHEAD + inputs.length * INPUT_SIZE + 2 * OUTPUT_SIZE;
|
|
631
|
-
const fee = BigInt(Math.ceil(vbytes * feeRate));
|
|
632
|
-
if (totalInput >= amount + fee) {
|
|
633
|
-
return {
|
|
634
|
-
inputs,
|
|
635
|
-
change: totalInput - amount - fee,
|
|
636
|
-
fee
|
|
637
|
-
};
|
|
638
|
-
}
|
|
639
|
-
}
|
|
640
|
-
throw new Error('Insufficient funds');
|
|
641
|
-
};
|
|
642
|
-
|
|
643
|
-
/**
|
|
644
|
-
* Fetches current fee rates from mempool.space
|
|
645
|
-
* Returns fees in sat/vByte
|
|
646
|
-
*
|
|
647
|
-
* @param network - The Bitcoin network (default: 'mainnet')
|
|
648
|
-
* @param rpcUrl - Optional custom RPC URL
|
|
649
|
-
* @returns The fee rates for fast, medium, and slow transactions
|
|
650
|
-
*/ const getFeeRates = async ({ network = 'mainnet', rpcUrl })=>{
|
|
651
|
-
const baseUrl = rpcUrl || getDefaultRpcUrl(network);
|
|
652
|
-
const response = await fetch(`${baseUrl}/v1/fees/recommended`);
|
|
653
|
-
if (!response.ok) {
|
|
654
|
-
throw new Error(`Failed to fetch fee rates: ${response.statusText}`);
|
|
655
|
-
}
|
|
656
|
-
const fees = await response.json();
|
|
657
|
-
return {
|
|
658
|
-
fast: fees.fastestFee,
|
|
659
|
-
medium: fees.halfHourFee,
|
|
660
|
-
slow: fees.hourFee
|
|
661
|
-
};
|
|
662
|
-
};
|
|
663
|
-
|
|
664
|
-
/**
|
|
665
|
-
* Calculates the Taproot tweak hash for BIP340 signing
|
|
666
|
-
* @param pubKey - The normalized public key (32-byte x-only for Taproot)
|
|
667
|
-
* @returns The tweak hash as a hex string (64 hex chars = 32 bytes)
|
|
668
|
-
*/ const calculateTaprootTweak = (pubKey)=>{
|
|
669
|
-
const tweakHash = bitcoin__namespace.crypto.taggedHash('TapTweak', pubKey);
|
|
670
|
-
return Buffer.from(tweakHash).toString('hex');
|
|
671
|
-
};
|
|
672
|
-
|
|
673
|
-
/**
|
|
674
|
-
* Converts a signature to a Buffer format suitable for Taproot (BIP340/Schnorr)
|
|
675
|
-
* Taproot signatures are 64 bytes (r|s concatenated)
|
|
676
|
-
* @param signature - The signature from MPC (can be Uint8Array, Buffer, or EcdsaSignature object)
|
|
677
|
-
* @returns A Buffer containing the 64-byte Schnorr signature
|
|
678
|
-
*/ const convertSignatureToTaprootBuffer = (signature)=>{
|
|
679
|
-
if (signature instanceof Uint8Array || Buffer.isBuffer(signature)) {
|
|
680
|
-
return Buffer.from(signature);
|
|
681
|
-
}
|
|
682
|
-
// ECDSA signature object - concat r and s for Schnorr
|
|
683
|
-
const r = signature.r;
|
|
684
|
-
const s = signature.s;
|
|
685
|
-
const rBuf = Buffer.isBuffer(r) ? r : Buffer.from(r);
|
|
686
|
-
const sBuf = Buffer.isBuffer(s) ? s : Buffer.from(s);
|
|
687
|
-
return Buffer.concat([
|
|
688
|
-
rBuf,
|
|
689
|
-
sBuf
|
|
690
|
-
]);
|
|
691
|
-
};
|
|
692
|
-
|
|
693
|
-
/**
|
|
694
|
-
* Collects prevOutScripts and values from all PSBT inputs
|
|
695
|
-
* Required for Taproot (BIP-341) hashForWitnessV1 calculation
|
|
696
|
-
* @param psbt - The PSBT to collect input data from
|
|
697
|
-
* @returns An object containing arrays of prevOutScripts and values
|
|
698
|
-
* @throws Error if any input is missing witnessUtxo
|
|
699
|
-
*/ const collectPSBTInputData = (psbt)=>{
|
|
700
|
-
const prevOutScripts = [];
|
|
701
|
-
const values = [];
|
|
702
|
-
psbt.data.inputs.forEach((input, index)=>{
|
|
703
|
-
if (!input.witnessUtxo) {
|
|
704
|
-
throw new Error(`Input ${index} missing witnessUtxo`);
|
|
705
|
-
}
|
|
706
|
-
prevOutScripts.push(Buffer.isBuffer(input.witnessUtxo.script) ? input.witnessUtxo.script : Buffer.from(input.witnessUtxo.script));
|
|
707
|
-
values.push(input.witnessUtxo.value);
|
|
708
|
-
});
|
|
709
|
-
return {
|
|
710
|
-
prevOutScripts,
|
|
711
|
-
values
|
|
712
|
-
};
|
|
713
|
-
};
|
|
714
|
-
|
|
715
|
-
/**
|
|
716
|
-
* Checks if a PSBT input belongs to a specific address
|
|
717
|
-
* @param input - The PSBT input (from psbt.data.inputs)
|
|
718
|
-
* @param address - The address to check against
|
|
719
|
-
* @param network - The Bitcoin network
|
|
720
|
-
* @returns True if the input belongs to the address, false otherwise
|
|
721
|
-
*/ const doesInputBelongToAddress = (input, address, network)=>{
|
|
722
|
-
if (!input.witnessUtxo) {
|
|
723
|
-
return false;
|
|
724
|
-
}
|
|
725
|
-
const script = Buffer.isBuffer(input.witnessUtxo.script) ? input.witnessUtxo.script : Buffer.from(input.witnessUtxo.script);
|
|
726
|
-
const bitcoinNetwork = getBitcoinNetwork(network);
|
|
727
|
-
try {
|
|
728
|
-
// Try P2WPKH (Native SegWit) - script is OP_0 <20-byte hash>
|
|
729
|
-
if (script.length === 22 && script[0] === 0x00 && script[1] === 0x14) {
|
|
730
|
-
const p2wpkh = bitcoin__namespace.payments.p2wpkh({
|
|
731
|
-
output: script,
|
|
732
|
-
network: bitcoinNetwork
|
|
733
|
-
});
|
|
734
|
-
return p2wpkh.address === address;
|
|
735
|
-
}
|
|
736
|
-
// Try P2TR (Taproot) - script is OP_1 <32-byte x-only pubkey>
|
|
737
|
-
if (script.length === 34 && script[0] === 0x51 && script[1] === 0x20) {
|
|
738
|
-
const p2tr = bitcoin__namespace.payments.p2tr({
|
|
739
|
-
output: script,
|
|
740
|
-
network: bitcoinNetwork
|
|
741
|
-
});
|
|
742
|
-
return p2tr.address === address;
|
|
743
|
-
}
|
|
744
|
-
return false;
|
|
745
|
-
} catch (error) {
|
|
746
|
-
// eslint-disable-next-line no-console
|
|
747
|
-
console.log('[doesInputBelongToAddress] Error checking input', {
|
|
748
|
-
address,
|
|
749
|
-
network,
|
|
750
|
-
error: error instanceof Error ? error.message : String(error)
|
|
751
|
-
});
|
|
752
|
-
return false;
|
|
753
|
-
}
|
|
754
|
-
};
|
|
755
|
-
|
|
756
38
|
/**
|
|
757
39
|
* Parses the derivation path string into a Uint32Array
|
|
758
40
|
* Handles empty strings and empty array strings by returning an empty Uint32Array
|
|
@@ -853,7 +135,7 @@ class DynamicBtcWalletClient extends browser.DynamicWalletClient {
|
|
|
853
135
|
password,
|
|
854
136
|
signedSessionId
|
|
855
137
|
});
|
|
856
|
-
const publicKeyHex =
|
|
138
|
+
const publicKeyHex = btcUtils.extractPublicKeyHex(rawPublicKey);
|
|
857
139
|
return {
|
|
858
140
|
accountAddress,
|
|
859
141
|
publicKeyHex,
|
|
@@ -867,22 +149,6 @@ class DynamicBtcWalletClient extends browser.DynamicWalletClient {
|
|
|
867
149
|
}
|
|
868
150
|
}
|
|
869
151
|
/**
|
|
870
|
-
* Automatically determines the chain config based on address type
|
|
871
|
-
* @param rawPublicKey - The raw public key from the server
|
|
872
|
-
* @returns The public key hex
|
|
873
|
-
*/ extractPublicKeyHex(rawPublicKey) {
|
|
874
|
-
if (rawPublicKey instanceof Uint8Array) {
|
|
875
|
-
return Buffer.from(rawPublicKey).toString('hex');
|
|
876
|
-
}
|
|
877
|
-
if (rawPublicKey && typeof rawPublicKey === 'object' && typeof rawPublicKey.pubKeyAsHex === 'function') {
|
|
878
|
-
return rawPublicKey.pubKeyAsHex();
|
|
879
|
-
}
|
|
880
|
-
if (typeof rawPublicKey === 'string') {
|
|
881
|
-
return rawPublicKey;
|
|
882
|
-
}
|
|
883
|
-
throw new Error('Invalid public key format');
|
|
884
|
-
}
|
|
885
|
-
/**
|
|
886
152
|
* Derives the Bitcoin account address
|
|
887
153
|
* - BIP340 keys (32 bytes x-only): Only for Taproot addresses
|
|
888
154
|
* - ECDSA keys (33/65 bytes): For all other address types (Legacy, SegWit, Native SegWit)
|
|
@@ -893,8 +159,8 @@ class DynamicBtcWalletClient extends browser.DynamicWalletClient {
|
|
|
893
159
|
* @returns The account address
|
|
894
160
|
*/ deriveAccountAddress({ rawPublicKey, addressType, network }) {
|
|
895
161
|
// Derive address based on the chosen address type and network
|
|
896
|
-
const normalizedKey = normalizePublicKey(rawPublicKey, addressType);
|
|
897
|
-
const accountAddress = publicKeyToBitcoinAddress(normalizedKey, addressType, network);
|
|
162
|
+
const normalizedKey = btcUtils.normalizePublicKey(rawPublicKey, addressType);
|
|
163
|
+
const accountAddress = btcUtils.publicKeyToBitcoinAddress(normalizedKey, addressType, network, ecc);
|
|
898
164
|
return {
|
|
899
165
|
accountAddress
|
|
900
166
|
};
|
|
@@ -931,7 +197,7 @@ class DynamicBtcWalletClient extends browser.DynamicWalletClient {
|
|
|
931
197
|
}
|
|
932
198
|
let addressType = walletProperties.addressType;
|
|
933
199
|
if (!addressType) {
|
|
934
|
-
addressType = getAddressTypeFromDerivationPath(derivationPath);
|
|
200
|
+
addressType = btcUtils.getAddressTypeFromDerivationPath(derivationPath);
|
|
935
201
|
}
|
|
936
202
|
const clientKeyShares = await this.getClientKeySharesFromStorage({
|
|
937
203
|
accountAddress
|
|
@@ -953,14 +219,14 @@ class DynamicBtcWalletClient extends browser.DynamicWalletClient {
|
|
|
953
219
|
if (!derivedPublicKey) {
|
|
954
220
|
throw new Error('Failed to derive public key');
|
|
955
221
|
}
|
|
956
|
-
const pubKey = normalizePublicKey(derivedPublicKey, addressType);
|
|
222
|
+
const pubKey = btcUtils.normalizePublicKey(derivedPublicKey, addressType);
|
|
957
223
|
this.verifyWalletAddress(derivedPublicKey, addressType, network, accountAddress);
|
|
958
224
|
// Prepare BIP-322 Transactions and calculate hash
|
|
959
|
-
const { formattedMessage, toSignPsbt } = calculateBip322Hash(message, pubKey, addressType, network);
|
|
225
|
+
const { formattedMessage, toSignPsbt } = btcUtils.calculateBip322Hash(message, pubKey, addressType, network, ecc);
|
|
960
226
|
// Prepare tweak for Taproot in case of BIP340
|
|
961
227
|
let tweak;
|
|
962
228
|
if (addressType === browser.BitcoinAddressType.TAPROOT) {
|
|
963
|
-
tweak = calculateTaprootTweak(pubKey);
|
|
229
|
+
tweak = btcUtils.calculateTaprootTweak(pubKey);
|
|
964
230
|
}
|
|
965
231
|
// Build complete bitcoinConfig with addressType and tweak
|
|
966
232
|
const completeBitcoinConfig = _extends({}, bitcoinConfig, {
|
|
@@ -983,7 +249,7 @@ class DynamicBtcWalletClient extends browser.DynamicWalletClient {
|
|
|
983
249
|
onError
|
|
984
250
|
});
|
|
985
251
|
// Encode Final Result
|
|
986
|
-
const bip322Signature = encodeBip322Signature(toSignPsbt, pubKey, signature, addressType);
|
|
252
|
+
const bip322Signature = btcUtils.encodeBip322Signature(toSignPsbt, pubKey, signature, addressType);
|
|
987
253
|
this.logger.debug('[BTC Client] signMessage - bip322Signature', {
|
|
988
254
|
bip322Signature
|
|
989
255
|
});
|
|
@@ -1002,8 +268,8 @@ class DynamicBtcWalletClient extends browser.DynamicWalletClient {
|
|
|
1002
268
|
* @param network - The network
|
|
1003
269
|
* @param expectedAddress - The expected address
|
|
1004
270
|
*/ verifyWalletAddress(rawPublicKey, addressType, network, expectedAddress) {
|
|
1005
|
-
const normalizedKey = normalizePublicKey(rawPublicKey, addressType);
|
|
1006
|
-
const derivedAddress = publicKeyToBitcoinAddress(normalizedKey, addressType, network);
|
|
271
|
+
const normalizedKey = btcUtils.normalizePublicKey(rawPublicKey, addressType);
|
|
272
|
+
const derivedAddress = btcUtils.publicKeyToBitcoinAddress(normalizedKey, addressType, network, ecc);
|
|
1007
273
|
if (derivedAddress !== expectedAddress) {
|
|
1008
274
|
throw new Error(`Address verification failed: expected ${expectedAddress}, got ${derivedAddress}`);
|
|
1009
275
|
}
|
|
@@ -1035,7 +301,7 @@ class DynamicBtcWalletClient extends browser.DynamicWalletClient {
|
|
|
1035
301
|
let addressType = walletProperties.addressType;
|
|
1036
302
|
if (!addressType && walletProperties.derivationPath) {
|
|
1037
303
|
try {
|
|
1038
|
-
addressType = getAddressTypeFromDerivationPath(walletProperties.derivationPath);
|
|
304
|
+
addressType = btcUtils.getAddressTypeFromDerivationPath(walletProperties.derivationPath);
|
|
1039
305
|
} catch (e) {
|
|
1040
306
|
this.logger.warn('Failed to infer address type from derivation path', e);
|
|
1041
307
|
}
|
|
@@ -1086,9 +352,9 @@ class DynamicBtcWalletClient extends browser.DynamicWalletClient {
|
|
|
1086
352
|
const ceremonyCompletePromise = new Promise((resolve)=>{
|
|
1087
353
|
ceremonyCeremonyCompleteResolver = resolve;
|
|
1088
354
|
});
|
|
1089
|
-
const formattedPrivateKey = wifToPrivateKey(privateKey, network);
|
|
355
|
+
const formattedPrivateKey = btcUtils.wifToPrivateKey(privateKey, network);
|
|
1090
356
|
if (publicAddressCheck) {
|
|
1091
|
-
const derivedPublicKey = await getPublicKeyFromPrivateKey(privateKey, addressTypeEnum, network);
|
|
357
|
+
const derivedPublicKey = await btcUtils.getPublicKeyFromPrivateKey(privateKey, addressTypeEnum, ecc, network);
|
|
1092
358
|
const { accountAddress } = this.deriveAccountAddress({
|
|
1093
359
|
rawPublicKey: derivedPublicKey,
|
|
1094
360
|
addressType: addressTypeEnum,
|
|
@@ -1155,7 +421,7 @@ class DynamicBtcWalletClient extends browser.DynamicWalletClient {
|
|
|
1155
421
|
password,
|
|
1156
422
|
signedSessionId
|
|
1157
423
|
});
|
|
1158
|
-
const publicKeyHex =
|
|
424
|
+
const publicKeyHex = btcUtils.extractPublicKeyHex(rawPublicKey);
|
|
1159
425
|
return {
|
|
1160
426
|
accountAddress,
|
|
1161
427
|
publicKeyHex,
|
|
@@ -1175,7 +441,7 @@ class DynamicBtcWalletClient extends browser.DynamicWalletClient {
|
|
|
1175
441
|
* @returns The Bitcoin WIF format
|
|
1176
442
|
*/ convertPrivateKeyToBitcoinFormat(privateKey, network) {
|
|
1177
443
|
try {
|
|
1178
|
-
const wif = privateKeyToWIF(privateKey, network, {
|
|
444
|
+
const wif = btcUtils.privateKeyToWIF(privateKey, network, {
|
|
1179
445
|
compressed: true,
|
|
1180
446
|
onWarning: (message, data)=>{
|
|
1181
447
|
this.logger.warn(`[BTC Client] convertPrivateKeyToBitcoinFormat - ${message}`, data);
|
|
@@ -1230,7 +496,7 @@ class DynamicBtcWalletClient extends browser.DynamicWalletClient {
|
|
|
1230
496
|
}
|
|
1231
497
|
let addressType = walletProperties.addressType;
|
|
1232
498
|
if (!addressType) {
|
|
1233
|
-
addressType = getAddressTypeFromDerivationPath(derivationPath);
|
|
499
|
+
addressType = btcUtils.getAddressTypeFromDerivationPath(derivationPath);
|
|
1234
500
|
}
|
|
1235
501
|
const bitcoinConfig = {
|
|
1236
502
|
addressType,
|
|
@@ -1253,13 +519,13 @@ class DynamicBtcWalletClient extends browser.DynamicWalletClient {
|
|
|
1253
519
|
if (!derivedPublicKey) {
|
|
1254
520
|
throw new Error('Failed to derive public key');
|
|
1255
521
|
}
|
|
1256
|
-
const pubKey = normalizePublicKey(derivedPublicKey, addressType);
|
|
522
|
+
const pubKey = btcUtils.normalizePublicKey(derivedPublicKey, addressType);
|
|
1257
523
|
const tx = psbt.__CACHE.__TX;
|
|
1258
524
|
// Filter inputs to only sign those that belong to the current address
|
|
1259
525
|
const inputsToSign = psbt.data.inputs.map((input, i)=>({
|
|
1260
526
|
input,
|
|
1261
527
|
index: i
|
|
1262
|
-
})).filter(({ input })=>doesInputBelongToAddress(input, senderAddress, network));
|
|
528
|
+
})).filter(({ input })=>btcUtils.doesInputBelongToAddress(input, senderAddress, network));
|
|
1263
529
|
this.logger.debug('[BTC Client] signTransaction - Filtering inputs', {
|
|
1264
530
|
totalInputs: psbt.data.inputs.length,
|
|
1265
531
|
inputsToSign: inputsToSign.length,
|
|
@@ -1272,12 +538,12 @@ class DynamicBtcWalletClient extends browser.DynamicWalletClient {
|
|
|
1272
538
|
});
|
|
1273
539
|
}
|
|
1274
540
|
if (addressType === browser.BitcoinAddressType.TAPROOT) {
|
|
1275
|
-
const tweak = calculateTaprootTweak(pubKey);
|
|
541
|
+
const tweak = btcUtils.calculateTaprootTweak(pubKey);
|
|
1276
542
|
const completeBitcoinConfig = _extends({}, bitcoinConfig, {
|
|
1277
543
|
addressType: addressType,
|
|
1278
544
|
tweak
|
|
1279
545
|
});
|
|
1280
|
-
const { prevOutScripts, values } = collectPSBTInputData(psbt);
|
|
546
|
+
const { prevOutScripts, values } = btcUtils.collectPSBTInputData(psbt);
|
|
1281
547
|
await Promise.all(inputsToSign.map(async ({ input, index: i })=>{
|
|
1282
548
|
if (!input.witnessUtxo) {
|
|
1283
549
|
throw new Error(`Input ${i} missing witnessUtxo`);
|
|
@@ -1295,7 +561,7 @@ class DynamicBtcWalletClient extends browser.DynamicWalletClient {
|
|
|
1295
561
|
bitcoinConfig: completeBitcoinConfig,
|
|
1296
562
|
onError
|
|
1297
563
|
});
|
|
1298
|
-
const sigBuffer = convertSignatureToTaprootBuffer(signature);
|
|
564
|
+
const sigBuffer = btcUtils.convertSignatureToTaprootBuffer(signature);
|
|
1299
565
|
psbt.updateInput(i, {
|
|
1300
566
|
tapKeySig: sigBuffer
|
|
1301
567
|
});
|
|
@@ -1314,7 +580,7 @@ class DynamicBtcWalletClient extends browser.DynamicWalletClient {
|
|
|
1314
580
|
const { script, value } = input.witnessUtxo;
|
|
1315
581
|
const p2pkh = bitcoin__namespace.payments.p2pkh({
|
|
1316
582
|
hash: script.slice(2),
|
|
1317
|
-
network: getBitcoinNetwork(network)
|
|
583
|
+
network: btcUtils.getBitcoinNetwork(network)
|
|
1318
584
|
});
|
|
1319
585
|
const scriptCode = p2pkh.output;
|
|
1320
586
|
if (!scriptCode) throw new Error('Failed to generate scriptCode');
|
|
@@ -1331,7 +597,7 @@ class DynamicBtcWalletClient extends browser.DynamicWalletClient {
|
|
|
1331
597
|
bitcoinConfig: completeBitcoinConfig,
|
|
1332
598
|
onError
|
|
1333
599
|
});
|
|
1334
|
-
const derSignature = convertSignatureToDER(signature);
|
|
600
|
+
const derSignature = btcUtils.convertSignatureToDER(signature);
|
|
1335
601
|
psbt.updateInput(i, {
|
|
1336
602
|
partialSig: [
|
|
1337
603
|
{
|
|
@@ -1381,7 +647,7 @@ class DynamicBtcWalletClient extends browser.DynamicWalletClient {
|
|
|
1381
647
|
}
|
|
1382
648
|
let addressType = walletProperties.addressType;
|
|
1383
649
|
if (!addressType && walletProperties.derivationPath) {
|
|
1384
|
-
addressType = getAddressTypeFromDerivationPath(walletProperties.derivationPath);
|
|
650
|
+
addressType = btcUtils.getAddressTypeFromDerivationPath(walletProperties.derivationPath);
|
|
1385
651
|
}
|
|
1386
652
|
if (!addressType) {
|
|
1387
653
|
throw new Error('Address type not determined');
|
|
@@ -1391,17 +657,17 @@ class DynamicBtcWalletClient extends browser.DynamicWalletClient {
|
|
|
1391
657
|
}
|
|
1392
658
|
const bitcoinNetwork = network === 'testnet' ? bitcoin__namespace.networks.testnet : bitcoin__namespace.networks.bitcoin;
|
|
1393
659
|
// Fetch fee rates
|
|
1394
|
-
const feeRates = await getFeeRates({
|
|
660
|
+
const feeRates = await btcUtils.getFeeRates({
|
|
1395
661
|
network
|
|
1396
662
|
});
|
|
1397
663
|
const feeRate = feeRates[feeRateLevel];
|
|
1398
664
|
// Fetch UTXOs
|
|
1399
|
-
const utxos = await getUTXOs({
|
|
665
|
+
const utxos = await btcUtils.getUTXOs({
|
|
1400
666
|
address: senderAddress,
|
|
1401
667
|
network
|
|
1402
668
|
});
|
|
1403
669
|
// Select UTXOs
|
|
1404
|
-
const { inputs, change, fee } = selectUTXOs({
|
|
670
|
+
const { inputs, change, fee } = btcUtils.selectUTXOs({
|
|
1405
671
|
utxos,
|
|
1406
672
|
amount: BigInt(amount),
|
|
1407
673
|
feeRate
|
|
@@ -1459,7 +725,7 @@ class DynamicBtcWalletClient extends browser.DynamicWalletClient {
|
|
|
1459
725
|
* @param network - The network
|
|
1460
726
|
* @returns The transaction hex
|
|
1461
727
|
*/ async getTxHex(txid, network) {
|
|
1462
|
-
const baseUrl = getDefaultRpcUrl(network);
|
|
728
|
+
const baseUrl = btcUtils.getDefaultRpcUrl(network);
|
|
1463
729
|
const response = await fetch(`${baseUrl}/tx/${txid}/hex`);
|
|
1464
730
|
if (!response.ok) {
|
|
1465
731
|
throw new Error(`Failed to fetch TX hex: ${response.statusText}`);
|
|
@@ -1490,6 +756,8 @@ class DynamicBtcWalletClient extends browser.DynamicWalletClient {
|
|
|
1490
756
|
sdkVersion,
|
|
1491
757
|
forwardMPCClient
|
|
1492
758
|
}, internalOptions), this.chainName = 'BTC';
|
|
759
|
+
// Initialize ECC library for bitcoinjs-lib
|
|
760
|
+
btcUtils.initEccLib(ecc);
|
|
1493
761
|
}
|
|
1494
762
|
}
|
|
1495
763
|
|