@bsv/sdk 1.1.29 → 1.1.32

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 (61) hide show
  1. package/dist/cjs/package.json +1 -1
  2. package/dist/cjs/src/primitives/Schnorr.js +92 -0
  3. package/dist/cjs/src/primitives/Schnorr.js.map +1 -0
  4. package/dist/cjs/src/primitives/index.js +3 -1
  5. package/dist/cjs/src/primitives/index.js.map +1 -1
  6. package/dist/cjs/src/totp/totp.js +1 -1
  7. package/dist/cjs/src/totp/totp.js.map +1 -1
  8. package/dist/cjs/src/transaction/Beef.js +292 -155
  9. package/dist/cjs/src/transaction/Beef.js.map +1 -1
  10. package/dist/cjs/src/transaction/BeefParty.js +46 -26
  11. package/dist/cjs/src/transaction/BeefParty.js.map +1 -1
  12. package/dist/cjs/src/transaction/BeefTx.js +31 -16
  13. package/dist/cjs/src/transaction/BeefTx.js.map +1 -1
  14. package/dist/cjs/src/transaction/MerklePath.js.map +1 -1
  15. package/dist/cjs/src/transaction/Transaction.js +12 -6
  16. package/dist/cjs/src/transaction/Transaction.js.map +1 -1
  17. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  18. package/dist/esm/src/primitives/Schnorr.js +87 -0
  19. package/dist/esm/src/primitives/Schnorr.js.map +1 -0
  20. package/dist/esm/src/primitives/index.js +1 -0
  21. package/dist/esm/src/primitives/index.js.map +1 -1
  22. package/dist/esm/src/totp/totp.js +1 -1
  23. package/dist/esm/src/totp/totp.js.map +1 -1
  24. package/dist/esm/src/transaction/Beef.js +294 -157
  25. package/dist/esm/src/transaction/Beef.js.map +1 -1
  26. package/dist/esm/src/transaction/BeefParty.js +47 -27
  27. package/dist/esm/src/transaction/BeefParty.js.map +1 -1
  28. package/dist/esm/src/transaction/BeefTx.js +35 -20
  29. package/dist/esm/src/transaction/BeefTx.js.map +1 -1
  30. package/dist/esm/src/transaction/MerklePath.js.map +1 -1
  31. package/dist/esm/src/transaction/Transaction.js +12 -6
  32. package/dist/esm/src/transaction/Transaction.js.map +1 -1
  33. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  34. package/dist/types/src/primitives/Schnorr.d.ts +65 -0
  35. package/dist/types/src/primitives/Schnorr.d.ts.map +1 -0
  36. package/dist/types/src/primitives/index.d.ts +1 -0
  37. package/dist/types/src/primitives/index.d.ts.map +1 -1
  38. package/dist/types/src/transaction/Beef.d.ts +131 -90
  39. package/dist/types/src/transaction/Beef.d.ts.map +1 -1
  40. package/dist/types/src/transaction/BeefParty.d.ts +34 -23
  41. package/dist/types/src/transaction/BeefParty.d.ts.map +1 -1
  42. package/dist/types/src/transaction/BeefTx.d.ts +11 -6
  43. package/dist/types/src/transaction/BeefTx.d.ts.map +1 -1
  44. package/dist/types/src/transaction/MerklePath.d.ts.map +1 -1
  45. package/dist/types/src/transaction/Transaction.d.ts +8 -2
  46. package/dist/types/src/transaction/Transaction.d.ts.map +1 -1
  47. package/dist/types/tsconfig.types.tsbuildinfo +1 -1
  48. package/dist/umd/bundle.js +1 -1
  49. package/docs/primitives.md +120 -9
  50. package/docs/transaction.md +141 -9
  51. package/package.json +1 -1
  52. package/src/primitives/Schnorr.ts +95 -0
  53. package/src/primitives/__tests/Schnorr.test.ts +272 -0
  54. package/src/primitives/index.ts +1 -0
  55. package/src/totp/totp.ts +1 -1
  56. package/src/transaction/Beef.ts +493 -378
  57. package/src/transaction/BeefParty.ts +71 -58
  58. package/src/transaction/BeefTx.ts +133 -143
  59. package/src/transaction/MerklePath.ts +11 -11
  60. package/src/transaction/Transaction.ts +42 -36
  61. package/src/transaction/__tests/Beef.test.ts +55 -10
