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