@bsv/sdk 1.1.29 → 1.1.30
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 +139 -118
- package/dist/cjs/src/transaction/Beef.js.map +1 -1
- package/dist/cjs/src/transaction/BeefParty.js +30 -26
- package/dist/cjs/src/transaction/BeefParty.js.map +1 -1
- package/dist/cjs/src/transaction/BeefTx.js +25 -15
- 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.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 +142 -121
- package/dist/esm/src/transaction/Beef.js.map +1 -1
- package/dist/esm/src/transaction/BeefParty.js +31 -27
- package/dist/esm/src/transaction/BeefParty.js.map +1 -1
- package/dist/esm/src/transaction/BeefTx.js +29 -19
- 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.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 +94 -94
- package/dist/types/src/transaction/Beef.d.ts.map +1 -1
- package/dist/types/src/transaction/BeefParty.d.ts +23 -23
- package/dist/types/src/transaction/BeefParty.d.ts.map +1 -1
- package/dist/types/src/transaction/BeefTx.d.ts +5 -5
- 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.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/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 +351 -375
- package/src/transaction/BeefParty.ts +54 -58
- package/src/transaction/BeefTx.ts +128 -143
- package/src/transaction/MerklePath.ts +11 -11
- package/src/transaction/Transaction.ts +32 -32
package/src/transaction/Beef.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import MerklePath from
|
|
2
|
-
import Transaction from
|
|
3
|
-
import ChainTracker from
|
|
4
|
-
import BeefTx from
|
|
5
|
-
import { Reader, Writer, toHex, toArray } from
|
|
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
|
|
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
10
|
|
|
@@ -13,225 +13,211 @@ export type BeefVersion = undefined | 'V1' | 'V2'
|
|
|
13
13
|
/*
|
|
14
14
|
* BEEF standard: BRC-62: Background Evaluation Extended Format (BEEF) Transactions
|
|
15
15
|
* https://github.com/bitcoin-sv/BRCs/blob/master/transactions/0062.md
|
|
16
|
-
*
|
|
16
|
+
*
|
|
17
17
|
* BUMP standard: BRC-74: BSV Unified Merkle Path (BUMP) Format
|
|
18
18
|
* https://github.com/bitcoin-sv/BRCs/blob/master/transactions/0074.md
|
|
19
|
-
*
|
|
19
|
+
*
|
|
20
20
|
* A valid serialized BEEF is the cornerstone of Simplified Payment Validation (SPV)
|
|
21
21
|
* where they are exchanged between two non-trusting parties to establish the
|
|
22
22
|
* validity of a newly constructed bitcoin transaction and its inputs from prior
|
|
23
23
|
* transactions.
|
|
24
|
-
*
|
|
24
|
+
*
|
|
25
25
|
* A `Beef` is fundamentally an list of `BUMP`s and a list of transactions.
|
|
26
|
-
*
|
|
26
|
+
*
|
|
27
27
|
* A `BUMP` is a partial merkle tree for a 'mined' bitcoin block.
|
|
28
28
|
* It can therefore be used to prove the validity of transaction data
|
|
29
29
|
* for each transaction txid whose merkle path is included in the tree.
|
|
30
|
-
*
|
|
30
|
+
*
|
|
31
31
|
* To be valid, the list of transactions must be sorted in dependency order:
|
|
32
32
|
* oldest transaction first;
|
|
33
33
|
* and each transaction must either
|
|
34
34
|
* have a merkle path in one of the BUMPs, or
|
|
35
35
|
* have all of its input transactions included in the list of transactions.
|
|
36
|
-
*
|
|
36
|
+
*
|
|
37
37
|
* The `Beef` class supports the construction of valid BEEFs by allowing BUMPs
|
|
38
38
|
* (merkle paths) and transactions to be merged sequentially.
|
|
39
|
-
*
|
|
39
|
+
*
|
|
40
40
|
* The `Beef` class also extends the standard by supporting 'known' transactions.
|
|
41
41
|
* A 'known' transaction is represented solely by its txid.
|
|
42
42
|
* To become valid, all the 'known' transactions in a `Beef` must be replaced by full
|
|
43
43
|
* transactions and merkle paths, if they are mined.
|
|
44
|
-
*
|
|
44
|
+
*
|
|
45
45
|
* The purpose of supporting 'known' transactions is that one or both parties
|
|
46
46
|
* generating and exchanging BEEFs often possess partial knowledge of valid transactions
|
|
47
47
|
* due to their history.
|
|
48
|
-
*
|
|
48
|
+
*
|
|
49
49
|
* A valid `Beef` is only required when sent to a party with no shared history,
|
|
50
50
|
* such as a transaction processor.
|
|
51
51
|
*/
|
|
52
52
|
export class Beef {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
53
|
+
bumps: MerklePath[] = []
|
|
54
|
+
txs: BeefTx[] = []
|
|
55
|
+
version: BeefVersion = undefined
|
|
56
56
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
57
|
+
constructor (version?: BeefVersion) {
|
|
58
|
+
this.version = version
|
|
59
|
+
}
|
|
60
60
|
|
|
61
|
-
|
|
61
|
+
/**
|
|
62
62
|
* BEEF_MAGIC is the original V1 version.
|
|
63
63
|
* BEEF_MAGIC_V2 includes support for txidOnly transactions in serialized beefs.
|
|
64
64
|
* @returns version magic value based on current contents and constructor version parameter.
|
|
65
65
|
*/
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
return BEEF_MAGIC
|
|
66
|
+
get magic (): number {
|
|
67
|
+
if (this.version === 'V1') { return BEEF_MAGIC }
|
|
69
68
|
|
|
70
|
-
|
|
71
|
-
return BEEF_MAGIC_V2
|
|
69
|
+
if (this.version === 'V2') { return BEEF_MAGIC_V2 }
|
|
72
70
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
return BEEF_MAGIC_V2
|
|
71
|
+
const hasTxidOnly = this.txs.findIndex(tx => tx.isTxidOnly) > -1
|
|
72
|
+
if (hasTxidOnly) { return BEEF_MAGIC_V2 }
|
|
76
73
|
|
|
77
|
-
|
|
78
|
-
|
|
74
|
+
return BEEF_MAGIC
|
|
75
|
+
}
|
|
79
76
|
|
|
80
|
-
|
|
77
|
+
/**
|
|
81
78
|
* @param txid of `beefTx` to find
|
|
82
79
|
* @returns `BeefTx` in `txs` with `txid`.
|
|
83
80
|
*/
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
81
|
+
findTxid (txid: string): BeefTx | undefined {
|
|
82
|
+
return this.txs.find(tx => tx.txid === txid)
|
|
83
|
+
}
|
|
87
84
|
|
|
88
|
-
|
|
85
|
+
/**
|
|
89
86
|
* Merge a MerklePath that is assumed to be fully valid.
|
|
90
|
-
* @param bump
|
|
87
|
+
* @param bump
|
|
91
88
|
* @returns index of merged bump
|
|
92
89
|
*/
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
}
|
|
111
|
-
}
|
|
90
|
+
mergeBump (bump: MerklePath): number {
|
|
91
|
+
let bumpIndex: number | undefined
|
|
92
|
+
// 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.
|
|
93
|
+
for (let i = 0; i < this.bumps.length; i++) {
|
|
94
|
+
const b = this.bumps[i]
|
|
95
|
+
if (b === bump) { // Literally the same
|
|
96
|
+
return i
|
|
97
|
+
}
|
|
98
|
+
if (b.blockHeight === bump.blockHeight) {
|
|
99
|
+
// Probably the same...
|
|
100
|
+
const rootA = b.computeRoot()
|
|
101
|
+
const rootB = bump.computeRoot()
|
|
102
|
+
if (rootA === rootB) {
|
|
103
|
+
// Definitely the same... combine them to save space
|
|
104
|
+
b.combine(bump)
|
|
105
|
+
bumpIndex = i
|
|
106
|
+
break
|
|
112
107
|
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
113
110
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
111
|
+
// if the proof is not yet added, add a new path.
|
|
112
|
+
if (bumpIndex === undefined) {
|
|
113
|
+
bumpIndex = this.bumps.length
|
|
114
|
+
this.bumps.push(bump)
|
|
115
|
+
}
|
|
119
116
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
}
|
|
132
|
-
}
|
|
117
|
+
// review if any transactions are proven by this bump
|
|
118
|
+
const b = this.bumps[bumpIndex]
|
|
119
|
+
for (const tx of this.txs) {
|
|
120
|
+
const txid = tx.txid
|
|
121
|
+
if (!tx.bumpIndex) {
|
|
122
|
+
for (const n of b.path[0]) {
|
|
123
|
+
if (n.hash === txid) {
|
|
124
|
+
tx.bumpIndex = bumpIndex
|
|
125
|
+
n.txid = true
|
|
126
|
+
break
|
|
127
|
+
}
|
|
133
128
|
}
|
|
134
|
-
|
|
135
|
-
return bumpIndex
|
|
129
|
+
}
|
|
136
130
|
}
|
|
137
131
|
|
|
138
|
-
|
|
132
|
+
return bumpIndex
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
139
136
|
* Merge a serialized transaction.
|
|
140
|
-
*
|
|
137
|
+
*
|
|
141
138
|
* Checks that a transaction with the same txid hasn't already been merged.
|
|
142
|
-
*
|
|
139
|
+
*
|
|
143
140
|
* Replaces existing transaction with same txid.
|
|
144
|
-
*
|
|
145
|
-
* @param rawTx
|
|
141
|
+
*
|
|
142
|
+
* @param rawTx
|
|
146
143
|
* @param bumpIndex Optional. If a number, must be valid index into bumps array.
|
|
147
144
|
* @returns txid of rawTx
|
|
148
145
|
*/
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
146
|
+
mergeRawTx (rawTx: number[], bumpIndex?: number): BeefTx {
|
|
147
|
+
const newTx: BeefTx = new BeefTx(rawTx, bumpIndex)
|
|
148
|
+
this.removeExistingTxid(newTx.txid)
|
|
149
|
+
this.txs.push(newTx)
|
|
150
|
+
this.tryToValidateBumpIndex(newTx)
|
|
151
|
+
return newTx
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
158
155
|
* Merge a `Transaction` and any referenced `merklePath` and `sourceTransaction`, recursifely.
|
|
159
|
-
*
|
|
156
|
+
*
|
|
160
157
|
* Replaces existing transaction with same txid.
|
|
161
|
-
*
|
|
158
|
+
*
|
|
162
159
|
* Attempts to match an existing bump to the new transaction.
|
|
163
|
-
*
|
|
164
|
-
* @param tx
|
|
160
|
+
*
|
|
161
|
+
* @param tx
|
|
165
162
|
* @returns txid of tx
|
|
166
163
|
*/
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
this.mergeTransaction(input.sourceTransaction)
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
return newTx
|
|
164
|
+
mergeTransaction (tx: Transaction): BeefTx {
|
|
165
|
+
const txid = tx.id('hex')
|
|
166
|
+
this.removeExistingTxid(txid)
|
|
167
|
+
let bumpIndex: number | undefined
|
|
168
|
+
if (tx.merklePath) { bumpIndex = this.mergeBump(tx.merklePath) }
|
|
169
|
+
const newTx = new BeefTx(tx, bumpIndex)
|
|
170
|
+
this.txs.push(newTx)
|
|
171
|
+
this.tryToValidateBumpIndex(newTx)
|
|
172
|
+
bumpIndex = newTx.bumpIndex
|
|
173
|
+
if (bumpIndex === undefined) {
|
|
174
|
+
for (const input of tx.inputs) {
|
|
175
|
+
if (input.sourceTransaction) { this.mergeTransaction(input.sourceTransaction) }
|
|
176
|
+
}
|
|
184
177
|
}
|
|
178
|
+
return newTx
|
|
179
|
+
}
|
|
185
180
|
|
|
186
|
-
|
|
181
|
+
/**
|
|
187
182
|
* Removes an existing transaction from the BEEF, given its TXID
|
|
188
183
|
* @param txid TXID of the transaction to remove
|
|
189
184
|
*/
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
185
|
+
removeExistingTxid (txid: string) {
|
|
186
|
+
const existingTxIndex = this.txs.findIndex(t => t.txid === txid)
|
|
187
|
+
if (existingTxIndex >= 0) { this.txs.splice(existingTxIndex, 1) }
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
mergeTxidOnly (txid: string): BeefTx {
|
|
191
|
+
if (this.version === 'V1') { throw new Error('BEEF V1 format does not support txid only transactions.') }
|
|
192
|
+
|
|
193
|
+
let tx = this.txs.find(t => t.txid === txid)
|
|
194
|
+
if (!tx) {
|
|
195
|
+
tx = new BeefTx(txid)
|
|
196
|
+
this.txs.push(tx)
|
|
197
|
+
this.tryToValidateBumpIndex(tx)
|
|
194
198
|
}
|
|
199
|
+
return tx
|
|
200
|
+
}
|
|
195
201
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
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
|
|
202
|
+
mergeBeefTx (btx: BeefTx): BeefTx {
|
|
203
|
+
let beefTx = this.findTxid(btx.txid)
|
|
204
|
+
if (!beefTx && btx.isTxidOnly) { beefTx = this.mergeTxidOnly(btx.txid) } else if (!beefTx || beefTx.isTxidOnly) {
|
|
205
|
+
if (btx._tx) { beefTx = this.mergeTransaction(btx._tx) } else { beefTx = this.mergeRawTx(btx._rawTx) }
|
|
207
206
|
}
|
|
207
|
+
return beefTx
|
|
208
|
+
}
|
|
208
209
|
|
|
209
|
-
|
|
210
|
-
|
|
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
|
-
}
|
|
210
|
+
mergeBeef (beef: number[] | Beef) {
|
|
211
|
+
const b: Beef = Array.isArray(beef) ? Beef.fromBinary(beef) : beef
|
|
221
212
|
|
|
222
|
-
|
|
223
|
-
const b: Beef = Array.isArray(beef) ? Beef.fromBinary(beef) : beef
|
|
213
|
+
for (const bump of b.bumps) { this.mergeBump(bump) }
|
|
224
214
|
|
|
225
|
-
|
|
226
|
-
|
|
215
|
+
for (const tx of b.txs) { this.mergeBeefTx(tx) }
|
|
216
|
+
}
|
|
227
217
|
|
|
228
|
-
|
|
229
|
-
this.mergeBeefTx(tx)
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
/**
|
|
218
|
+
/**
|
|
233
219
|
* Sorts `txs` and checks structural validity of beef.
|
|
234
|
-
*
|
|
220
|
+
*
|
|
235
221
|
* Does NOT verify merkle roots.
|
|
236
222
|
*
|
|
237
223
|
* Validity requirements:
|
|
@@ -239,14 +225,14 @@ export class Beef {
|
|
|
239
225
|
* 2. All transactions have bumps or their inputs chain back to bumps (or are known).
|
|
240
226
|
* 3. Order of transactions satisfies dependencies before dependents.
|
|
241
227
|
* 4. No transactions with duplicate txids.
|
|
242
|
-
*
|
|
228
|
+
*
|
|
243
229
|
* @param allowTxidOnly optional. If true, transaction txid only is assumed valid
|
|
244
230
|
*/
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
231
|
+
isValid (allowTxidOnly?: boolean): boolean {
|
|
232
|
+
return this.verifyValid(allowTxidOnly).valid
|
|
233
|
+
}
|
|
248
234
|
|
|
249
|
-
|
|
235
|
+
/**
|
|
250
236
|
* Sorts `txs` and confirms validity of transaction data contained in beef
|
|
251
237
|
* by validating structure of this beef and confirming computed merkle roots
|
|
252
238
|
* using `chainTracker`.
|
|
@@ -256,291 +242,281 @@ export class Beef {
|
|
|
256
242
|
* 2. All transactions have bumps or their inputs chain back to bumps (or are known).
|
|
257
243
|
* 3. Order of transactions satisfies dependencies before dependents.
|
|
258
244
|
* 4. No transactions with duplicate txids.
|
|
259
|
-
*
|
|
245
|
+
*
|
|
260
246
|
* @param chainTracker Used to verify computed merkle path roots for all bump txids.
|
|
261
247
|
* @param allowTxidOnly optional. If true, transaction txid is assumed valid
|
|
262
248
|
*/
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
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
|
-
}
|
|
249
|
+
async verify (chainTracker: ChainTracker, allowTxidOnly?: boolean): Promise<boolean> {
|
|
250
|
+
const r = this.verifyValid(allowTxidOnly)
|
|
251
|
+
if (!r.valid) return false
|
|
272
252
|
|
|
273
|
-
|
|
253
|
+
for (const height of Object.keys(r.roots)) {
|
|
254
|
+
const isValid = await chainTracker.isValidRootForHeight(r.roots[height], Number(height))
|
|
255
|
+
if (!isValid) { return false }
|
|
274
256
|
}
|
|
275
257
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
const r: { valid: boolean, roots: Record<number, string> } = { valid: false, roots: {} }
|
|
258
|
+
return true
|
|
259
|
+
}
|
|
280
260
|
|
|
281
|
-
|
|
261
|
+
private verifyValid (allowTxidOnly?: boolean): { valid: boolean, roots: Record<number, string> } {
|
|
262
|
+
const r: { valid: boolean, roots: Record<number, string> } = { valid: false, roots: {} }
|
|
282
263
|
|
|
283
|
-
|
|
284
|
-
const txids: Record<string, boolean> = {}
|
|
264
|
+
this.sortTxs()
|
|
285
265
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
if (!allowTxidOnly) return r
|
|
289
|
-
txids[tx.txid] = true
|
|
290
|
-
}
|
|
291
|
-
}
|
|
266
|
+
// valid txids: only txids if allowed, bump txids, then txids with input's in txids
|
|
267
|
+
const txids: Record<string, boolean> = {}
|
|
292
268
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
if (r.roots[b.blockHeight] !== root)
|
|
300
|
-
return false
|
|
301
|
-
return true
|
|
302
|
-
}
|
|
269
|
+
for (const tx of this.txs) {
|
|
270
|
+
if (tx.isTxidOnly) {
|
|
271
|
+
if (!allowTxidOnly) return r
|
|
272
|
+
txids[tx.txid] = true
|
|
273
|
+
}
|
|
274
|
+
}
|
|
303
275
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
}
|
|
276
|
+
const confirmComputedRoot = (b: MerklePath, txid: string): boolean => {
|
|
277
|
+
const root = b.computeRoot(txid)
|
|
278
|
+
if (!r.roots[b.blockHeight]) {
|
|
279
|
+
// accept the root as valid for this block and reuse for subsequent txids
|
|
280
|
+
r.roots[b.blockHeight] = root
|
|
281
|
+
}
|
|
282
|
+
if (r.roots[b.blockHeight] !== root) { return false }
|
|
283
|
+
return true
|
|
284
|
+
}
|
|
314
285
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
286
|
+
for (const b of this.bumps) {
|
|
287
|
+
for (const n of b.path[0]) {
|
|
288
|
+
if (n.txid && n.hash) {
|
|
289
|
+
txids[n.hash] = true
|
|
290
|
+
// all txid hashes in all bumps must have agree on computed merkle path roots
|
|
291
|
+
if (!confirmComputedRoot(b, n.hash)) { return r }
|
|
320
292
|
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
321
295
|
|
|
322
|
-
|
|
323
|
-
|
|
296
|
+
for (const t of this.txs) {
|
|
297
|
+
for (const i of t.inputTxids)
|
|
298
|
+
// all input txids must be included before they are referenced
|
|
299
|
+
{ if (!txids[i]) return r }
|
|
300
|
+
txids[t.txid] = true
|
|
324
301
|
}
|
|
325
302
|
|
|
326
|
-
|
|
303
|
+
r.valid = true
|
|
304
|
+
return r
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
327
308
|
* Returns a binary array representing the serialized BEEF
|
|
328
309
|
* @returns A binary array representing the BEEF
|
|
329
310
|
*/
|
|
330
|
-
|
|
311
|
+
toBinary (): number[] {
|
|
312
|
+
const writer = new Writer()
|
|
313
|
+
writer.writeUInt32LE(this.magic)
|
|
331
314
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
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
|
-
}
|
|
315
|
+
writer.writeVarIntNum(this.bumps.length)
|
|
316
|
+
for (const b of this.bumps) {
|
|
317
|
+
writer.write(b.toBinary())
|
|
318
|
+
}
|
|
344
319
|
|
|
345
|
-
|
|
320
|
+
writer.writeVarIntNum(this.txs.length)
|
|
321
|
+
for (const tx of this.txs) {
|
|
322
|
+
tx.toWriter(writer, this.magic)
|
|
346
323
|
}
|
|
347
324
|
|
|
348
|
-
|
|
325
|
+
return writer.toArray()
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
349
329
|
* Returns a hex string representing the serialized BEEF
|
|
350
330
|
* @returns A hex string representing the BEEF
|
|
351
331
|
*/
|
|
352
|
-
|
|
353
|
-
|
|
332
|
+
toHex (): string {
|
|
333
|
+
return toHex(this.toBinary())
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
static fromReader (br: Reader): Beef {
|
|
337
|
+
const version = br.readUInt32LE()
|
|
338
|
+
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}`) }
|
|
339
|
+
const beef = new Beef(version === BEEF_MAGIC_V2 ? 'V2' : undefined)
|
|
340
|
+
const bumpsLength = br.readVarIntNum()
|
|
341
|
+
for (let i = 0; i < bumpsLength; i++) {
|
|
342
|
+
const bump = MerklePath.fromReader(br)
|
|
343
|
+
beef.bumps.push(bump)
|
|
354
344
|
}
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
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
|
|
345
|
+
const txsLength = br.readVarIntNum()
|
|
346
|
+
for (let i = 0; i < txsLength; i++) {
|
|
347
|
+
const beefTx = BeefTx.fromReader(br, version)
|
|
348
|
+
beef.txs.push(beefTx)
|
|
372
349
|
}
|
|
350
|
+
return beef
|
|
351
|
+
}
|
|
373
352
|
|
|
374
|
-
|
|
353
|
+
/**
|
|
375
354
|
* Constructs an instance of the Beef class based on the provided binary array
|
|
376
355
|
* @param bin The binary array from which to construct BEEF
|
|
377
356
|
* @returns An instance of the Beef class constructed from the binary data
|
|
378
357
|
*/
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
358
|
+
static fromBinary (bin: number[]): Beef {
|
|
359
|
+
const br = new Reader(bin)
|
|
360
|
+
return Beef.fromReader(br)
|
|
361
|
+
}
|
|
383
362
|
|
|
384
|
-
|
|
363
|
+
/**
|
|
385
364
|
* Constructs an instance of the Beef class based on the provided string
|
|
386
365
|
* @param s The string value from which to construct BEEF
|
|
387
366
|
* @param enc The encoding of the string value from which BEEF should be constructed
|
|
388
367
|
* @returns An instance of the Beef class constructed from the string
|
|
389
368
|
*/
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
369
|
+
static fromString (s: string, enc?: 'hex' | 'utf8' | 'base64'): Beef {
|
|
370
|
+
enc ||= 'hex'
|
|
371
|
+
const bin = toArray(s, enc)
|
|
372
|
+
const br = new Reader(bin)
|
|
373
|
+
return Beef.fromReader(br)
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
398
377
|
* Try to validate newTx.bumpIndex by looking for an existing bump
|
|
399
378
|
* that proves newTx.txid
|
|
400
|
-
*
|
|
379
|
+
*
|
|
401
380
|
* @param newTx A new `BeefTx` that has been added to this.txs
|
|
402
381
|
* @returns true if a bump was found, false otherwise
|
|
403
382
|
*/
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
return false
|
|
383
|
+
private tryToValidateBumpIndex (newTx: BeefTx): boolean {
|
|
384
|
+
if (newTx.bumpIndex !== undefined) { return true }
|
|
385
|
+
const txid = newTx.txid
|
|
386
|
+
for (let i = 0; i < this.bumps.length; i++) {
|
|
387
|
+
const j = this.bumps[i].path[0].findIndex(b => b.hash === txid)
|
|
388
|
+
if (j >= 0) {
|
|
389
|
+
newTx.bumpIndex = i
|
|
390
|
+
this.bumps[i].path[0][j].txid = true
|
|
391
|
+
return true
|
|
392
|
+
}
|
|
417
393
|
}
|
|
394
|
+
return false
|
|
395
|
+
}
|
|
418
396
|
|
|
419
|
-
|
|
397
|
+
/**
|
|
420
398
|
* Sort the `txs` by input txid dependency order.
|
|
421
399
|
* @returns array of input txids of unproven transactions that aren't included in txs.
|
|
422
400
|
*/
|
|
423
|
-
|
|
424
|
-
|
|
401
|
+
sortTxs (): string[] {
|
|
402
|
+
const missingInputs: Record<string, boolean> = {}
|
|
425
403
|
|
|
426
|
-
|
|
404
|
+
const txidToTx: Record<string, BeefTx> = {}
|
|
427
405
|
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
406
|
+
for (const tx of this.txs) {
|
|
407
|
+
txidToTx[tx.txid] = tx
|
|
408
|
+
// All transactions in this beef start at degree zero.
|
|
409
|
+
tx.degree = 0
|
|
410
|
+
}
|
|
433
411
|
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
missingInputs[inputTxid] = true
|
|
442
|
-
}
|
|
443
|
-
}
|
|
412
|
+
for (const tx of this.txs) {
|
|
413
|
+
if (tx.bumpIndex === undefined) {
|
|
414
|
+
// For all the unproven transactions,
|
|
415
|
+
// link their inputs that exist in this beef,
|
|
416
|
+
// make a note of missing inputs.
|
|
417
|
+
for (const inputTxid of tx.inputTxids) {
|
|
418
|
+
if (!txidToTx[inputTxid]) { missingInputs[inputTxid] = true }
|
|
444
419
|
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
445
422
|
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
}
|
|
423
|
+
// queue of transactions that no unsorted transactions depend upon...
|
|
424
|
+
const queue: BeefTx[] = []
|
|
425
|
+
// sorted transactions
|
|
426
|
+
const result: BeefTx[] = []
|
|
427
|
+
|
|
428
|
+
// Increment each txid's degree for every input reference to it by another txid
|
|
429
|
+
for (const tx of this.txs) {
|
|
430
|
+
for (const inputTxid of tx.inputTxids) {
|
|
431
|
+
const tx = txidToTx[inputTxid]
|
|
432
|
+
if (tx) { tx.degree++ }
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
// Since circular dependencies aren't possible, start with the txids no one depends on.
|
|
436
|
+
// These are the transactions that should be sent last...
|
|
437
|
+
for (const tx of this.txs) {
|
|
438
|
+
if (tx.degree === 0) {
|
|
439
|
+
queue.push(tx)
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
// As long as we have transactions to send...
|
|
443
|
+
while (queue.length > 0) {
|
|
444
|
+
const tx = queue.shift()!
|
|
445
|
+
// Add it as new first to send
|
|
446
|
+
result.unshift(tx)
|
|
447
|
+
// And remove its impact on degree
|
|
448
|
+
// noting that any tx achieving a
|
|
449
|
+
// value of zero can be sent...
|
|
450
|
+
for (const inputTxid of tx.inputTxids) {
|
|
451
|
+
const inputTx = txidToTx[inputTxid]
|
|
452
|
+
if (inputTx) {
|
|
453
|
+
inputTx.degree--
|
|
454
|
+
if (inputTx.degree === 0) {
|
|
455
|
+
queue.push(inputTx)
|
|
456
|
+
}
|
|
483
457
|
}
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
return Object.keys(missingInputs)
|
|
458
|
+
}
|
|
487
459
|
}
|
|
460
|
+
this.txs = result
|
|
461
|
+
|
|
462
|
+
return Object.keys(missingInputs)
|
|
463
|
+
}
|
|
488
464
|
|
|
489
|
-
|
|
465
|
+
/**
|
|
490
466
|
* @returns a shallow copy of this beef
|
|
491
467
|
*/
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
468
|
+
clone (): Beef {
|
|
469
|
+
const c = new Beef()
|
|
470
|
+
c.bumps = Array.from(this.bumps)
|
|
471
|
+
c.txs = Array.from(this.txs)
|
|
472
|
+
return c
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
/**
|
|
500
476
|
* Ensure that all the txids in `knownTxids` are txidOnly
|
|
501
|
-
* @param knownTxids
|
|
477
|
+
* @param knownTxids
|
|
502
478
|
*/
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
}
|
|
512
|
-
// TODO: bumps could be trimmed to eliminate unreferenced proofs.
|
|
479
|
+
trimKnownTxids (knownTxids: string[]) {
|
|
480
|
+
for (let i = 0; i < this.txs.length;) {
|
|
481
|
+
const tx = this.txs[i]
|
|
482
|
+
if (tx.isTxidOnly && knownTxids.includes(tx.txid)) {
|
|
483
|
+
this.txs.splice(i, 1)
|
|
484
|
+
} else {
|
|
485
|
+
i++
|
|
486
|
+
}
|
|
513
487
|
}
|
|
488
|
+
// TODO: bumps could be trimmed to eliminate unreferenced proofs.
|
|
489
|
+
}
|
|
514
490
|
|
|
515
|
-
|
|
491
|
+
/**
|
|
516
492
|
* @returns Summary of `Beef` contents as multi-line string.
|
|
517
493
|
*/
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
}
|
|
542
|
-
return log
|
|
494
|
+
toLogString (): string {
|
|
495
|
+
let log = ''
|
|
496
|
+
log += `BEEF with ${this.bumps.length} BUMPS and ${this.txs.length} Transactions, isValid ${this.isValid()}\n`
|
|
497
|
+
let i = -1
|
|
498
|
+
for (const b of this.bumps) {
|
|
499
|
+
i++
|
|
500
|
+
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`
|
|
501
|
+
}
|
|
502
|
+
i = -1
|
|
503
|
+
for (const t of this.txs) {
|
|
504
|
+
i++
|
|
505
|
+
log += ` TX ${i}\n txid: ${t.txid}\n`
|
|
506
|
+
if (t.bumpIndex !== undefined) {
|
|
507
|
+
log += ` bumpIndex: ${t.bumpIndex}\n`
|
|
508
|
+
}
|
|
509
|
+
if (t.isTxidOnly) {
|
|
510
|
+
log += ' txidOnly\n'
|
|
511
|
+
} else {
|
|
512
|
+
log += ` rawTx length=${t.rawTx.length}\n`
|
|
513
|
+
}
|
|
514
|
+
if (t.inputTxids.length > 0) {
|
|
515
|
+
log += ` inputs: [\n${t.inputTxids.map(it => ` '${it}'`).join(',\n')}\n ]\n`
|
|
516
|
+
}
|
|
543
517
|
}
|
|
518
|
+
return log
|
|
519
|
+
}
|
|
544
520
|
}
|
|
545
521
|
|
|
546
|
-
export default Beef
|
|
522
|
+
export default Beef
|