@@ -3,13 +3,14 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.Beef = exports.BEEF_MAGIC_TXID_ONLY_EXTENSION = exports.BEEF_MAGIC_V2 = exports.BEEF_MAGIC = void 0;
6
+ exports.Beef = exports.ATOMIC_BEEF = exports.BEEF_MAGIC_TXID_ONLY_EXTENSION = exports.BEEF_MAGIC_V2 = exports.BEEF_MAGIC = void 0;
7
7
  const MerklePath_js_1 = __importDefault(require("./MerklePath.js"));
8
8
  const BeefTx_js_1 = __importDefault(require("./BeefTx.js"));
9
9
  const utils_js_1 = require("../primitives/utils.js");
10
10
  exports.BEEF_MAGIC = 4022206465; // 0100BEEF in LE order
11
11
  exports.BEEF_MAGIC_V2 = 4022206466; // 0200BEEF in LE order
12
12
  exports.BEEF_MAGIC_TXID_ONLY_EXTENSION = 4022206465; // 0100BEEF in LE order
13
+ exports.ATOMIC_BEEF = 0x01010101; // Atomic Beef serialization prefix
13
14
  /*
14
15
  * BEEF standard: BRC-62: Background Evaluation Extended Format (BEEF) Transactions
15
16
  * https://github.com/bitcoin-sv/BRCs/blob/master/transactions/0062.md
@@ -17,6 +18,14 @@ exports.BEEF_MAGIC_TXID_ONLY_EXTENSION = 4022206465; // 0100BEEF in LE order
17
18
  * BUMP standard: BRC-74: BSV Unified Merkle Path (BUMP) Format
18
19
  * https://github.com/bitcoin-sv/BRCs/blob/master/transactions/0074.md
19
20
  *
21
+ * BRC-95: Atomic BEEF Transactions
22
+ * https://github.com/bitcoin-sv/BRCs/blob/master/transactions/0095.md
23
+ *
24
+ * The Atomic BEEF format is supported by the binary deserialization static method `fromBinary`.
25
+ *
26
+ * BRC-96: BEEF V2, Txid Only Extension
27
+ * https://github.com/bitcoin-sv/BRCs/blob/master/transactions/0096.md
28
+ *
20
29
  * A valid serialized BEEF is the cornerstone of Simplified Payment Validation (SPV)
21
30
  * where they are exchanged between two non-trusting parties to establish the
22
31
  * validity of a newly constructed bitcoin transaction and its inputs from prior
@@ -48,43 +57,121 @@ exports.BEEF_MAGIC_TXID_ONLY_EXTENSION = 4022206465; // 0100BEEF in LE order
48
57
  *
49
58
  * A valid `Beef` is only required when sent to a party with no shared history,
50
59
  * such as a transaction processor.
60
+ *
61
+ * IMPORTANT NOTE:
62
+ * It is fundamental to the BEEF value proposition that only valid transactions and valid
63
+ * merkle path (BUMP) data be added to it. Merging invalid data breaks the `verify` and `isValid`
64
+ * functions. There is no support for removing invalid data. A `Beef` that becomes invalid
65
+ * must be discarded.
51
66
  */
