@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.
- package/dist/cjs/package.json +1 -1
- package/dist/cjs/src/primitives/Schnorr.js +92 -0
- package/dist/cjs/src/primitives/Schnorr.js.map +1 -0
- package/dist/cjs/src/primitives/index.js +3 -1
- package/dist/cjs/src/primitives/index.js.map +1 -1
- package/dist/cjs/src/totp/totp.js +1 -1
- package/dist/cjs/src/totp/totp.js.map +1 -1
- package/dist/cjs/src/transaction/Beef.js +292 -155
- package/dist/cjs/src/transaction/Beef.js.map +1 -1
- package/dist/cjs/src/transaction/BeefParty.js +46 -26
- package/dist/cjs/src/transaction/BeefParty.js.map +1 -1
- package/dist/cjs/src/transaction/BeefTx.js +31 -16
- package/dist/cjs/src/transaction/BeefTx.js.map +1 -1
- package/dist/cjs/src/transaction/MerklePath.js.map +1 -1
- package/dist/cjs/src/transaction/Transaction.js +12 -6
- package/dist/cjs/src/transaction/Transaction.js.map +1 -1
- package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/esm/src/primitives/Schnorr.js +87 -0
- package/dist/esm/src/primitives/Schnorr.js.map +1 -0
- package/dist/esm/src/primitives/index.js +1 -0
- package/dist/esm/src/primitives/index.js.map +1 -1
- package/dist/esm/src/totp/totp.js +1 -1
- package/dist/esm/src/totp/totp.js.map +1 -1
- package/dist/esm/src/transaction/Beef.js +294 -157
- package/dist/esm/src/transaction/Beef.js.map +1 -1
- package/dist/esm/src/transaction/BeefParty.js +47 -27
- package/dist/esm/src/transaction/BeefParty.js.map +1 -1
- package/dist/esm/src/transaction/BeefTx.js +35 -20
- package/dist/esm/src/transaction/BeefTx.js.map +1 -1
- package/dist/esm/src/transaction/MerklePath.js.map +1 -1
- package/dist/esm/src/transaction/Transaction.js +12 -6
- package/dist/esm/src/transaction/Transaction.js.map +1 -1
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
- package/dist/types/src/primitives/Schnorr.d.ts +65 -0
- package/dist/types/src/primitives/Schnorr.d.ts.map +1 -0
- package/dist/types/src/primitives/index.d.ts +1 -0
- package/dist/types/src/primitives/index.d.ts.map +1 -1
- package/dist/types/src/transaction/Beef.d.ts +131 -90
- package/dist/types/src/transaction/Beef.d.ts.map +1 -1
- package/dist/types/src/transaction/BeefParty.d.ts +34 -23
- package/dist/types/src/transaction/BeefParty.d.ts.map +1 -1
- package/dist/types/src/transaction/BeefTx.d.ts +11 -6
- package/dist/types/src/transaction/BeefTx.d.ts.map +1 -1
- package/dist/types/src/transaction/MerklePath.d.ts.map +1 -1
- package/dist/types/src/transaction/Transaction.d.ts +8 -2
- package/dist/types/src/transaction/Transaction.d.ts.map +1 -1
- package/dist/types/tsconfig.types.tsbuildinfo +1 -1
- package/dist/umd/bundle.js +1 -1
- package/docs/primitives.md +120 -9
- package/docs/transaction.md +141 -9
- package/package.json +1 -1
- package/src/primitives/Schnorr.ts +95 -0
- package/src/primitives/__tests/Schnorr.test.ts +272 -0
- package/src/primitives/index.ts +1 -0
- package/src/totp/totp.ts +1 -1
- package/src/transaction/Beef.ts +493 -378
- package/src/transaction/BeefParty.ts +71 -58
- package/src/transaction/BeefTx.ts +133 -143
- package/src/transaction/MerklePath.ts +11 -11
- package/src/transaction/Transaction.ts +42 -36
- package/src/transaction/__tests/Beef.test.ts +55 -10
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import MerklePath from
|
|
2
|
-
import BeefTx from
|
|
3
|
-
import { Reader, Writer, toHex, toArray } from
|
|
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
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
-
|
|
77
|
+
}
|
|
78
|
+
if (this.version === 'V2') {
|
|
62
79
|
return BEEF_MAGIC_V2;
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
|
|
70
|
-
|
|
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
|
-
*
|
|
77
|
-
|
|
78
|
-
|
|
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
|
|
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
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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
|
|
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
|
-
|
|
169
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
297
|
+
}
|
|
298
|
+
for (const tx of b.txs) {
|
|
204
299
|
this.mergeBeefTx(tx);
|
|
300
|
+
}
|
|
205
301
|
}
|
|
206
302
|
/**
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
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
|
-
|
|
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
|
-
|
|
291
|
-
|
|
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
|
-
|
|
308
|
-
|
|
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
|
-
|
|
315
|
-
|
|
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
|
-
|
|
332
|
-
|
|
333
|
-
|
|
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
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
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
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
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
|
-
|
|
374
|
-
|
|
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
|
-
|
|
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
|
-
|
|
382
|
-
tx.
|
|
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
|
-
|
|
385
|
-
|
|
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
|
-
|
|
396
|
-
|
|
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
|
|
415
|
-
while (queue.length) {
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
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
|
-
|
|
433
|
-
|
|
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
|
-
|
|
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
|
-
|
|
446
|
-
|
|
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 &&
|
|
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
|
|
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 +=
|
|
616
|
+
log += ' txidOnly\n';
|
|
480
617
|
}
|
|
481
618
|
else {
|
|
482
619
|
log += ` rawTx length=${t.rawTx.length}\n`;
|