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