52
67
  class Beef {
53
68
  constructor(version) {
54
69
  this.bumps = [];
55
70
  this.txs = [];
56
71
  this.version = undefined;
72
+ this.atomicTxid = undefined;
57
73
  this.version = version;
58
74
  }
59
75
  /**
60
- * BEEF_MAGIC is the original V1 version.
61
- * BEEF_MAGIC_V2 includes support for txidOnly transactions in serialized beefs.
62
- * @returns version magic value based on current contents and constructor version parameter.
63
- */
76
+ * BEEF_MAGIC is the original V1 version.
77
+ * BEEF_MAGIC_V2 includes support for txidOnly transactions in serialized beefs.
78
+ * @returns version magic value based on current contents and constructor version parameter.
79
+ */
64
80
  get magic() {
65
- if (this.version === 'V1')
81
+ if (this.version === 'V1') {
66
82
  return exports.BEEF_MAGIC;
67
- if (this.version === 'V2')
83
+ }
84
+ if (this.version === 'V2') {
68
85
  return exports.BEEF_MAGIC_V2;
69
- const hasTxidOnly = -1 < this.txs.findIndex(tx => tx.isTxidOnly);
70
- if (hasTxidOnly)
86
+ }
87
+ const hasTxidOnly = this.txs.findIndex(tx => tx.isTxidOnly) > -1;
88
+ if (hasTxidOnly) {
71
89
  return exports.BEEF_MAGIC_V2;
90
+ }
72
91
  return exports.BEEF_MAGIC;
73
92
  }
74
93
  /**
75
- * @param txid of `beefTx` to find
76
- * @returns `BeefTx` in `txs` with `txid`.
77
- */
94
+ * @param txid of `beefTx` to find
95
+ * @returns `BeefTx` in `txs` with `txid`.
96
+ */
78
97
  findTxid(txid) {
79
98
  return this.txs.find(tx => tx.txid === txid);
80
99
  }
81
100
  /**
82
- * Merge a MerklePath that is assumed to be fully valid.
83
- * @param bump
84
- * @returns index of merged bump
101
+ * @returns `MerklePath` with level zero hash equal to txid or undefined.
102
+ */
103
+ findBump(txid) {
104
+ return this.bumps.find(b => b.path[0].find(leaf => leaf.hash === txid));
105
+ }
106
+ /**
107
+ * Finds a Transaction in this `Beef`
108
+ * and adds any missing input SourceTransactions from this `Beef`.
109
+ *
110
+ * The result is suitable for signing.
111
+ *
112
+ * @param txid The id of the target transaction.
113
+ * @returns Transaction with all available input `SourceTransaction`s from this Beef.
114
+ */
115
+ findTransactionForSigning(txid) {
116
+ const beefTx = this.findTxid(txid);
117
+ if (!beefTx)
118
+ return undefined;
119
+ for (const i of beefTx.tx.inputs) {
120
+ if (!i.sourceTransaction) {
121
+ const itx = this.findTxid(i.sourceTXID);
122
+ if (itx) {
123
+ i.sourceTransaction = itx.tx;
124
+ }
125
+ }
126
+ }
127
+ return beefTx.tx;
128
+ }
129
+ /**
130
+ * Builds the proof tree rooted at a specific `Transaction`.
131
+ *
132
+ * To succeed, the Beef must contain all the required transaction and merkle path data.
133
+ *
134
+ * @param txid The id of the target transaction.
135
+ * @returns Transaction with input `SourceTransaction` and `MerklePath` populated from this Beef.
85
136
  */
137
+ findAtomicTransaction(txid) {
138
+ const beefTx = this.findTxid(txid);
139
+ if (!beefTx)
140
+ return undefined;
141
+ const addInputProof = (beef, tx) => {
142
+ const mp = beef.findBump(tx.id('hex'));
143
+ if (mp)
144
+ tx.merklePath = mp;
145
+ else {
146
+ for (const i of tx.inputs) {
147
+ if (!i.sourceTransaction) {
148
+ const itx = beef.findTxid(i.sourceTXID);
149
+ if (itx) {
150
+ i.sourceTransaction = itx.tx;
151
+ }
152
+ }
153
+ if (i.sourceTransaction) {
154
+ const mp = beef.findBump(i.sourceTransaction.id('hex'));
155
+ if (mp) {
156
+ i.sourceTransaction.merklePath = mp;
157
+ }
158
+ else {
159
+ addInputProof(beef, i.sourceTransaction);
160
+ }
161
+ }
162
+ }
163
+ }
164
+ };
165
+ addInputProof(this, beefTx.tx);
166
+ return beefTx.tx;
167
+ }
168
+ /**
169
+ * Merge a MerklePath that is assumed to be fully valid.
170
+ * @param bump
171
+ * @returns index of merged bump
172
+ */
86
173
  mergeBump(bump) {
87
- let bumpIndex = undefined;
174
+ let bumpIndex;
88
175
  // 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.
89
176
  for (let i = 0; i < this.bumps.length; i++) {
90
177
  const b = this.bumps[i];
@@ -125,16 +212,16 @@ class Beef {
125
212
  return bumpIndex;
126
213
  }
127
214
  /**
128
- * Merge a serialized transaction.
129
- *
130
- * Checks that a transaction with the same txid hasn't already been merged.
131
- *
132
- * Replaces existing transaction with same txid.
133
- *
134
- * @param rawTx
135
- * @param bumpIndex Optional. If a number, must be valid index into bumps array.
136
- * @returns txid of rawTx
137
- */
215
+ * Merge a serialized transaction.
216
+ *
217
+ * Checks that a transaction with the same txid hasn't already been merged.
218
+ *
219
+ * Replaces existing transaction with same txid.
220
+ *
221
+ * @param rawTx
222
+ * @param bumpIndex Optional. If a number, must be valid index into bumps array.
223
+ * @returns txid of rawTx
224
+ */
138
225
  mergeRawTx(rawTx, bumpIndex) {
139
226
  const newTx = new BeefTx_js_1.default(rawTx, bumpIndex);
140
227
  this.removeExistingTxid(newTx.txid);
@@ -143,45 +230,49 @@ class Beef {
143
230
  return newTx;
144
231
  }
145
232
  /**
146
- * Merge a `Transaction` and any referenced `merklePath` and `sourceTransaction`, recursifely.
147
- *
148
- * Replaces existing transaction with same txid.
149
- *
150
- * Attempts to match an existing bump to the new transaction.
151
- *
152
- * @param tx
153
- * @returns txid of tx
154
- */
233
+ * Merge a `Transaction` and any referenced `merklePath` and `sourceTransaction`, recursifely.
234
+ *
235
+ * Replaces existing transaction with same txid.
236
+ *
237
+ * Attempts to match an existing bump to the new transaction.
238
+ *
239
+ * @param tx
240
+ * @returns txid of tx
241
+ */
155
242
  mergeTransaction(tx) {
156
243
  const txid = tx.id('hex');
157
244
  this.removeExistingTxid(txid);
158
- let bumpIndex = undefined;
159
- if (tx.merklePath)
245
+ let bumpIndex;
246
+ if (tx.merklePath) {
160
247
  bumpIndex = this.mergeBump(tx.merklePath);
248
+ }
161
249
  const newTx = new BeefTx_js_1.default(tx, bumpIndex);
162
250
  this.txs.push(newTx);
163
251
  this.tryToValidateBumpIndex(newTx);
164
252
  bumpIndex = newTx.bumpIndex;
165
253
  if (bumpIndex === undefined) {
166
254
  for (const input of tx.inputs) {
167
- if (input.sourceTransaction)
255
+ if (input.sourceTransaction) {
168
256
  this.mergeTransaction(input.sourceTransaction);
257
+ }
169
258
  }
170
259
  }
171
260
  return newTx;
172
261
  }
173
262
  /**
174
- * Removes an existing transaction from the BEEF, given its TXID
175
- * @param txid TXID of the transaction to remove
176
- */
263
+ * Removes an existing transaction from the BEEF, given its TXID
264
+ * @param txid TXID of the transaction to remove
265
+ */
177
266
  removeExistingTxid(txid) {
178
267
  const existingTxIndex = this.txs.findIndex(t => t.txid === txid);
179
- if (existingTxIndex >= 0)
268
+ if (existingTxIndex >= 0) {
180
269
  this.txs.splice(existingTxIndex, 1);
270
+ }
181
271
  }
182
272
  mergeTxidOnly(txid) {
183
- if (this.version === 'V1')
184
- throw new Error(`BEEF V1 format does not support txid only transactions.`);
273
+ if (this.version === 'V1') {
274
+ throw new Error('BEEF V1 format does not support txid only transactions.');
275
+ }
185
276
  let tx = this.txs.find(t => t.txid === txid);
186
277
  if (!tx) {
187
278
  tx = new BeefTx_js_1.default(txid);
@@ -192,61 +283,67 @@ class Beef {
192
283
  }
193
284
  mergeBeefTx(btx) {
194
285
  let beefTx = this.findTxid(btx.txid);
195
- if (!beefTx && btx.isTxidOnly)
286
+ if (!beefTx && btx.isTxidOnly) {
196
287
  beefTx = this.mergeTxidOnly(btx.txid);
288
+ }
197
289
  else if (!beefTx || beefTx.isTxidOnly) {
198
- if (btx._tx)
290
+ if (btx._tx) {
199
291
  beefTx = this.mergeTransaction(btx._tx);
200
- else
292
+ }
293
+ else {
201
294
  beefTx = this.mergeRawTx(btx._rawTx);
295
+ }
202
296
  }
203
297
  return beefTx;
204
298
  }
205
299
  mergeBeef(beef) {
206
300
  const b = Array.isArray(beef) ? Beef.fromBinary(beef) : beef;
207
- for (const bump of b.bumps)
301
+ for (const bump of b.bumps) {
208
302
  this.mergeBump(bump);
209
- for (const tx of b.txs)
303
+ }
304
+ for (const tx of b.txs) {
210
305
  this.mergeBeefTx(tx);
306
+ }
211
307
  }
212
308
  /**
213
- * Sorts `txs` and checks structural validity of beef.
214
- *
215
- * Does NOT verify merkle roots.
216
- *
217
- * Validity requirements:
218
- * 1. No 'known' txids, unless `allowTxidOnly` is true.
219
- * 2. All transactions have bumps or their inputs chain back to bumps (or are known).
220
- * 3. Order of transactions satisfies dependencies before dependents.
221
- * 4. No transactions with duplicate txids.
222
- *
223
- * @param allowTxidOnly optional. If true, transaction txid only is assumed valid
224
- */
309
+ * Sorts `txs` and checks structural validity of beef.
310
+ *
311
+ * Does NOT verify merkle roots.
312
+ *
313
+ * Validity requirements:
314
+ * 1. No 'known' txids, unless `allowTxidOnly` is true.
315
+ * 2. All transactions have bumps or their inputs chain back to bumps (or are known).
316
+ * 3. Order of transactions satisfies dependencies before dependents.
317
+ * 4. No transactions with duplicate txids.
318
+ *
319
+ * @param allowTxidOnly optional. If true, transaction txid only is assumed valid
320
+ */
225
321
  isValid(allowTxidOnly) {
226
322
  return this.verifyValid(allowTxidOnly).valid;
227
323
  }
228
324
  /**
229
- * Sorts `txs` and confirms validity of transaction data contained in beef
230
- * by validating structure of this beef and confirming computed merkle roots
231
- * using `chainTracker`.
232
- *
233
- * Validity requirements:
234
- * 1. No 'known' txids, unless `allowTxidOnly` is true.
235
- * 2. All transactions have bumps or their inputs chain back to bumps (or are known).
236
- * 3. Order of transactions satisfies dependencies before dependents.
237
- * 4. No transactions with duplicate txids.
238
- *
239
- * @param chainTracker Used to verify computed merkle path roots for all bump txids.
240
- * @param allowTxidOnly optional. If true, transaction txid is assumed valid
241
- */
325
+ * Sorts `txs` and confirms validity of transaction data contained in beef
326
+ * by validating structure of this beef and confirming computed merkle roots
327
+ * using `chainTracker`.
328
+ *
329
+ * Validity requirements:
330
+ * 1. No 'known' txids, unless `allowTxidOnly` is true.
331
+ * 2. All transactions have bumps or their inputs chain back to bumps (or are known).
332
+ * 3. Order of transactions satisfies dependencies before dependents.
333
+ * 4. No transactions with duplicate txids.
334
+ *
335
+ * @param chainTracker Used to verify computed merkle path roots for all bump txids.
336
+ * @param allowTxidOnly optional. If true, transaction txid is assumed valid
337
+ */
242
338
  async verify(chainTracker, allowTxidOnly) {
243
339
  const r = this.verifyValid(allowTxidOnly);
244
340
  if (!r.valid)
245
341
  return false;
246
342
  for (const height of Object.keys(r.roots)) {
247
343
  const isValid = await chainTracker.isValidRootForHeight(r.roots[height], Number(height));
248
- if (!isValid)
344
+ if (!isValid) {
249
345
  return false;
346
+ }
250
347
  }
251
348
  return true;
252
349
  }
@@ -268,8 +365,9 @@ class Beef {
268
365
  // accept the root as valid for this block and reuse for subsequent txids
269
366
  r.roots[b.blockHeight] = root;
270
367
  }
271
- if (r.roots[b.blockHeight] !== root)
368
+ if (r.roots[b.blockHeight] !== root) {
272
369
  return false;
370
+ }
273
371
  return true;
274
372
  };
275
373
  for (const b of this.bumps) {
@@ -277,25 +375,28 @@ class Beef {
277
375
  if (n.txid && n.hash) {
278
376
  txids[n.hash] = true;
279
377
  // all txid hashes in all bumps must have agree on computed merkle path roots
280
- if (!confirmComputedRoot(b, n.hash))
378
+ if (!confirmComputedRoot(b, n.hash)) {
281
379
  return r;
380
+ }
282
381
  }
283
382
  }
284
383
  }
285
384
  for (const t of this.txs) {
286
- for (const i of t.inputTxids)
287
- // all input txids must be included before they are referenced
385
+ for (const i of t.inputTxids)
386
+ // all input txids must be included before they are referenced
387
+ {
288
388
  if (!txids[i])
289
389
  return r;
390
+ }
290
391
  txids[t.txid] = true;
291
392
  }
292
393
  r.valid = true;
293
394
  return r;
294
395
  }
295
396
  /**
296
- * Returns a binary array representing the serialized BEEF
297
- * @returns A binary array representing the BEEF
298
- */
397
+ * Returns a binary array representing the serialized BEEF
398
+ * @returns A binary array representing the BEEF
399
+ */
299
400
  toBinary() {
300
401
  const writer = new utils_js_1.Writer();
301
402
  writer.writeUInt32LE(this.magic);
@@ -310,16 +411,23 @@ class Beef {
310
411
  return writer.toArray();
311
412
  }
312
413
  /**
313
- * Returns a hex string representing the serialized BEEF
314
- * @returns A hex string representing the BEEF
315
- */
414
+ * Returns a hex string representing the serialized BEEF
415
+ * @returns A hex string representing the BEEF
416
+ */
316
417
  toHex() {
317
418
  return (0, utils_js_1.toHex)(this.toBinary());
318
419
  }
319
420
  static fromReader(br) {
320
- const version = br.readUInt32LE();
321
- if (version !== exports.BEEF_MAGIC && version !== exports.BEEF_MAGIC_V2)
421
+ let version = br.readUInt32LE();
422
+ let atomicTxid = undefined;
423
+ if (version === exports.ATOMIC_BEEF) {
424
+ // Skip the txid and re-read the BEEF version
425
+ atomicTxid = (0, utils_js_1.toHex)(br.readReverse(32));
426
+ version = br.readUInt32LE();
427
+ }
428
+ if (version !== exports.BEEF_MAGIC && version !== exports.BEEF_MAGIC_V2) {
322
429
  throw new Error(`Serialized BEEF must start with ${exports.BEEF_MAGIC} or ${exports.BEEF_MAGIC_V2} but starts with ${version}`);
430
+ }
323
431
  const beef = new Beef(version === exports.BEEF_MAGIC_V2 ? 'V2' : undefined);
324
432
  const bumpsLength = br.readVarIntNum();
325
433
  for (let i = 0; i < bumpsLength; i++) {
@@ -331,23 +439,24 @@ class Beef {
331
439
  const beefTx = BeefTx_js_1.default.fromReader(br, version);
332
440
  beef.txs.push(beefTx);
333
441
  }
442
+ beef.atomicTxid = atomicTxid;
334
443
  return beef;
335
444
  }
336
445
  /**
337
- * Constructs an instance of the Beef class based on the provided binary array
338
- * @param bin The binary array from which to construct BEEF
339
- * @returns An instance of the Beef class constructed from the binary data
340
- */
446
+ * Constructs an instance of the Beef class based on the provided binary array
447
+ * @param bin The binary array from which to construct BEEF
448
+ * @returns An instance of the Beef class constructed from the binary data
449
+ */
341
450
  static fromBinary(bin) {
342
451
  const br = new utils_js_1.Reader(bin);
343
452
  return Beef.fromReader(br);
344
453
  }
345
454
  /**
346
- * Constructs an instance of the Beef class based on the provided string
347
- * @param s The string value from which to construct BEEF
348
- * @param enc The encoding of the string value from which BEEF should be constructed
349
- * @returns An instance of the Beef class constructed from the string
350
- */
455
+ * Constructs an instance of the Beef class based on the provided string
456
+ * @param s The string value from which to construct BEEF
457
+ * @param enc The encoding of the string value from which BEEF should be constructed
458
+ * @returns An instance of the Beef class constructed from the string
459
+ */
351
460
  static fromString(s, enc) {
352
461
  enc || (enc = 'hex');
353
462
  const bin = (0, utils_js_1.toArray)(s, enc);
@@ -355,15 +464,16 @@ class Beef {
355
464
  return Beef.fromReader(br);
356
465
  }
357
466
  /**
358
- * Try to validate newTx.bumpIndex by looking for an existing bump
359
- * that proves newTx.txid
360
- *
361
- * @param newTx A new `BeefTx` that has been added to this.txs
362
- * @returns true if a bump was found, false otherwise
363
- */
467
+ * Try to validate newTx.bumpIndex by looking for an existing bump
468
+ * that proves newTx.txid
469
+ *
470
+ * @param newTx A new `BeefTx` that has been added to this.txs
471
+ * @returns true if a bump was found, false otherwise
472
+ */
364
473
  tryToValidateBumpIndex(newTx) {
365
- if (newTx.bumpIndex !== undefined)
474
+ if (newTx.bumpIndex !== undefined) {
366
475
  return true;
476
+ }
367
477
  const txid = newTx.txid;
368
478
  for (let i = 0; i < this.bumps.length; i++) {
369
479
  const j = this.bumps[i].path[0].findIndex(b => b.hash === txid);
@@ -376,71 +486,91 @@ class Beef {
376
486
  return false;
377
487
  }
378
488
  /**
379
- * Sort the `txs` by input txid dependency order.
380
- * @returns array of input txids of unproven transactions that aren't included in txs.
381
- */
489
+ * Sort the `txs` by input txid dependency order:
490
+ * - Oldest Tx Anchored by Path
491
+ * - Newer Txs depending on Older parents
492
+ * - Newest Tx
493
+ *
494
+ * with proof (MerklePath) last, longest chain of dependencies first
495
+ *
496
+ * @returns `{ missingInputs, notValid, valid, withMissingInputs }`
497
+ */
382
498
  sortTxs() {
383
- const missingInputs = {};
499
+ // Hashtable of valid txids (with proof or all inputs chain to proof)
500
+ const validTxids = {};
501
+ // Hashtable of all transaction txids to transaction
384
502
  const txidToTx = {};
503
+ // queue of unsorted transactions ...
504
+ let queue = [];
505
+ // sorted transactions: hasProof to with longest dependency chain
506
+ const result = [];
507
+ const txidOnly = [];
385
508
  for (const tx of this.txs) {
386
509
  txidToTx[tx.txid] = tx;
387
- // All transactions in this beef start at degree zero.
388
- tx.degree = 0;
510
+ tx.isValid = tx.hasProof;
511
+ if (tx.isValid) {
512
+ validTxids[tx.txid] = true;
513
+ result.push(tx);
514
+ }
515
+ else if (tx.isTxidOnly)
516
+ txidOnly.push(tx);
517
+ else
518
+ queue.push(tx);
389
519
  }
390
- for (const tx of this.txs) {
391
- if (tx.bumpIndex === undefined) {
520
+ // Hashtable of unknown input txids used to fund transactions without their own proof.
521
+ const missingInputs = {};
522
+ // transactions with one or more missing inputs
523
+ const txsMissingInputs = [];
524
+ const possiblyMissingInputs = queue;
525
+ queue = [];
526
+ for (const tx of possiblyMissingInputs) {
527
+ let hasMissingInput = false;
528
+ if (!tx.isValid) {
392
529
  // For all the unproven transactions,
393
530
  // link their inputs that exist in this beef,
394
531
  // make a note of missing inputs.
395
532
  for (const inputTxid of tx.inputTxids) {
396
- if (!txidToTx[inputTxid])
533
+ if (!txidToTx[inputTxid]) {
397
534
  missingInputs[inputTxid] = true;
535
+ hasMissingInput = true;
536
+ }
398
537
  }
399
538
  }
400
- }
401
- // queue of transactions that no unsorted transactions depend upon...
402
- const queue = [];
403
- // sorted transactions
404
- const result = [];
405
- // Increment each txid's degree for every input reference to it by another txid
406
- for (const tx of this.txs) {
407
- for (const inputTxid of tx.inputTxids) {
408
- const tx = txidToTx[inputTxid];
409
- if (tx)
410
- tx.degree++;
411
- }
412
- }
413
- // Since circular dependencies aren't possible, start with the txids no one depends on.
414
- // These are the transactions that should be sent last...
415
- for (const tx of this.txs) {
416
- if (tx.degree === 0) {
539
+ if (hasMissingInput)
540
+ txsMissingInputs.push(tx);
541
+ else
417
542
  queue.push(tx);
418
- }
419
543
  }
420
- // As long as we have transactions to send...
421
- while (queue.length) {
422
- let tx = queue.shift();
423
- // Add it as new first to send
424
- result.unshift(tx);
425
- // And remove its impact on degree
426
- // noting that any tx achieving a
427
- // value of zero can be sent...
428
- for (const inputTxid of tx.inputTxids) {
429
- const inputTx = txidToTx[inputTxid];
430
- if (inputTx) {
431
- inputTx.degree--;
432
- if (inputTx.degree === 0) {
433
- queue.push(inputTx);
434
- }
544
+ // As long as we have unsorted transactions...
545
+ while (queue.length > 0) {
546
+ const oldQueue = queue;
547
+ queue = [];
548
+ for (const tx of oldQueue) {
549
+ if (tx.inputTxids.every(txid => validTxids[txid])) {
550
+ validTxids[tx.txid] = true;
551
+ result.push(tx);
435
552
  }
553
+ else
554
+ queue.push(tx);
436
555
  }
556
+ if (oldQueue.length === queue.length)
557
+ break;
437
558
  }
438
- this.txs = result;
439
- return Object.keys(missingInputs);
559
+ // transactions that don't have proofs and don't chain to proofs
560
+ const txsNotValid = queue;
561
+ // New order of txs is sorted, unsortable, txidOnly (no raw transaction)
562
+ this.txs = result.concat(queue).concat(txidOnly).concat(txsMissingInputs);
563
+ return {
564
+ missingInputs: Object.keys(missingInputs),
565
+ notValid: txsNotValid.map(tx => tx.txid),
566
+ valid: Object.keys(validTxids),
567
+ withMissingInputs: txsMissingInputs.map(tx => tx.txid),
568
+ txidOnly: txidOnly.map(tx => tx.txid)
569
+ };
440
570
  }
441
571
  /**
442
- * @returns a shallow copy of this beef
443
- */
572
+ * @returns a shallow copy of this beef
573
+ */
444
574
  clone() {
445
575
  const c = new Beef();
446
576
  c.bumps = Array.from(this.bumps);
@@ -448,13 +578,13 @@ class Beef {
448
578
  return c;
449
579
  }
450
580
  /**
451
- * Ensure that all the txids in `knownTxids` are txidOnly
452
- * @param knownTxids
453
- */
581
+ * Ensure that all the txids in `knownTxids` are txidOnly
582
+ * @param knownTxids
583
+ */
454
584
  trimKnownTxids(knownTxids) {
455
585
  for (let i = 0; i < this.txs.length;) {
456
586
  const tx = this.txs[i];
457
- if (tx.isTxidOnly && -1 < knownTxids.indexOf(tx.txid)) {
587
+ if (tx.isTxidOnly && knownTxids.includes(tx.txid)) {
458
588
  this.txs.splice(i, 1);
459
589
  }
460
590
  else {
@@ -464,8 +594,15 @@ class Beef {
464
594
  // TODO: bumps could be trimmed to eliminate unreferenced proofs.
465
595
  }
466
596
  /**
467
- * @returns Summary of `Beef` contents as multi-line string.
597
+ * @returns array of transaction txids that either have a proof or whose inputs chain back to a proven transaction.
468
598
  */
599
+ getValidTxids() {
600
+ const r = this.sortTxs();
601
+ return r.valid;
602
+ }
603
+ /**
604
+ * @returns Summary of `Beef` contents as multi-line string.
605
+ */
469
606
  toLogString() {
470
607
  let log = '';
471
608
  log += `BEEF with ${this.bumps.length} BUMPS and ${this.txs.length} Transactions, isValid ${this.isValid()}\n`;
@@ -482,7 +619,7 @@ class Beef {
482
619
  log += ` bumpIndex: ${t.bumpIndex}\n`;
483
620
  }
484
621
  if (t.isTxidOnly) {
485
- log += ` txidOnly\n`;
622
+ log += ' txidOnly\n';
486
623
  }
487
624
  else {
488
625
  log += ` rawTx length=${t.rawTx.length}\n`;