@bsv/sdk 1.1.23 → 1.1.25

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.
Files changed (86) hide show
  1. package/dist/cjs/package.json +1 -1
  2. package/dist/cjs/src/primitives/Curve.js +7 -7
  3. package/dist/cjs/src/primitives/Curve.js.map +1 -1
  4. package/dist/cjs/src/primitives/ECDSA.js +394 -71
  5. package/dist/cjs/src/primitives/ECDSA.js.map +1 -1
  6. package/dist/cjs/src/primitives/Point.js +103 -23
  7. package/dist/cjs/src/primitives/Point.js.map +1 -1
  8. package/dist/cjs/src/primitives/TransactionSignature.js +4 -3
  9. package/dist/cjs/src/primitives/TransactionSignature.js.map +1 -1
  10. package/dist/cjs/src/primitives/utils.js +14 -15
  11. package/dist/cjs/src/primitives/utils.js.map +1 -1
  12. package/dist/cjs/src/script/Spend.js +4 -4
  13. package/dist/cjs/src/script/Spend.js.map +1 -1
  14. package/dist/cjs/src/totp/totp.js +1 -1
  15. package/dist/cjs/src/totp/totp.js.map +1 -1
  16. package/dist/cjs/src/transaction/Beef.js +492 -0
  17. package/dist/cjs/src/transaction/Beef.js.map +1 -0
  18. package/dist/cjs/src/transaction/BeefParty.js +97 -0
  19. package/dist/cjs/src/transaction/BeefParty.js.map +1 -0
  20. package/dist/cjs/src/transaction/BeefTx.js +123 -0
  21. package/dist/cjs/src/transaction/BeefTx.js.map +1 -0
  22. package/dist/cjs/src/transaction/Transaction.js +81 -66
  23. package/dist/cjs/src/transaction/Transaction.js.map +1 -1
  24. package/dist/cjs/src/transaction/index.js +7 -1
  25. package/dist/cjs/src/transaction/index.js.map +1 -1
  26. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  27. package/dist/esm/src/primitives/Curve.js +7 -7
  28. package/dist/esm/src/primitives/Curve.js.map +1 -1
  29. package/dist/esm/src/primitives/ECDSA.js +394 -71
  30. package/dist/esm/src/primitives/ECDSA.js.map +1 -1
  31. package/dist/esm/src/primitives/Point.js +103 -23
  32. package/dist/esm/src/primitives/Point.js.map +1 -1
  33. package/dist/esm/src/primitives/TransactionSignature.js +4 -3
  34. package/dist/esm/src/primitives/TransactionSignature.js.map +1 -1
  35. package/dist/esm/src/primitives/utils.js +14 -15
  36. package/dist/esm/src/primitives/utils.js.map +1 -1
  37. package/dist/esm/src/script/Spend.js +4 -4
  38. package/dist/esm/src/script/Spend.js.map +1 -1
  39. package/dist/esm/src/totp/totp.js +1 -1
  40. package/dist/esm/src/totp/totp.js.map +1 -1
  41. package/dist/esm/src/transaction/Beef.js +485 -0
  42. package/dist/esm/src/transaction/Beef.js.map +1 -0
  43. package/dist/esm/src/transaction/BeefParty.js +93 -0
  44. package/dist/esm/src/transaction/BeefParty.js.map +1 -0
  45. package/dist/esm/src/transaction/BeefTx.js +121 -0
  46. package/dist/esm/src/transaction/BeefTx.js.map +1 -0
  47. package/dist/esm/src/transaction/Transaction.js +81 -66
  48. package/dist/esm/src/transaction/Transaction.js.map +1 -1
  49. package/dist/esm/src/transaction/index.js +3 -0
  50. package/dist/esm/src/transaction/index.js.map +1 -1
  51. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  52. package/dist/types/src/primitives/ECDSA.d.ts.map +1 -1
  53. package/dist/types/src/primitives/Point.d.ts +5 -0
  54. package/dist/types/src/primitives/Point.d.ts.map +1 -1
  55. package/dist/types/src/primitives/TransactionSignature.d.ts.map +1 -1
  56. package/dist/types/src/primitives/utils.d.ts.map +1 -1
  57. package/dist/types/src/transaction/Beef.d.ts +143 -0
  58. package/dist/types/src/transaction/Beef.d.ts.map +1 -0
  59. package/dist/types/src/transaction/BeefParty.d.ts +62 -0
  60. package/dist/types/src/transaction/BeefParty.d.ts.map +1 -0
  61. package/dist/types/src/transaction/BeefTx.d.ts +35 -0
  62. package/dist/types/src/transaction/BeefTx.d.ts.map +1 -0
  63. package/dist/types/src/transaction/Transaction.d.ts.map +1 -1
  64. package/dist/types/src/transaction/TransactionInput.d.ts +1 -1
  65. package/dist/types/src/transaction/TransactionInput.d.ts.map +1 -1
  66. package/dist/types/src/transaction/index.d.ts +3 -0
  67. package/dist/types/src/transaction/index.d.ts.map +1 -1
  68. package/dist/types/tsconfig.types.tsbuildinfo +1 -1
  69. package/docs/primitives.md +373 -55
  70. package/docs/transaction.md +467 -1
  71. package/package.json +1 -1
  72. package/src/primitives/Curve.ts +7 -7
  73. package/src/primitives/ECDSA.ts +485 -75
  74. package/src/primitives/Point.ts +110 -25
  75. package/src/primitives/TransactionSignature.ts +4 -3
  76. package/src/primitives/utils.ts +15 -11
  77. package/src/script/Spend.ts +4 -4
  78. package/src/totp/totp.ts +1 -1
  79. package/src/transaction/Beef.ts +533 -0
  80. package/src/transaction/BeefParty.ts +100 -0
  81. package/src/transaction/BeefTx.ts +121 -0
  82. package/src/transaction/Transaction.ts +95 -69
  83. package/src/transaction/TransactionInput.ts +1 -1
  84. package/src/transaction/__tests/Beef.test.ts +290 -0
  85. package/src/transaction/__tests/Transaction.benchmarks.test.ts +222 -0
  86. package/src/transaction/index.ts +3 -0
