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