@caravan/psbt 1.0.0
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/.eslintrc.cjs +6 -0
- package/.prettierrc +4 -0
- package/.turbo/turbo-build.log +20 -0
- package/.turbo/turbo-ci.log +258 -0
- package/.turbo/turbo-lint.log +49 -0
- package/.turbo/turbo-test$colon$watch.log +223 -0
- package/.turbo/turbo-test.log +196 -0
- package/CHANGELOG.md +10 -0
- package/README.md +197 -0
- package/dist/index.d.mts +150 -0
- package/dist/index.d.ts +150 -0
- package/dist/index.js +949 -0
- package/dist/index.mjs +924 -0
- package/jest.config.js +4 -0
- package/package.json +49 -0
- package/src/index.ts +2 -0
- package/src/psbt.test.ts +338 -0
- package/src/psbt.ts +440 -0
- package/src/psbtv2.test.ts +1241 -0
- package/src/psbtv2.ts +1352 -0
- package/tsconfig.json +3 -0
- package/tsup.config.ts +6 -0
package/src/psbt.ts
ADDED
|
@@ -0,0 +1,440 @@
|
|
|
1
|
+
import { Psbt, Transaction } from "bitcoinjs-lib";
|
|
2
|
+
import { reverseBuffer } from "bitcoinjs-lib/src/bufferutils.js";
|
|
3
|
+
import { toHexString } from "@caravan/bitcoin";
|
|
4
|
+
import {
|
|
5
|
+
generateMultisigFromHex,
|
|
6
|
+
multisigAddressType,
|
|
7
|
+
multisigBraidDetails,
|
|
8
|
+
multisigRedeemScript,
|
|
9
|
+
multisigWitnessScript,
|
|
10
|
+
bip32PathToSequence,
|
|
11
|
+
P2SH,
|
|
12
|
+
P2WSH,
|
|
13
|
+
P2SH_P2WSH,
|
|
14
|
+
generateBip32DerivationByIndex,
|
|
15
|
+
generateBraid,
|
|
16
|
+
networkData
|
|
17
|
+
} from "@caravan/bitcoin";
|
|
18
|
+
import BigNumber from "bignumber.js";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* This module provides functions for interacting with PSBTs, see BIP174
|
|
22
|
+
* https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Represents a transaction PSBT input.
|
|
27
|
+
*
|
|
28
|
+
* The [`Multisig`]{@link module:multisig.MULTISIG} object represents
|
|
29
|
+
* the address the corresponding UTXO belongs to.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Represents an output in a PSBT transaction.
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
export const PSBT_MAGIC_HEX = "70736274ff";
|
|
37
|
+
export const PSBT_MAGIC_B64 = "cHNidP8";
|
|
38
|
+
export const PSBT_MAGIC_BYTES = Buffer.from([0x70, 0x73, 0x62, 0x74, 0xff]);
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Given a string, try to create a Psbt object based on MAGIC (hex or Base64)
|
|
42
|
+
*/
|
|
43
|
+
export function autoLoadPSBT(psbtFromFile, options?: any) {
|
|
44
|
+
if (typeof psbtFromFile !== "string") return null;
|
|
45
|
+
// Auto-detect and decode Base64 and Hex.
|
|
46
|
+
if (psbtFromFile.substring(0, 10) === PSBT_MAGIC_HEX) {
|
|
47
|
+
return Psbt.fromHex(psbtFromFile, options);
|
|
48
|
+
} else if (psbtFromFile.substring(0, 7) === PSBT_MAGIC_B64) {
|
|
49
|
+
return Psbt.fromBase64(psbtFromFile, options);
|
|
50
|
+
} else {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Return the getBip32Derivation (if known) for a given `Multisig` object.
|
|
57
|
+
*/
|
|
58
|
+
function getBip32Derivation(multisig, index = 0) {
|
|
59
|
+
// Already have one, return it
|
|
60
|
+
if (multisig.bip32Derivation) {
|
|
61
|
+
return multisig.bip32Derivation;
|
|
62
|
+
}
|
|
63
|
+
// Otherwise generate it
|
|
64
|
+
const config = JSON.parse(multisigBraidDetails(multisig));
|
|
65
|
+
const braid = generateBraid(
|
|
66
|
+
config.network,
|
|
67
|
+
config.addressType,
|
|
68
|
+
config.extendedPublicKeys,
|
|
69
|
+
config.requiredSigners,
|
|
70
|
+
config.index
|
|
71
|
+
);
|
|
72
|
+
return generateBip32DerivationByIndex(braid, index);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Grabs appropriate bip32Derivation based on the input's last index
|
|
77
|
+
*/
|
|
78
|
+
function psbtInputDerivation(input) {
|
|
79
|
+
// Multi-address inputs will have different bip32Derivations per address (index/path),
|
|
80
|
+
// so specify the index ... If the input is missing a path, assume you want index = 0.
|
|
81
|
+
const index = input.bip32Path
|
|
82
|
+
? bip32PathToSequence(input.bip32Path).slice(-1)[0]
|
|
83
|
+
: 0;
|
|
84
|
+
return getBip32Derivation(input.multisig, index);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Grabs appropriate bip32Derivation for a change output
|
|
89
|
+
*/
|
|
90
|
+
function psbtOutputDerivation(output) {
|
|
91
|
+
return getBip32Derivation(output.multisig);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Gets the Witness script from the ouput that generated the input
|
|
96
|
+
*/
|
|
97
|
+
function getWitnessOutputScriptFromInput(input) {
|
|
98
|
+
// We have the transactionHex - use bitcoinjs to pluck out the witness script
|
|
99
|
+
// return format is:
|
|
100
|
+
// {
|
|
101
|
+
// script: Buffer.from(out.script, 'hex'),
|
|
102
|
+
// amount: out.value,
|
|
103
|
+
// }
|
|
104
|
+
// See https://github.com/bitcoinjs/bitcoinjs-lib/issues/1282
|
|
105
|
+
const tx = Transaction.fromHex(input.transactionHex);
|
|
106
|
+
return tx.outs[input.index];
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Return the locking script for the given `Multisig` object in a PSBT consumable format
|
|
111
|
+
*/
|
|
112
|
+
function psbtMultisigLock(multisig) {
|
|
113
|
+
const multisigLock: any = {};
|
|
114
|
+
|
|
115
|
+
// eslint-disable-next-line default-case
|
|
116
|
+
switch (multisigAddressType(multisig)) {
|
|
117
|
+
case P2SH:
|
|
118
|
+
multisigLock.redeemScript = multisigRedeemScript(multisig).output;
|
|
119
|
+
break;
|
|
120
|
+
case P2WSH:
|
|
121
|
+
multisigLock.witnessScript = multisigWitnessScript(multisig).output;
|
|
122
|
+
break;
|
|
123
|
+
case P2SH_P2WSH: // need both
|
|
124
|
+
multisigLock.witnessScript = multisigWitnessScript(multisig).output;
|
|
125
|
+
multisigLock.redeemScript = multisigRedeemScript(multisig).output;
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return multisigLock;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Take a MultisigTransactionInput and turn it into a MultisigTransactionPSBTInput
|
|
134
|
+
*/
|
|
135
|
+
export function psbtInputFormatter(input) {
|
|
136
|
+
// In this function we're decorating the MultisigTransactionInput appropriately based
|
|
137
|
+
// on its address type.
|
|
138
|
+
//
|
|
139
|
+
// Essentially we need to define a couple parameters to make the whole thing work.
|
|
140
|
+
// 1) Either a Witness UTXO or Non-Witness UTXO pointing to where this input originated
|
|
141
|
+
// 2) multisigScript (spending lock) which can be either a redeemScript, a witnessScript, or both.
|
|
142
|
+
//
|
|
143
|
+
// For more info see https://github.com/bitcoinjs/bitcoinjs-lib/blob/v5.1.10/test/integration/transactions.spec.ts#L680
|
|
144
|
+
|
|
145
|
+
// For SegWit inputs, you need an object with the output script buffer and output value
|
|
146
|
+
const witnessUtxo = getWitnessOutputScriptFromInput(input);
|
|
147
|
+
// For non-SegWit inputs, you must pass the full transaction buffer
|
|
148
|
+
const nonWitnessUtxo = Buffer.from(input.transactionHex, "hex");
|
|
149
|
+
|
|
150
|
+
// FIXME - this makes the assumption that the funding transaction used the same transaction type as the current input
|
|
151
|
+
// we dont have isSegWit info on our inputs at the moment, so we don't know for sure.
|
|
152
|
+
// This assumption holds in our fixtures, but it may need to be remedied in the future.
|
|
153
|
+
const isSegWit = multisigWitnessScript(input.multisig) !== null;
|
|
154
|
+
const utxoToVerify = isSegWit ? { witnessUtxo } : { nonWitnessUtxo };
|
|
155
|
+
const multisigScripts = psbtMultisigLock(input.multisig);
|
|
156
|
+
|
|
157
|
+
const bip32Derivation = psbtInputDerivation(input);
|
|
158
|
+
return {
|
|
159
|
+
hash: input.txid,
|
|
160
|
+
index: input.index,
|
|
161
|
+
...utxoToVerify,
|
|
162
|
+
...multisigScripts,
|
|
163
|
+
bip32Derivation,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Take a MultisigTransactionOutput and turn it into a MultisigTransactionPSBTOutput
|
|
169
|
+
*/
|
|
170
|
+
export function psbtOutputFormatter(output) {
|
|
171
|
+
let multisigScripts = {};
|
|
172
|
+
let bip32Derivation = [];
|
|
173
|
+
|
|
174
|
+
if (output.multisig) {
|
|
175
|
+
// This indicates that this output is a *change* output, so we include additional information:
|
|
176
|
+
// Change address bip32Derivation (rootFingerprints && pubkeys && bip32paths)
|
|
177
|
+
// Change address multisig locking script (redeem || witness || both)
|
|
178
|
+
// With the above information, the device (e.g. Coldcard) can validate that the change address
|
|
179
|
+
// can be signed with the same device. The display will show the output as "Change" instead of
|
|
180
|
+
// a normal external output.
|
|
181
|
+
multisigScripts = psbtMultisigLock(output.multisig);
|
|
182
|
+
bip32Derivation = psbtOutputDerivation(output);
|
|
183
|
+
return {
|
|
184
|
+
address: output.address,
|
|
185
|
+
value: new BigNumber(output.amountSats).toNumber(),
|
|
186
|
+
...multisigScripts,
|
|
187
|
+
bip32Derivation,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return {
|
|
192
|
+
address: output.address,
|
|
193
|
+
value: new BigNumber(output.amountSats).toNumber(),
|
|
194
|
+
...output, // the output may have come in already decorated with bip32Derivation/multisigScripts
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Create @caravan/wallets style transaction input objects from a PSBT
|
|
200
|
+
*/
|
|
201
|
+
function getUnchainedInputsFromPSBT(network, addressType, psbt) {
|
|
202
|
+
return psbt.txInputs.map((input, index) => {
|
|
203
|
+
const dataInput = psbt.data.inputs[index];
|
|
204
|
+
|
|
205
|
+
// FIXME - this is where we're currently only handling P2SH correctly
|
|
206
|
+
const fundingTxHex = dataInput.nonWitnessUtxo.toString("hex");
|
|
207
|
+
const fundingTx = Transaction.fromHex(fundingTxHex);
|
|
208
|
+
const multisig = generateMultisigFromHex(
|
|
209
|
+
network,
|
|
210
|
+
addressType,
|
|
211
|
+
dataInput.redeemScript.toString("hex")
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
return {
|
|
215
|
+
amountSats: fundingTx.outs[input.index].value,
|
|
216
|
+
index: input.index,
|
|
217
|
+
transactionHex: fundingTxHex,
|
|
218
|
+
txid: reverseBuffer(input.hash).toString("hex"),
|
|
219
|
+
multisig,
|
|
220
|
+
};
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Create @caravan/wallets style transaction output objects from a PSBT
|
|
226
|
+
*/
|
|
227
|
+
function getUnchainedOutputsFromPSBT(psbt) {
|
|
228
|
+
return psbt.txOutputs.map((output) => ({
|
|
229
|
+
address: output.address,
|
|
230
|
+
amountSats: output.value,
|
|
231
|
+
}));
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Create @caravan/wallets style transaction input objects
|
|
236
|
+
*
|
|
237
|
+
* @param {Object} psbt - Psbt bitcoinjs-lib object
|
|
238
|
+
* @param {Object} signingKeyDetails - Object containing signing key details (Fingerprint + bip32path prefix)
|
|
239
|
+
* @return {Object[]} bip32Derivations - array of signing bip32Derivation objects
|
|
240
|
+
*/
|
|
241
|
+
function filterRelevantBip32Derivations(psbt, signingKeyDetails) {
|
|
242
|
+
return psbt.data.inputs.map((input) => {
|
|
243
|
+
const bip32Derivation = input.bip32Derivation.filter(
|
|
244
|
+
(b32d) =>
|
|
245
|
+
b32d.path.startsWith(signingKeyDetails.path) &&
|
|
246
|
+
b32d.masterFingerprint.toString("hex") === signingKeyDetails.xfp
|
|
247
|
+
);
|
|
248
|
+
|
|
249
|
+
if (!bip32Derivation.length) {
|
|
250
|
+
throw new Error("Signing key details not included in PSBT");
|
|
251
|
+
}
|
|
252
|
+
return bip32Derivation[0];
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Translates a PSBT into inputs/outputs consumable by supported non-PSBT devices in the
|
|
258
|
+
* `@caravan/wallets` library.
|
|
259
|
+
*
|
|
260
|
+
* FIXME - Have only confirmed this is working for P2SH addresses on Ledger on regtest
|
|
261
|
+
*/
|
|
262
|
+
export function translatePSBT(network, addressType, psbt, signingKeyDetails) {
|
|
263
|
+
if (addressType !== P2SH) {
|
|
264
|
+
throw new Error(
|
|
265
|
+
"Unsupported addressType -- only P2SH is supported right now"
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
let localPSBT = autoLoadPSBT(psbt, { network: networkData(network) });
|
|
269
|
+
if (localPSBT === null) return null;
|
|
270
|
+
|
|
271
|
+
// The information we need to provide proper @caravan/wallets style objects to the supported
|
|
272
|
+
// non-PSBT devices, we need to grab info from different places from within the PSBT.
|
|
273
|
+
// 1. the "data inputs"
|
|
274
|
+
// 2. the "transaction inputs"
|
|
275
|
+
//
|
|
276
|
+
// We'll do that in the functions below.
|
|
277
|
+
|
|
278
|
+
// First, we check that we actually do have any inputs to sign:
|
|
279
|
+
const bip32Derivations = filterRelevantBip32Derivations(
|
|
280
|
+
localPSBT,
|
|
281
|
+
signingKeyDetails
|
|
282
|
+
);
|
|
283
|
+
|
|
284
|
+
// The shape of these return objects are specific to existing code
|
|
285
|
+
// in @caravan/wallets for signing with Trezor and Ledger devices.
|
|
286
|
+
const unchainedInputs = getUnchainedInputsFromPSBT(
|
|
287
|
+
network,
|
|
288
|
+
addressType,
|
|
289
|
+
localPSBT
|
|
290
|
+
);
|
|
291
|
+
const unchainedOutputs = getUnchainedOutputsFromPSBT(localPSBT);
|
|
292
|
+
|
|
293
|
+
return {
|
|
294
|
+
unchainedInputs,
|
|
295
|
+
unchainedOutputs,
|
|
296
|
+
bip32Derivations,
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Given a PSBT, an input index, a pubkey, and a signature,
|
|
302
|
+
* update the input inside the PSBT with a partial signature object.
|
|
303
|
+
*
|
|
304
|
+
* Make sure it validates, and then return the PSBT with the partial
|
|
305
|
+
* signature inside.
|
|
306
|
+
*/
|
|
307
|
+
function addSignatureToPSBT(psbt, inputIndex, pubkey, signature) {
|
|
308
|
+
const partialSig = [
|
|
309
|
+
{
|
|
310
|
+
pubkey,
|
|
311
|
+
signature,
|
|
312
|
+
},
|
|
313
|
+
];
|
|
314
|
+
psbt.data.updateInput(inputIndex, { partialSig });
|
|
315
|
+
if (!psbt.validateSignaturesOfInput(inputIndex, pubkey)) {
|
|
316
|
+
throw new Error("One or more invalid signatures.");
|
|
317
|
+
}
|
|
318
|
+
return psbt;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Given an unsigned PSBT, an array of signing public key(s) (one per input),
|
|
323
|
+
* an array of signature(s) (one per input) in the same order as the pubkey(s),
|
|
324
|
+
* adds partial signature object(s) to each input and returns the PSBT with
|
|
325
|
+
* partial signature(s) included.
|
|
326
|
+
*
|
|
327
|
+
* FIXME - maybe we add functionality of sending in a single pubkey as well,
|
|
328
|
+
* which would assume all of the signature(s) are for that pubkey.
|
|
329
|
+
*/
|
|
330
|
+
export function addSignaturesToPSBT(network, psbt, pubkeys, signatures) {
|
|
331
|
+
let psbtWithSignatures = autoLoadPSBT(psbt, {
|
|
332
|
+
network: networkData(network),
|
|
333
|
+
});
|
|
334
|
+
if (psbtWithSignatures === null) return null;
|
|
335
|
+
|
|
336
|
+
signatures.forEach((sig, idx) => {
|
|
337
|
+
const pubkey = pubkeys[idx];
|
|
338
|
+
psbtWithSignatures = addSignatureToPSBT(
|
|
339
|
+
psbtWithSignatures,
|
|
340
|
+
idx,
|
|
341
|
+
pubkey,
|
|
342
|
+
sig
|
|
343
|
+
);
|
|
344
|
+
});
|
|
345
|
+
return psbtWithSignatures.toBase64();
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Get number of signers in the PSBT
|
|
350
|
+
*/
|
|
351
|
+
|
|
352
|
+
function getNumSigners(psbt) {
|
|
353
|
+
const partialSignatures =
|
|
354
|
+
psbt && psbt.data && psbt.data.inputs && psbt.data.inputs[0]
|
|
355
|
+
? psbt.data.inputs[0].partialSig
|
|
356
|
+
: undefined;
|
|
357
|
+
return partialSignatures === undefined ? 0 : partialSignatures.length;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Extracts the signature(s) from a PSBT.
|
|
362
|
+
* NOTE: there should be one signature per input, per signer.
|
|
363
|
+
*
|
|
364
|
+
* ADDITIONAL NOTE: because of the restrictions we place on braids to march their
|
|
365
|
+
* multisig addresses (slices) forward at the *same* index across each chain of the
|
|
366
|
+
* braid, we do not run into a possible collision with this data structure.
|
|
367
|
+
* BUT - to have this method accommodate the *most* general form of signature parsing,
|
|
368
|
+
* it would be wise to wrap this one level deeper like:
|
|
369
|
+
*
|
|
370
|
+
* address: [pubkey : [signature(s)]]
|
|
371
|
+
*
|
|
372
|
+
* that way if your braid only advanced one chain's (member's) index so that a pubkey
|
|
373
|
+
* could be used in more than one address, everything would still function properly.
|
|
374
|
+
*/
|
|
375
|
+
export function parseSignaturesFromPSBT(psbtFromFile) {
|
|
376
|
+
let psbt = autoLoadPSBT(psbtFromFile, {});
|
|
377
|
+
if (psbt === null) {
|
|
378
|
+
return null;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
const numSigners = getNumSigners(psbt);
|
|
382
|
+
|
|
383
|
+
const signatureSet = {};
|
|
384
|
+
let pubKey = "";
|
|
385
|
+
const inputs = psbt.data.inputs;
|
|
386
|
+
// Find signatures in the PSBT
|
|
387
|
+
if (numSigners >= 1) {
|
|
388
|
+
// return array of arrays of signatures
|
|
389
|
+
for (let i = 0; i < inputs.length; i++) {
|
|
390
|
+
for (let j = 0; j < numSigners; j++) {
|
|
391
|
+
pubKey = toHexString(
|
|
392
|
+
Array.prototype.slice.call(inputs?.[i]?.partialSig?.[j].pubkey)
|
|
393
|
+
);
|
|
394
|
+
if (pubKey in signatureSet) {
|
|
395
|
+
signatureSet[pubKey].push(
|
|
396
|
+
inputs?.[i]?.partialSig?.[j].signature.toString("hex")
|
|
397
|
+
);
|
|
398
|
+
} else {
|
|
399
|
+
signatureSet[pubKey] = [
|
|
400
|
+
inputs?.[i]?.partialSig?.[j].signature.toString("hex"),
|
|
401
|
+
];
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
} else {
|
|
406
|
+
return null;
|
|
407
|
+
}
|
|
408
|
+
return signatureSet;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Extracts signatures in order of inputs and returns as array (or array of arrays if multiple signature sets)
|
|
413
|
+
*/
|
|
414
|
+
export function parseSignatureArrayFromPSBT(psbtFromFile) {
|
|
415
|
+
let psbt = autoLoadPSBT(psbtFromFile);
|
|
416
|
+
if (psbt === null) return null;
|
|
417
|
+
|
|
418
|
+
const numSigners = getNumSigners(psbt);
|
|
419
|
+
|
|
420
|
+
const signatureArrays: string[][] = Array.from(
|
|
421
|
+
{ length: numSigners },
|
|
422
|
+
() => []
|
|
423
|
+
);
|
|
424
|
+
|
|
425
|
+
const { inputs } = psbt.data;
|
|
426
|
+
|
|
427
|
+
if (numSigners >= 1) {
|
|
428
|
+
for (let i = 0; i < inputs.length; i += 1) {
|
|
429
|
+
for (let j = 0; j < numSigners; j += 1) {
|
|
430
|
+
let signature = inputs?.[i]?.partialSig?.[j].signature.toString("hex");
|
|
431
|
+
if (signature) {
|
|
432
|
+
signatureArrays[j].push(signature);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
} else {
|
|
437
|
+
return null;
|
|
438
|
+
}
|
|
439
|
+
return numSigners === 1 ? signatureArrays[0] : signatureArrays;
|
|
440
|
+
}
|