@@ -0,0 +1,533 @@
1
+ import MerklePath from "./MerklePath.js";
2
+ import Transaction from "./Transaction.js";
3
+ import ChainTracker from "./ChainTracker.js";
4
+ import BeefTx from "./BeefTx.js";
5
+ import { Reader, Writer, toHex, toArray } from "../primitives/utils.js"
6
+
7
+ export const BEEF_MAGIC = 4022206465 // 0100BEEF in LE order
8
+ export const BEEF_MAGIC_V2 = 4022206466 // 0200BEEF in LE order
9
+ export const BEEF_MAGIC_TXID_ONLY_EXTENSION = 4022206465 // 0100BEEF in LE order
10
+
11
+ /*
12
+ * BEEF standard: BRC-62: Background Evaluation Extended Format (BEEF) Transactions
13
+ * https://github.com/bitcoin-sv/BRCs/blob/master/transactions/0062.md
14
+ *
15
+ * BUMP standard: BRC-74: BSV Unified Merkle Path (BUMP) Format
16
+ * https://github.com/bitcoin-sv/BRCs/blob/master/transactions/0074.md
17
+ *
18
+ * A valid serialized BEEF is the cornerstone of Simplified Payment Validation (SPV)
19
+ * where they are exchanged between two non-trusting parties to establish the
20
+ * validity of a newly constructed bitcoin transaction and its inputs from prior
21
+ * transactions.
22
+ *
23
+ * A `Beef` is fundamentally an list of `BUMP`s and a list of transactions.
24
+ *
25
+ * A `BUMP` is a partial merkle tree for a 'mined' bitcoin block.
26
+ * It can therefore be used to prove the validity of transaction data
27
+ * for each transaction txid whose merkle path is included in the tree.
28
+ *
29
+ * To be valid, the list of transactions must be sorted in dependency order:
30
+ * oldest transaction first;
31
+ * and each transaction must either
32
+ * have a merkle path in one of the BUMPs, or
33
+ * have all of its input transactions included in the list of transactions.
34
+ *
35
+ * The `Beef` class supports the construction of valid BEEFs by allowing BUMPs
36
+ * (merkle paths) and transactions to be merged sequentially.
37
+ *
38
+ * The `Beef` class also extends the standard by supporting 'known' transactions.
39
+ * A 'known' transaction is represented solely by its txid.
40
+ * To become valid, all the 'known' transactions in a `Beef` must be replaced by full
41
+ * transactions and merkle paths, if they are mined.
42
+ *
43
+ * The purpose of supporting 'known' transactions is that one or both parties
44
+ * generating and exchanging BEEFs often possess partial knowledge of valid transactions
45
+ * due to their history.
46
+ *
47
+ * A valid `Beef` is only required when sent to a party with no shared history,
48
+ * such as a transaction processor.
49
+ */
50
+ export class Beef {
51
+ bumps: MerklePath[] = []
52
+ txs: BeefTx[] = []
53
+
54
+ constructor() {
55
+ }
56
+
57
+ /**
58
+ * BEEF_MAGIC is the original V1 version.
59
+ * BEEF_MAGIC_V2 includes support for txidOnly transactions in serialized beefs.
60
+ * @returns version based on current contents.
61
+ */
62
+ get version(): number {
63
+ const hasTxidOnly = !this.txs.every(tx => !tx.isTxidOnly)
64
+ if (hasTxidOnly)
65
+ return BEEF_MAGIC_V2
66
+ else
67
+ return BEEF_MAGIC
68
+ }
69
+
70
+ /**
71
+ * @param txid of `beefTx` to find
72
+ * @returns `BeefTx` in `txs` with `txid`.
73
+ */
74
+ findTxid(txid: string): BeefTx | undefined {
75
+ return this.txs.find(tx => tx.txid === txid)
76
+ }
77
+
78
+ /**
79
+ * Merge a MerklePath that is assumed to be fully valid.
80
+ * @param bump
81
+ * @returns index of merged bump
82
+ */
83
+ mergeBump(bump: MerklePath): number {
84
+ let bumpIndex: number | undefined = undefined
85
+ // If this proof is identical to another one previously added, we use that first. Otherwise, we try to merge it with proofs from the same block.
86
+ for (let i = 0; i < this.bumps.length; i++) {
87
+ const b = this.bumps[i]
88
+ if (b === bump) { // Literally the same
89
+ return i
90
+ }
91
+ if (b.blockHeight === bump.blockHeight) {
92
+ // Probably the same...
93
+ const rootA = b.computeRoot()
94
+ const rootB = bump.computeRoot()
95
+ if (rootA === rootB) {
96
+ // Definitely the same... combine them to save space
97
+ b.combine(bump)
98
+ bumpIndex = i
99
+ break
100
+ }
101
+ }
102
+ }
103
+
104
+ // if the proof is not yet added, add a new path.
105
+ if (bumpIndex === undefined) {
106
+ bumpIndex = this.bumps.length
107
+ this.bumps.push(bump)
108
+ }
109
+
110
+ // review if any transactions are proven by this bump
111
+ const b = this.bumps[bumpIndex]
112
+ for (const tx of this.txs) {
113
+ const txid = tx.txid
114
+ if (!tx.bumpIndex) {
115
+ for (const n of b.path[0]) {
116
+ if (n.hash === txid) {
117
+ tx.bumpIndex = bumpIndex
118
+ n.txid = true
119
+ break
120
+ }
121
+ }
122
+ }
123
+ }
124
+
125
+ return bumpIndex
126
+ }
127
+
128
+ /**
129
+ * Merge a serialized transaction.
130
+ *
131
+ * Checks that a transaction with the same txid hasn't already been merged.
132
+ *
133
+ * Replaces existing transaction with same txid.
134
+ *
135
+ * @param rawTx
136
+ * @param bumpIndex Optional. If a number, must be valid index into bumps array.
137
+ * @returns txid of rawTx
138
+ */
139
+ mergeRawTx(rawTx: number[], bumpIndex?: number): BeefTx {
140
+ const newTx: BeefTx = new BeefTx(rawTx, bumpIndex)
141
+ this.removeExistingTxid(newTx.txid)
142
+ this.txs.push(newTx)
143
+ this.tryToValidateBumpIndex(newTx)
144
+ return newTx
145
+ }
146
+
147
+ /**
148
+ * Merge a `Transaction` and any referenced `merklePath` and `sourceTransaction`, recursifely.
149
+ *
150
+ * Replaces existing transaction with same txid.
151
+ *
152
+ * Attempts to match an existing bump to the new transaction.
153
+ *
154
+ * @param tx
155
+ * @returns txid of tx
156
+ */
157
+ mergeTransaction(tx: Transaction): BeefTx {
158
+ const txid = tx.id('hex')
159
+ this.removeExistingTxid(txid)
160
+ let bumpIndex: number | undefined = undefined
161
+ if (tx.merklePath)
162
+ bumpIndex = this.mergeBump(tx.merklePath)
163
+ const newTx = new BeefTx(tx, bumpIndex)
164
+ this.txs.push(newTx)
165
+ this.tryToValidateBumpIndex(newTx)
166
+ bumpIndex = newTx.bumpIndex
167
+ if (bumpIndex === undefined) {
168
+ for (const input of tx.inputs) {
169
+ if (input.sourceTransaction)
170
+ this.mergeTransaction(input.sourceTransaction)
171
+ }
172
+ }
173
+ return newTx
174
+ }
175
+
176
+ /**
177
+ * Removes an existing transaction from the BEEF, given its TXID
178
+ * @param txid TXID of the transaction to remove
179
+ */
180
+ removeExistingTxid(txid: string) {
181
+ const existingTxIndex = this.txs.findIndex(t => t.txid === txid)
182
+ if (existingTxIndex >= 0)
183
+ this.txs.splice(existingTxIndex, 1)
184
+ }
185
+
186
+ mergeTxidOnly(txid: string): BeefTx {
187
+ let tx = this.txs.find(t => t.txid === txid)
188
+ if (!tx) {
189
+ tx = new BeefTx(txid)
190
+ this.txs.push(tx)
191
+ this.tryToValidateBumpIndex(tx)
192
+ }
193
+ return tx
194
+ }
195
+
196
+ mergeBeefTx(btx: BeefTx): BeefTx {
197
+ let beefTx = this.findTxid(btx.txid)
198
+ if (!beefTx && btx.isTxidOnly)
199
+ beefTx = this.mergeTxidOnly(btx.txid)
200
+ else if (!beefTx || beefTx.isTxidOnly) {
201
+ if (btx._tx)
202
+ beefTx = this.mergeTransaction(btx._tx)
203
+ else
204
+ beefTx = this.mergeRawTx(btx._rawTx!)
205
+ }
206
+ return beefTx
207
+ }
208
+
209
+ mergeBeef(beef: number[] | Beef) {
210
+ const b: Beef = Array.isArray(beef) ? Beef.fromBinary(beef) : beef
211
+
212
+ for (const bump of b.bumps)
213
+ this.mergeBump(bump)
214
+
215
+ for (const tx of b.txs)
216
+ this.mergeBeefTx(tx)
217
+ }
218
+
219
+ /**
220
+ * Sorts `txs` and checks structural validity of beef.
221
+ *
222
+ * Does NOT verify merkle roots.
223
+ *
224
+ * Validity requirements:
225
+ * 1. No 'known' txids, unless `allowTxidOnly` is true.
226
+ * 2. All transactions have bumps or their inputs chain back to bumps (or are known).
227
+ * 3. Order of transactions satisfies dependencies before dependents.
228
+ * 4. No transactions with duplicate txids.
229
+ *
230
+ * @param allowTxidOnly optional. If true, transaction txid only is assumed valid
231
+ */
232
+ isValid(allowTxidOnly?: boolean): boolean {
233
+ return this.verifyValid(allowTxidOnly).valid
234
+ }
235
+
236
+ /**
237
+ * Sorts `txs` and confirms validity of transaction data contained in beef
238
+ * by validating structure of this beef and confirming computed merkle roots
239
+ * using `chainTracker`.
240
+ *
241
+ * Validity requirements:
242
+ * 1. No 'known' txids, unless `allowTxidOnly` is true.
243
+ * 2. All transactions have bumps or their inputs chain back to bumps (or are known).
244
+ * 3. Order of transactions satisfies dependencies before dependents.
245
+ * 4. No transactions with duplicate txids.
246
+ *
247
+ * @param chainTracker Used to verify computed merkle path roots for all bump txids.
248
+ * @param allowTxidOnly optional. If true, transaction txid is assumed valid
249
+ */
250
+ async verify(chainTracker: ChainTracker, allowTxidOnly?: boolean): Promise<boolean> {
251
+ const r = this.verifyValid(allowTxidOnly)
252
+ if (!r.valid) return false
253
+
254
+ for (const height of Object.keys(r.roots)) {
255
+ const isValid = await chainTracker.isValidRootForHeight(r.roots[height], Number(height))
256
+ if (!isValid)
257
+ return false
258
+ }
259
+
260
+ return true
261
+ }
262
+
263
+ private verifyValid(allowTxidOnly?: boolean)
264
+ : { valid: boolean, roots: Record<number, string> } {
265
+
266
+ const r: { valid: boolean, roots: Record<number, string> } = { valid: false, roots: {} }
267
+
268
+ this.sortTxs()
269
+
270
+ // valid txids: only txids if allowed, bump txids, then txids with input's in txids
271
+ const txids: Record<string, boolean> = {}
272
+
273
+ for (const tx of this.txs) {
274
+ if (tx.isTxidOnly) {
275
+ if (!allowTxidOnly) return r
276
+ txids[tx.txid] = true
277
+ }
278
+ }
279
+
280
+ const confirmComputedRoot = (b: MerklePath, txid: string): boolean => {
281
+ const root = b.computeRoot(txid)
282
+ if (!r.roots[b.blockHeight]) {
283
+ // accept the root as valid for this block and reuse for subsequent txids
284
+ r.roots[b.blockHeight] = root
285
+ }
286
+ if (r.roots[b.blockHeight] !== root)
287
+ return false
288
+ return true
289
+ }
290
+
291
+ for (const b of this.bumps) {
292
+ for (const n of b.path[0]) {
293
+ if (n.txid && n.hash) {
294
+ txids[n.hash] = true
295
+ // all txid hashes in all bumps must have agree on computed merkle path roots
296
+ if (!confirmComputedRoot(b, n.hash))
297
+ return r
298
+ }
299
+ }
300
+ }
301
+
302
+ for (const t of this.txs) {
303
+ for (const i of t.inputTxids)
304
+ // all input txids must be included before they are referenced
305
+ if (!txids[i]) return r
306
+ txids[t.txid] = true
307
+ }
308
+
309
+ r.valid = true
310
+ return r
311
+ }
312
+
313
+ /**
314
+ * Returns a binary array representing the serialized BEEF
315
+ * @returns A binary array representing the BEEF
316
+ */
317
+ toBinary(): number[] {
318
+
319
+ const writer = new Writer()
320
+ writer.writeUInt32LE(this.version)
321
+
322
+ writer.writeVarIntNum(this.bumps.length)
323
+ for (const b of this.bumps) {
324
+ writer.write(b.toBinary())
325
+ }
326
+
327
+ writer.writeVarIntNum(this.txs.length)
328
+ for (const tx of this.txs) {
329
+ tx.toWriter(writer)
330
+ }
331
+
332
+ return writer.toArray()
333
+ }
334
+
335
+ /**
336
+ * Returns a hex string representing the serialized BEEF
337
+ * @returns A hex string representing the BEEF
338
+ */
339
+ toHex(): string {
340
+ return toHex(this.toBinary())
341
+ }
342
+
343
+ static fromReader(br: Reader): Beef {
344
+ const version = br.readUInt32LE()
345
+ if (version !== BEEF_MAGIC && version !== BEEF_MAGIC_V2)
346
+ throw new Error(`Serialized BEEF must start with ${BEEF_MAGIC} or ${BEEF_MAGIC_V2} but starts with ${version}`)
347
+ const beef = new Beef()
348
+ const bumpsLength = br.readVarIntNum()
349
+ for (let i = 0; i < bumpsLength; i++) {
350
+ const bump = MerklePath.fromReader(br)
351
+ beef.bumps.push(bump)
352
+ }
353
+ const txsLength = br.readVarIntNum()
354
+ for (let i = 0; i < txsLength; i++) {
355
+ const beefTx = BeefTx.fromReader(br)
356
+ beef.txs.push(beefTx)
357
+ }
358
+ return beef
359
+ }
360
+
361
+ /**
362
+ * Constructs an instance of the Beef class based on the provided binary array
363
+ * @param bin The binary array from which to construct BEEF
364
+ * @returns An instance of the Beef class constructed from the binary data
365
+ */
366
+ static fromBinary(bin: number[]): Beef {
367
+ const br = new Reader(bin)
368
+ return Beef.fromReader(br)
369
+ }
370
+
371
+ /**
372
+ * Constructs an instance of the Beef class based on the provided string
373
+ * @param s The string value from which to construct BEEF
374
+ * @param enc The encoding of the string value from which BEEF should be constructed
375
+ * @returns An instance of the Beef class constructed from the string
376
+ */
377
+ static fromString(s: string, enc?: 'hex' | 'utf8' | 'base64'): Beef {
378
+ enc ||= 'hex'
379
+ const bin = toArray(s, enc)
380
+ const br = new Reader(bin)
381
+ return Beef.fromReader(br)
382
+ }
383
+
384
+ /**
385
+ * Try to validate newTx.bumpIndex by looking for an existing bump
386
+ * that proves newTx.txid
387
+ *
388
+ * @param newTx A new `BeefTx` that has been added to this.txs
389
+ * @returns true if a bump was found, false otherwise
390
+ */
391
+ private tryToValidateBumpIndex(newTx: BeefTx): boolean {
392
+ if (newTx.bumpIndex !== undefined)
393
+ return true
394
+ const txid = newTx.txid
395
+ for (let i = 0; i < this.bumps.length; i++) {
396
+ const j = this.bumps[i].path[0].findIndex(b => b.hash === txid)
397
+ if (j >= 0) {
398
+ newTx.bumpIndex = i
399
+ this.bumps[i].path[0][j].txid = true
400
+ return true
401
+ }
402
+ }
403
+ return false
404
+ }
405
+
406
+ /**
407
+ * Sort the `txs` by input txid dependency order.
408
+ * @returns array of input txids of unproven transactions that aren't included in txs.
409
+ */
410
+ sortTxs(): string[] {
411
+ const missingInputs: Record<string, boolean> = {}
412
+
413
+ const txidToTx: Record<string, BeefTx> = {}
414
+
415
+ for (const tx of this.txs) {
416
+ txidToTx[tx.txid] = tx
417
+ // All transactions in this beef start at degree zero.
418
+ tx.degree = 0
419
+ }
420
+
421
+ for (const tx of this.txs) {
422
+ if (tx.bumpIndex === undefined) {
423
+ // For all the unproven transactions,
424
+ // link their inputs that exist in this beef,
425
+ // make a note of missing inputs.
426
+ for (const inputTxid of tx.inputTxids) {
427
+ if (!txidToTx[inputTxid])
428
+ missingInputs[inputTxid] = true
429
+ }
430
+ }
431
+ }
432
+
433
+ // queue of transactions that no unsorted transactions depend upon...
434
+ const queue: BeefTx[] = []
435
+ // sorted transactions
436
+ const result: BeefTx[] = []
437
+
438
+ // Increment each txid's degree for every input reference to it by another txid
439
+ for (const tx of this.txs) {
440
+ for (const inputTxid of tx.inputTxids) {
441
+ const tx = txidToTx[inputTxid]
442
+ if (tx)
443
+ tx.degree++
444
+ }
445
+ }
446
+ // Since circular dependencies aren't possible, start with the txids no one depends on.
447
+ // These are the transactions that should be sent last...
448
+ for (const tx of this.txs) {
449
+ if (tx.degree === 0) {
450
+ queue.push(tx)
451
+ }
452
+ }
453
+ // As long as we have transactions to send...
454
+ while (queue.length) {
455
+ let tx = queue.shift()!
456
+ // Add it as new first to send
457
+ result.unshift(tx)
458
+ // And remove its impact on degree
459
+ // noting that any tx achieving a
460
+ // value of zero can be sent...
461
+ for (const inputTxid of tx.inputTxids) {
462
+ const inputTx = txidToTx[inputTxid]
463
+ if (inputTx) {
464
+ inputTx.degree--
465
+ if (inputTx.degree === 0) {
466
+ queue.push(inputTx)
467
+ }
468
+ }
469
+ }
470
+ }
471
+ this.txs = result
472
+
473
+ return Object.keys(missingInputs)
474
+ }
475
+
476
+ /**
477
+ * @returns a shallow copy of this beef
478
+ */
479
+ clone(): Beef {
480
+ const c = new Beef()
481
+ c.bumps = Array.from(this.bumps)
482
+ c.txs = Array.from(this.txs)
483
+ return c
484
+ }
485
+
486
+ /**
487
+ * Ensure that all the txids in `knownTxids` are txidOnly
488
+ * @param knownTxids
489
+ */
490
+ trimKnownTxids(knownTxids: string[]) {
491
+ for (let i = 0; i < this.txs.length;) {
492
+ const tx = this.txs[i]
493
+ if (tx.isTxidOnly && -1 < knownTxids.indexOf(tx.txid)) {
494
+ this.txs.splice(i, 1)
495
+ } else {
496
+ i++
497
+ }
498
+ }
499
+ // TODO: bumps could be trimmed to eliminate unreferenced proofs.
500
+ }
501
+
502
+ /**
503
+ * @returns Summary of `Beef` contents as multi-line string.
504
+ */
505
+ toLogString(): string {
506
+ let log = ''
507
+ log += `BEEF with ${this.bumps.length} BUMPS and ${this.txs.length} Transactions, isValid ${this.isValid()}\n`
508
+ let i = -1
509
+ for (const b of this.bumps) {
510
+ i++
511
+ log += ` BUMP ${i}\n block: ${b.blockHeight}\n txids: [\n${b.path[0].filter(n => !!n.txid).map(n => ` '${n.hash}'`).join(',\n')}\n ]\n`
512
+ }
513
+ i = -1
514
+ for (const t of this.txs) {
515
+ i++
516
+ log += ` TX ${i}\n txid: ${t.txid}\n`
517
+ if (t.bumpIndex !== undefined) {
518
+ log += ` bumpIndex: ${t.bumpIndex}\n`
519
+ }
520
+ if (t.isTxidOnly) {
521
+ log += ` txidOnly\n`
522
+ } else {
523
+ log += ` rawTx length=${t.rawTx!.length}\n`
524
+ }
525
+ if (t.inputTxids.length > 0) {
526
+ log += ` inputs: [\n${t.inputTxids.map(it => ` '${it}'`).join(',\n')}\n ]\n`
527
+ }
528
+ }
529
+ return log
530
+ }
531
+ }
532
+
533
+ export default Beef
@@ -0,0 +1,100 @@
1
+ import { Beef } from "./Beef.js";
2
+
3
+ /**
4
+ * Extends `Beef` that is used to exchange transaction validity data with more than one external party.
5
+ *
6
+ * Use `addKnownTxidsForParty` to keep track of who knows what to reduce re-transmission of potentially large transactions.
7
+ *
8
+ * Use `getTrimmedBeefForParty` to obtain a `Beef` trimmed of transaction validity data known to a specific party.
9
+ *
10
+ * Typical usage scenario:
11
+ *
12
+ * 1. Query a wallet storage provider for spendable outputs.
13
+ * 2. The provider replies with a Beef validating the returned outputs.
14
+ * 3. Construct a new transaction using some of the queried outputs as inputs, including Beef validating all the inputs.
15
+ * 4. Receive new valid raw transaction after processing and Beef validating change outputs added to original inputs.
16
+ * 5. Return to step 1, continuing to build on old and new spendable outputs.
17
+ *
18
+ * By default, each Beef is required to be complete and valid: All transactions appear as full serialized bitcoin transactions and
19
+ * each transaction either has a merkle path proof (it has been mined) or all of its input transactions are included.
20
+ *
21
+ * The size and redundancy of these Beefs becomes a problem when chained transaction creation out-paces the block mining rate.
22
+ *
23
+ */
24
+ export class BeefParty extends Beef {
25
+ /**
26
+ * keys are party identifiers.
27
+ * values are records of txids with truthy value for which the party already has validity proof.
28
+ */
29
+ knownTo: Record<string, Record<string, boolean>> = {}
30
+
31
+ /**
32
+ *
33
+ * @param parties Optional array of initial unique party identifiers.
34
+ */
35
+ constructor(parties?: string[]) {
36
+ super()
37
+ if (parties) {
38
+ for (const party of parties)
39
+ this.addParty(party)
40
+ }
41
+ }
42
+
43
+ /**
44
+ * @param party
45
+ * @returns `true` if `party` has already beed added to this `BeefParty`.
46
+ */
47
+ isParty(party: string) {
48
+ const r = Object.keys(this.knownTo).includes(party)
49
+ return r
50
+ }
51
+
52
+ /**
53
+ * Adds a new unique party identifier to this `BeefParty`.
54
+ * @param party
55
+ */
56
+ addParty(party: string) {
57
+ if (this.isParty(party))
58
+ throw new Error(`Party ${party} already exists.`)
59
+ this.knownTo[party] = {}
60
+ }
61
+
62
+ /**
63
+ * @param party
64
+ * @returns Array of txids "known" to `party`.
65
+ */
66
+ getKnownTxidsForParty(party: string) : string[] {
67
+ const knownTxids = this.knownTo[party]
68
+ if (!knownTxids)
69
+ throw new Error(`Party ${party} is unknown.`)
70
+ return Object.keys(knownTxids)
71
+ }
72
+
73
+ /**
74
+ * @param party
75
+ * @returns trimmed beef of unknown transactions and proofs for `party`
76
+ */
77
+ getTrimmedBeefForParty(party: string) : Beef {
78
+ const knownTxids = this.getKnownTxidsForParty(party)
79
+ const prunedBeef = this.clone()
80
+ prunedBeef.trimKnownTxids(knownTxids)
81
+ return prunedBeef
82
+ }
83
+
84
+ /**
85
+ * Make note of additional txids "known" to `party`.
86
+ * @param party unique identifier, added if new.
87
+ * @param knownTxids
88
+ */
89
+ addKnownTxidsForParty(party: string, knownTxids: string[]) {
90
+ if (!this.isParty(party))
91
+ this.addParty(party)
92
+ const kts = this.knownTo[party]
93
+ for (const txid of knownTxids) {
94
+ kts[txid] = true
95
+ this.mergeTxidOnly(txid)
96
+ }
97
+ }
98
+ }
99
+
100
+ export default BeefParty