@caravan/psbt 1.1.0 → 1.3.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/README.md CHANGED
@@ -2,22 +2,15 @@
2
2
 
3
3
  A set of utilities for working with PSBTs.
4
4
 
5
- ## Table of contents
6
-
7
- - [PSBTv0](#psbtv0)
8
- - [Exports](#psbtv0-exports)
5
+ # Table of contents
6
+ - [Constants](#constants)
7
+ - [Exports](#exports)
9
8
  - [`const PSBT_MAGIC_HEX`](#const-psbt_magic_hex)
10
9
  - [`const PSBT_MAGIC_B64`](#const-psbt_magic_b64)
11
10
  - [`const PSBT_MAGIC_BYTES`](#const-psbt_magic_bytes)
12
- - [`function autoLoadPSBT`](#function-autoloadpsbt)
13
- - [`function psbtInputFormatter`](#function-psbtinputformatter)
14
- - [`function psbtOutputFormatter`](#function-psbtoutputformatter)
15
- - [`function translatePSBT`](#function-translatepsbt)
16
- - [`function addSignaturesToPSBT`](#function-addsignaturestopsbt)
17
- - [`function parseSignaturesFromPSBT`](#function-parsesignaturesfrompsbt)
18
- - [`function parseSignatureArrayFromPSBT`](#function-parsesignaturearrayfrompsbt)
11
+ - [PSBTv0](#psbtv0)
19
12
  - [PSBTv2](#psbtv2)
20
- - [Exports](#psbtv2-exports)
13
+ - [Exports](#exports-1)
21
14
  - [`class PsbtV2`](#class-psbtv2)
22
15
  - [`get isReadyForConstructor`](#get-isreadyforconstructor)
23
16
  - [`get isReadyForUpdater`](#get-isreadyforupdater)
@@ -34,16 +27,19 @@ A set of utilities for working with PSBTs.
34
27
  - [`public deleteOutput`](#public-deleteoutput)
35
28
  - [`public addPartialSig`](#public-addpartialsig)
36
29
  - [`public removePartialSig`](#public-removepartialsig)
30
+ - [`public setProprietaryValue`](#public-setproprietaryvalue)
37
31
  - [`static PsbtV2.FromV0`](#static-psbtv2fromv0)
38
32
  - [`function getPsbtVersionNumber`](#function-getpsbtversionnumber)
39
33
  - [Concepts](#concepts)
40
34
  - [The operator role saga](#the-operator-role-saga)
41
35
  - [TODO](#todo)
36
+ - [PsbtV2](#psbtv2-1)
37
+ - [Operator role validation](#operator-role-validation)
38
+ - [Class constructor](#class-constructor)
39
+ - [Add input timelocks](#add-input-timelocks)
40
+ - [Add input sighash\_single](#add-input-sighash_single)
42
41
 
43
- ## PSBTv0
44
-
45
- [BIP 174](https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki)
46
-
42
+ ## Constants
47
43
  ### Exports
48
44
 
49
45
  #### `const PSBT_MAGIC_HEX`
@@ -58,43 +54,13 @@ A utility constant for base64 encoded psbt magic bytes equal to `"cHNidP8"`.
58
54
 
59
55
  A utility constant for `Buffer` instance of psbt magic bytes.
60
56
 
61
- #### `function autoLoadPSBT`
62
-
63
- Given a string, try to create a Psbt object based on MAGIC (hex or Base64).
64
-
65
- #### `function psbtInputFormatter`
66
-
67
- Take a `MultisigTransactionInput` and turn it into a `MultisigTransactionPSBTInput`.
68
-
69
- #### `function psbtOutputFormatter`
70
-
71
- Take a `MultisigTransactionOutput` and turn it into a `MultisigTransactionPSBTOutput`.
72
57
 
73
- #### `function translatePSBT`
74
-
75
- Translates a PSBT into inputs/outputs consumable by supported non-PSBT devices in the `@caravan/wallets` library.
76
-
77
- #### `function addSignaturesToPSBT`
78
-
79
- Given an unsigned PSBT, an array of signing public key(s) (one per input), an array of signature(s) (one per input) in the same order as the pubkey(s), adds partial signature object(s) to each input and returns the PSBT with partial signature(s) included.
80
-
81
- #### `function parseSignaturesFromPSBT`
82
-
83
- Extracts the signature(s) from a PSBT.
84
-
85
- NOTE: there should be one signature per input, per signer.
86
-
87
- ADDITIONAL NOTE: because of the restrictions we place on braids to march their multisig addresses (slices) forward at the _same_ index across each chain of the braid, we do not run into a possible collision with this data structure. BUT - to have this method accommodate the _most_ general form of signature parsing, it would be wise to wrap this one level deeper like:
58
+ ## PSBTv0
88
59
 
89
- ```
90
- address: [pubkey : [signature(s)]]
91
- ```
60
+ [BIP 174](https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki)
92
61
 
93
- that way if your braid only advanced one chain's (member's) index so that a pubkey could be used in more than one address, everything would still function properly.
94
62
 
95
- #### `function parseSignatureArrayFromPSBT`
96
63
 
97
- Extracts signatures in order of inputs and returns as array (or array of arrays if multiple signature sets).
98
64
 
99
65
  ## PSBTv2
100
66
 
@@ -217,6 +183,20 @@ The Signer, when it creates a signature, must add the partial sig keypair to the
217
183
 
218
184
  Removes all sigs for an input unless a pubkey is specified. Validates that the input exists. When providing a pubkey, this validates that a sig for the pubkey exists.
219
185
 
186
+ ##### `public setProprietaryValue`
187
+
188
+ Sets values on the proprietary keytype for a global, input, or output map. BIP 174 allows for proprietary values to be set on all maps with the keytype `0xFC`. This method sets byte data to key values defined by the args.
189
+
190
+ Args:
191
+
192
+ - `mapSelector` selects which map to set the proprietary value. If this value is not `"global"`, then a tuple must be provided with `"inputs"` or `"outputs"` as the first element and the index `number` on the second element representing which input or output map to set the value to. An example looks like `["inputs", 0]`. If the map name doesn't match, the values will be set to the global map. If the index is missing on `"inputs"` or `"outputs"`, then it will throw.
193
+ - `identifier` should be the bytes identifier for the set of proprietary keytypes.
194
+ - `subkeyType` accepts bytes proprietary keytype.
195
+ - `subkeyData` accepts bytes proprietary keydata.
196
+ - `valueData` accepts bytes which will be written as the proprietary value.
197
+
198
+ From the provided args, a key with the following format will be generated: `0xFC<compact uint identifier length><bytes identifier><bytes subtype><bytes subkeydata>`
199
+
220
200
  ##### `static PsbtV2.FromV0`
221
201
 
222
202
  Attempts to return a `PsbtV2` by converting from a PSBTv0 string or Buffer
@@ -231,22 +211,80 @@ Attempts to extract the version number as uint32LE from raw psbt regardless of p
231
211
 
232
212
  The PSBT is a resource which may be passed between several operators or services. It's best to look at the operator roles as stages of a saga. The next valid operator role(s) can be determined by the state of the PSBT. The actions allowed for a PSBT are determined by which operator role the PSBT can be now and which role it could be next. See the following blog article at Unchained for a more detailed illustration: [Operator roles: Life stages in the saga of a PSBT](https://unchained.com/blog/operator-roles-life-stages-in-the-saga-of-a-psbt/)
233
213
 
234
- ### TODO
214
+ ## TODO
235
215
 
236
- #### PsbtV2
216
+ ### PsbtV2
237
217
 
238
- ##### Operator role validation
218
+ #### Operator role validation
239
219
 
240
220
  Work remains for determining readiness for operator roles Input Finalizer and Transaction Extractor. The getters responsible for these checks are `isReadyForInputFinalizer` and `isReadyForTransactionExtractor`. Work also remains to expand the PsbtV2 method functionality beyond the Signer role. A huge benefit might be gained from building methods aimed at the Combiner role.
241
221
 
242
- ##### Class constructor
222
+ #### Class constructor
243
223
 
244
224
  The constructor must be able to handle values which the Creator role is responsible for. Currently, the constructor can only accept an optional psbt which it parses to configure itself. It would be ideal if a fresh PsbtV2 instance could be initialized with minimal arguments for which the Creator role is responsible. See `private create()`.
245
225
 
246
- ##### Add input timelocks
226
+ #### Add input timelocks
247
227
 
248
228
  The `public addInput` must be able to properly handle input locktimes which interact with the global value.
249
229
 
250
- ##### Add input sighash_single
230
+ #### Add input sighash_single
251
231
 
252
232
  The `public addInput` must be able to properly handle new inputs when the psbt has a `SIGHASH_SINGLE` flag on `PSBT_GLOBAL_TX_MODIFIABLE`.
233
+
234
+ ## Troubleshooting and FAQ
235
+ ### What's with the vendor version of tiny-secp256k1?
236
+ In v6 of bitcoinjs-lib, which @caravan/psbt upgraded to use relative v5 in the older psbt code in @caravan/bitcoin,
237
+ some functions of the library require an elliptic curve library to be initialized w/ bitcoinjs-lib (see [this issue](https://github.com/bitcoinjs/bitcoinjs-lib/issues/1889#issuecomment-1443792692)), e.g. for taproot functionality.
238
+ For some reason, the recommended library `tiny-secp256k1` fails on initialization saying the library is invalid. The cause
239
+ seems to be a comparison of a Buffer with Uint8Array (see [this issue](https://github.com/bitcoinjs/tiny-secp256k1/issues/136) for more info).
240
+
241
+ A proposed fix is pending review and approval [here](https://github.com/bitcoinjs/tiny-secp256k1/pull/137). Unfortunately, since there
242
+ is a special build requirement to get the package code, the easiest way to get bitcoinjs initialized was to include patched vendor code in
243
+ the caravan codebase for now.
244
+
245
+ If a fork needs to be maintained and updated, to build and update the code, you can fork the repo, and run the docker build steps:
246
+
247
+ ```
248
+ % docker build -t tiny-secp256k1 .
249
+ % docker run -it --rm -v `pwd`:/tiny-secp256k1 -w /tiny-secp256k1 tiny-secp256k1
250
+ # make build
251
+ ```
252
+ Then copy the resulting built code (ends up in the lib directory) into the vendor/tiny-secp256k1. Currently
253
+ we just use the asmjs build to avoid wasm complications in
254
+ the build system.
255
+
256
+ ### Experimental VM Modules
257
+ This is related to the tiny-secp256k1 library which has difficulties importing in different environments.
258
+
259
+ Note that the jest configs and the special prefix in the npm test scripts were needed
260
+ to let jest understand the esmodule imports from the tiny-secp256k1 library.
261
+
262
+ The npm script prefix in particular may result in a console warning when running tests:
263
+
264
+ ```
265
+ ExperimentalWarning: VM Modules is an experimental feature and might change at any time
266
+ ```
267
+
268
+ This can be safely ignored. See the [documentation in jest](https://jestjs.io/docs/ecmascript-modules)
269
+ for more information on running with `--experimental-vm-modules`.
270
+
271
+
272
+ ### What's with the `bitcoinjs-lib-v6` dependency?
273
+
274
+ npm workspaces and maybe vite have issues with nested dependencies with
275
+ mismatched versions. `@caravan/psbt` requires v6 of bitcoinjs-lib but in order
276
+ to avoid making massive breaking changes, other libraries like `@caravan/bitcoin` are
277
+ still using bitcoinjs-lib v5. Unfortunately when being built altogether, it's possible
278
+ that the wrong version takes precedence, causing the build to break.
279
+
280
+ Because npm lacks a `nohoist` option for workspaces, the workaround is to use a kind
281
+ of alias in the package.json. So we add `"bitcoinjs-lib-v6": "npm:bitcoinjs-lib@^6.1.5",`
282
+ to say that we want to use v6.1.5 from npm whenever use the alias `bitcoinjs-lib-v6`
283
+ in imports in our code. This forces build systems to look for this reference and
284
+ the correct version of the package and avoid using a mismatch. Unfortunately
285
+ this was the only workaround that worked. `overrides` for example was not being
286
+ respected.
287
+
288
+ Learn more [here](https://github.com/vitejs/vite/issues/4245),
289
+ [here](https://github.com/zackerydev/noist?tab=readme-ov-file), and
290
+ [here](https://github.com/prisma/prisma/issues/9649).
package/dist/index.d.ts CHANGED
@@ -1,3 +1,7 @@
1
+ import { Network } from '@caravan/bitcoin';
2
+ import { Psbt } from 'bitcoinjs-lib-v6';
3
+ import { MultisigWalletConfig } from '@caravan/multisig';
4
+
1
5
  /**
2
6
  * Hex encoded string containing `<keytype><keydata>`. A string is needed for
3
7
  * Map.get() since it matches by identity. Most commonly, a `Key` only contains a
@@ -24,6 +28,8 @@ declare enum PsbtGlobalTxModifiableBits {
24
28
  OUTPUTS = "OUTPUTS",// 0b00000010
25
29
  SIGHASH_SINGLE = "SIGHASH_SINGLE"
26
30
  }
31
+ type InputOutputIndexType = number;
32
+ type MapSelectorType = "global" | ["inputs", InputOutputIndexType] | ["outputs", InputOutputIndexType];
27
33
 
28
34
  /**
29
35
  * Attempts to extract the version number as uint32LE from raw psbt regardless
@@ -279,6 +285,31 @@ declare class PsbtV2 extends PsbtV2Maps {
279
285
  * the pubkey exists.
280
286
  */
281
287
  removePartialSig(inputIndex: number, pubkey?: Buffer): void;
288
+ /**
289
+ * Sets values on the proprietary keytype for a global, input, or output map.
290
+ * BIP 174 allows for proprietary values to be set on all maps with the
291
+ * keytype `0xFC`. This method sets byte data to key values defined by the
292
+ * args.
293
+ *
294
+ * Args:
295
+ * - `mapSelector` selects which map to set the proprietary value. If this
296
+ * value is not `"global"`, then a tuple must be provided with `"inputs"` or
297
+ * `"outputs"` as the first element and the index `number` on the second
298
+ * element representing which input or output map to set the value to. An
299
+ * example looks like `["inputs", 0]`. If the map name doesn't match, the
300
+ * values will be set to the global map. If the index is missing on
301
+ * `"inputs"` or `"outputs"`, then it will throw.
302
+ * - `identifier` should be the bytes identifier for the set of proprietary
303
+ * keytypes.
304
+ * - `subkeyType` accepts bytes proprietary keytype.
305
+ * - `subkeyData` accepts bytes proprietary keydata.
306
+ * - `valueData` accepts bytes which will be written as the proprietary value.
307
+ *
308
+ * From the provided args, a key with the following format will be generated:
309
+ * `0xFC<compact uint identifier length><bytes identifier><bytes
310
+ * subtype><bytes subkeydata>`
311
+ */
312
+ setProprietaryValue(mapSelector: MapSelectorType, identifier: Buffer, subkeyType: Buffer, subkeyData: Buffer, valueData: Buffer): void;
282
313
  /**
283
314
  * Ensures the PSBT is in the proper state when adding a partial sig keypair.
284
315
  * https://github.com/bitcoin/bips/blob/master/bip-0370.mediawiki#signer
@@ -294,4 +325,103 @@ declare class PsbtV2 extends PsbtV2Maps {
294
325
  static FromV0(psbt: string | Buffer, allowTxnVersion1?: boolean): PsbtV2;
295
326
  }
296
327
 
297
- export { PsbtV2, getPsbtVersionNumber };
328
+ interface PsbtInput {
329
+ hash: string | Buffer;
330
+ index: number;
331
+ transactionHex: string;
332
+ redeemScript?: Buffer;
333
+ witnessScript?: Buffer;
334
+ bip32Derivation?: {
335
+ masterFingerprint: Buffer;
336
+ path: string;
337
+ pubkey: Buffer;
338
+ }[];
339
+ spendingWallet: MultisigWalletConfig;
340
+ }
341
+ interface PsbtOutput {
342
+ address: string;
343
+ value: number;
344
+ bip32Derivation?: {
345
+ masterFingerprint: Buffer;
346
+ path: string;
347
+ pubkey: Buffer;
348
+ }[];
349
+ redeemScript?: Buffer;
350
+ witnessScript?: Buffer;
351
+ }
352
+ /**
353
+ * This function seeks to be an updated version of the legacy `unsignedMultisigPSBT` function
354
+ * from @caravan/bitcoin.
355
+ * It takes the network and a set of inputs and outputs which it creates a PSBT from.
356
+ * This combines several operator roles of the PSBT saga into one function, getting a PSBT
357
+ * ready to be signed. It optionally can also add the global xpubs to the PSBT.
358
+ */
359
+ declare const getUnsignedMultisigPsbtV0: ({ network, inputs, outputs, includeGlobalXpubs, }: {
360
+ network: Network;
361
+ inputs: PsbtInput[];
362
+ outputs: PsbtOutput[];
363
+ includeGlobalXpubs?: boolean | undefined;
364
+ }) => Psbt;
365
+ declare const addGlobalXpubs: (psbt: Psbt, inputs: PsbtInput[], network: Network) => void;
366
+ /**
367
+ * Validate the signature on a psbt for a given input. Returns false if no
368
+ * valid signature is found otherwise returns the public key that was signed for.
369
+ *
370
+ * This is a port of the validateMultisigSignature function from @caravan/bitcoin
371
+ * to support a newer API and be more PSBT-native.
372
+ */
373
+ declare const validateMultisigPsbtSignature: (raw: string | Buffer, inputIndex: number, inputSignature: Buffer, inputAmount?: string) => boolean | string;
374
+
375
+ /**
376
+ * @file This file primarily contains utility functions migrated from the
377
+ * legacy psbt module in @caravan/bitcoin. With the new @caravan/psbt
378
+ * module, the goal is to make a more modular and legible API. But in order
379
+ * to make migrations easier from the old API, we need to provide conversion functions
380
+ * for converting the deeply nested objects in the legacy API.
381
+ */
382
+
383
+ interface LegacyMultisig {
384
+ /**
385
+ * JSON stringified object with the following properties:
386
+ * braidDetails: {
387
+ * network: Network;
388
+ * addressType: number;
389
+ * extendedPublicKeys: string[];
390
+ * requiredSigners: number;
391
+ * index: string;
392
+ * };
393
+ */
394
+ braidDetails: string;
395
+ bip32Derivation?: {
396
+ masterFingerprint: string;
397
+ path: string;
398
+ pubkey: Buffer;
399
+ }[];
400
+ }
401
+ interface LegacyInput {
402
+ txid: string;
403
+ index: number;
404
+ transactionHex: string;
405
+ amountSats: number | string;
406
+ multisig: LegacyMultisig;
407
+ }
408
+ interface LegacyOutput {
409
+ address: string;
410
+ amountSats: number | string;
411
+ bip32Derivation?: {
412
+ masterFingerprint: string;
413
+ path: string;
414
+ pubkey: Buffer;
415
+ }[];
416
+ witnessScript?: Buffer;
417
+ redeemScript?: Buffer;
418
+ multisig?: LegacyMultisig;
419
+ }
420
+ declare const convertLegacyInput: (input: LegacyInput) => PsbtInput;
421
+ declare const convertLegacyOutput: (output: LegacyOutput) => PsbtOutput;
422
+
423
+ declare const PSBT_MAGIC_HEX = "70736274ff";
424
+ declare const PSBT_MAGIC_B64 = "cHNidP8";
425
+ declare const PSBT_MAGIC_BYTES: Buffer;
426
+
427
+ export { LegacyInput, LegacyMultisig, LegacyOutput, PSBT_MAGIC_B64, PSBT_MAGIC_BYTES, PSBT_MAGIC_HEX, PsbtInput, PsbtOutput, PsbtV2, addGlobalXpubs, convertLegacyInput, convertLegacyOutput, getPsbtVersionNumber, getUnsignedMultisigPsbtV0, validateMultisigPsbtSignature };