@bsv/sdk 1.2.15 → 1.2.18
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 +2 -2
- package/dist/cjs/src/primitives/BigNumber.js +85 -89
- package/dist/cjs/src/primitives/BigNumber.js.map +1 -1
- package/dist/cjs/src/primitives/PublicKey.js +5 -2
- package/dist/cjs/src/primitives/PublicKey.js.map +1 -1
- package/dist/cjs/src/primitives/Random.js +3 -2
- package/dist/cjs/src/primitives/Random.js.map +1 -1
- package/dist/cjs/src/primitives/utils.js +71 -62
- package/dist/cjs/src/primitives/utils.js.map +1 -1
- package/dist/cjs/src/totp/totp.js +0 -1
- package/dist/cjs/src/totp/totp.js.map +1 -1
- package/dist/cjs/src/transaction/Beef.js +61 -49
- package/dist/cjs/src/transaction/Beef.js.map +1 -1
- package/dist/cjs/src/transaction/BeefParty.js +1 -1
- package/dist/cjs/src/transaction/BeefTx.js +75 -73
- package/dist/cjs/src/transaction/BeefTx.js.map +1 -1
- package/dist/cjs/src/transaction/MerklePath.js +4 -4
- package/dist/cjs/src/transaction/MerklePath.js.map +1 -1
- package/dist/cjs/src/transaction/Transaction.js +70 -96
- package/dist/cjs/src/transaction/Transaction.js.map +1 -1
- package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/esm/src/primitives/BigNumber.js +85 -89
- package/dist/esm/src/primitives/BigNumber.js.map +1 -1
- package/dist/esm/src/primitives/PublicKey.js +5 -2
- package/dist/esm/src/primitives/PublicKey.js.map +1 -1
- package/dist/esm/src/primitives/Random.js +2 -2
- package/dist/esm/src/primitives/Random.js.map +1 -1
- package/dist/esm/src/primitives/utils.js +70 -61
- package/dist/esm/src/primitives/utils.js.map +1 -1
- package/dist/esm/src/totp/totp.js +0 -1
- package/dist/esm/src/totp/totp.js.map +1 -1
- package/dist/esm/src/transaction/Beef.js +60 -48
- package/dist/esm/src/transaction/Beef.js.map +1 -1
- package/dist/esm/src/transaction/BeefParty.js +1 -1
- package/dist/esm/src/transaction/BeefTx.js +76 -74
- package/dist/esm/src/transaction/BeefTx.js.map +1 -1
- package/dist/esm/src/transaction/MerklePath.js +4 -4
- package/dist/esm/src/transaction/MerklePath.js.map +1 -1
- package/dist/esm/src/transaction/Transaction.js +71 -97
- package/dist/esm/src/transaction/Transaction.js.map +1 -1
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
- package/dist/types/src/primitives/BigNumber.d.ts +24 -22
- package/dist/types/src/primitives/BigNumber.d.ts.map +1 -1
- package/dist/types/src/primitives/PublicKey.d.ts.map +1 -1
- package/dist/types/src/primitives/utils.d.ts +17 -17
- package/dist/types/src/primitives/utils.d.ts.map +1 -1
- package/dist/types/src/transaction/Beef.d.ts +17 -14
- package/dist/types/src/transaction/Beef.d.ts.map +1 -1
- package/dist/types/src/transaction/BeefParty.d.ts +1 -1
- package/dist/types/src/transaction/BeefTx.d.ts +5 -2
- package/dist/types/src/transaction/BeefTx.d.ts.map +1 -1
- package/dist/types/src/transaction/MerklePath.d.ts +2 -2
- package/dist/types/src/transaction/MerklePath.d.ts.map +1 -1
- package/dist/types/src/transaction/Transaction.d.ts +6 -0
- 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/compat.md +13 -11
- package/docs/primitives.md +152 -188
- package/docs/transaction.md +87 -79
- package/docs/wallet-substrates.md +1410 -1
- package/package.json +2 -2
- package/src/primitives/BigNumber.ts +111 -111
- package/src/primitives/PublicKey.ts +5 -2
- package/src/primitives/Random.ts +2 -2
- package/src/primitives/utils.ts +92 -77
- package/src/totp/totp.ts +0 -1
- package/src/transaction/Beef.ts +60 -42
- package/src/transaction/BeefParty.ts +1 -1
- package/src/transaction/BeefTx.ts +89 -57
- package/src/transaction/MerklePath.ts +4 -4
- package/src/transaction/Transaction.ts +77 -100
- package/src/transaction/__tests/Beef.test.ts +32 -13
- package/src/transaction/__tests/Transaction.benchmarks.test.ts +1 -1
- package/src/transaction/__tests/Transaction.test.ts +3 -3
- package/src/transaction/broadcasters/__tests/WhatsOnChainBroadcaster.test.ts +2 -2
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { hash256 } from '../primitives/Hash.js'
|
|
2
2
|
import { Reader, Writer, toHex, toArray } from '../primitives/utils.js'
|
|
3
3
|
import Transaction from './Transaction.js'
|
|
4
|
-
import {
|
|
4
|
+
import { BEEF_V2, TX_DATA_FORMAT } from './Beef.js'
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* A single bitcoin transaction associated with a `Beef` validity proof set.
|
|
@@ -42,20 +42,32 @@ export default class BeefTx {
|
|
|
42
42
|
|
|
43
43
|
get txid () {
|
|
44
44
|
if (this._txid) return this._txid
|
|
45
|
-
if (this._tx)
|
|
46
|
-
|
|
45
|
+
if (this._tx) {
|
|
46
|
+
this._txid = this._tx.id('hex')
|
|
47
|
+
return this._txid
|
|
48
|
+
}
|
|
49
|
+
if (this._rawTx) {
|
|
50
|
+
this._txid = toHex(hash256(this._rawTx))
|
|
51
|
+
return this._txid
|
|
52
|
+
}
|
|
47
53
|
throw new Error('Internal')
|
|
48
54
|
}
|
|
49
55
|
|
|
50
56
|
get tx () {
|
|
51
57
|
if (this._tx) return this._tx
|
|
52
|
-
if (this._rawTx)
|
|
58
|
+
if (this._rawTx) {
|
|
59
|
+
this._tx = Transaction.fromBinary(this._rawTx)
|
|
60
|
+
return this._tx
|
|
61
|
+
}
|
|
53
62
|
return undefined
|
|
54
63
|
}
|
|
55
64
|
|
|
56
65
|
get rawTx () {
|
|
57
66
|
if (this._rawTx) return this._rawTx
|
|
58
|
-
if (this._tx)
|
|
67
|
+
if (this._tx) {
|
|
68
|
+
this._rawTx = this._tx.toBinary()
|
|
69
|
+
return this._rawTx
|
|
70
|
+
}
|
|
59
71
|
return undefined
|
|
60
72
|
}
|
|
61
73
|
|
|
@@ -66,87 +78,107 @@ export default class BeefTx {
|
|
|
66
78
|
constructor (tx: Transaction | number[] | string, bumpIndex?: number) {
|
|
67
79
|
if (typeof tx === 'string') {
|
|
68
80
|
this._txid = tx
|
|
81
|
+
} else if (Array.isArray(tx)) {
|
|
82
|
+
this._rawTx = tx
|
|
69
83
|
} else {
|
|
70
|
-
|
|
71
|
-
this._rawTx = tx
|
|
72
|
-
} else {
|
|
73
|
-
this._tx = tx
|
|
74
|
-
}
|
|
84
|
+
this._tx = tx
|
|
75
85
|
}
|
|
76
86
|
this.bumpIndex = bumpIndex
|
|
77
87
|
this.updateInputTxids()
|
|
78
88
|
}
|
|
79
89
|
|
|
90
|
+
static fromTx (tx: Transaction, bumpIndex?: number): BeefTx {
|
|
91
|
+
return new BeefTx(tx, bumpIndex)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
static fromRawTx (rawTx: number[], bumpIndex?: number): BeefTx {
|
|
95
|
+
return new BeefTx(rawTx, bumpIndex)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
static fromTxid (txid: string, bumpIndex?: number): BeefTx {
|
|
99
|
+
return new BeefTx(txid, bumpIndex)
|
|
100
|
+
}
|
|
101
|
+
|
|
80
102
|
private updateInputTxids () {
|
|
81
|
-
if (this.hasProof || !this.tx)
|
|
82
|
-
|
|
83
|
-
|
|
103
|
+
if (this.hasProof || !this.tx) {
|
|
104
|
+
// If we have a proof, or don't have a parsed transaction
|
|
105
|
+
this.inputTxids = []
|
|
106
|
+
} else {
|
|
84
107
|
const inputTxids = {}
|
|
85
108
|
for (const input of this.tx.inputs) { inputTxids[input.sourceTXID] = true }
|
|
86
109
|
this.inputTxids = Object.keys(inputTxids)
|
|
87
110
|
}
|
|
88
111
|
}
|
|
89
112
|
|
|
90
|
-
toWriter (writer: Writer,
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
113
|
+
toWriter (writer: Writer, version: number): void {
|
|
114
|
+
const writeByte = (bb: number) => {
|
|
115
|
+
writer.writeUInt8(bb)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const writeTxid = () => {
|
|
119
|
+
writer.writeReverse(toArray(this._txid, 'hex'))
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const writeTx = () => {
|
|
123
|
+
if (this._rawTx) {
|
|
124
|
+
writer.write(this._rawTx)
|
|
125
|
+
} else if (this._tx) {
|
|
126
|
+
writer.write(this._tx.toBinary())
|
|
127
|
+
} else {
|
|
128
|
+
throw new Error('a valid serialized Transaction is expected')
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const writeBumpIndex = () => {
|
|
98
133
|
if (this.bumpIndex === undefined) {
|
|
99
|
-
|
|
134
|
+
writeByte(TX_DATA_FORMAT.RAWTX) // 0
|
|
100
135
|
} else {
|
|
101
|
-
|
|
102
|
-
writer.writeVarIntNum(this.bumpIndex)
|
|
136
|
+
writeByte(TX_DATA_FORMAT.RAWTX_AND_BUMP_INDEX) // 1
|
|
137
|
+
writer.writeVarIntNum(this.bumpIndex) // the index of the associated bump
|
|
103
138
|
}
|
|
104
|
-
}
|
|
105
|
-
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (version === BEEF_V2) {
|
|
106
142
|
if (this.isTxidOnly) {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
143
|
+
writeByte(TX_DATA_FORMAT.TXID_ONLY)
|
|
144
|
+
writeTxid()
|
|
145
|
+
} else if (this.bumpIndex !== undefined) {
|
|
146
|
+
writeByte(TX_DATA_FORMAT.RAWTX_AND_BUMP_INDEX)
|
|
147
|
+
writer.writeVarIntNum(this.bumpIndex)
|
|
148
|
+
writeTx()
|
|
110
149
|
} else {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
} else {
|
|
114
|
-
writer.writeUInt8(1)
|
|
115
|
-
writer.writeVarIntNum(this.bumpIndex)
|
|
116
|
-
}
|
|
117
|
-
if (this._rawTx) { writer.write(this._rawTx) } else if (this._tx) { writer.write(this._tx.toBinary()) } else { throw new Error('a valid serialized Transaction is expected') }
|
|
150
|
+
writeByte(TX_DATA_FORMAT.RAWTX)
|
|
151
|
+
writeTx()
|
|
118
152
|
}
|
|
153
|
+
} else {
|
|
154
|
+
writeTx()
|
|
155
|
+
writeBumpIndex()
|
|
119
156
|
}
|
|
120
157
|
}
|
|
121
158
|
|
|
122
|
-
static fromReader (br: Reader,
|
|
123
|
-
let
|
|
159
|
+
static fromReader (br: Reader, version: number): BeefTx {
|
|
160
|
+
let data: Transaction | number[] | string | undefined
|
|
124
161
|
let bumpIndex: number | undefined
|
|
125
|
-
|
|
126
|
-
if (
|
|
127
|
-
// V1
|
|
128
|
-
const version = br.readUInt32LE()
|
|
129
|
-
if (version === BEEF_MAGIC_TXID_ONLY_EXTENSION) {
|
|
130
|
-
// This is the extension to support known transactions
|
|
131
|
-
tx = toHex(br.readReverse(32))
|
|
132
|
-
} else {
|
|
133
|
-
br.pos -= 4 // Unread the version...
|
|
134
|
-
tx = Transaction.fromReader(br)
|
|
135
|
-
}
|
|
136
|
-
bumpIndex = br.readUInt8() ? br.readVarIntNum() : undefined
|
|
137
|
-
} else {
|
|
162
|
+
let beefTx: BeefTx | undefined
|
|
163
|
+
if (version === BEEF_V2) {
|
|
138
164
|
// V2
|
|
139
165
|
const format = br.readUInt8()
|
|
140
|
-
if (format ===
|
|
141
|
-
|
|
142
|
-
tx = toHex(br.readReverse(32))
|
|
166
|
+
if (format === TX_DATA_FORMAT.TXID_ONLY) {
|
|
167
|
+
beefTx = BeefTx.fromTxid(toHex(br.readReverse(32)))
|
|
143
168
|
} else {
|
|
144
|
-
if (format ===
|
|
145
|
-
|
|
169
|
+
if (format === TX_DATA_FORMAT.RAWTX_AND_BUMP_INDEX) {
|
|
170
|
+
bumpIndex = br.readVarIntNum()
|
|
171
|
+
}
|
|
172
|
+
data = Transaction.fromReader(br)
|
|
173
|
+
beefTx = BeefTx.fromTx(data, bumpIndex)
|
|
146
174
|
}
|
|
175
|
+
} else {
|
|
176
|
+
// V1
|
|
177
|
+
data = Transaction.fromReader(br)
|
|
178
|
+
bumpIndex = br.readUInt8() ? br.readVarIntNum() : undefined
|
|
179
|
+
beefTx = BeefTx.fromTx(data, bumpIndex)
|
|
147
180
|
}
|
|
148
181
|
|
|
149
|
-
const beefTx = new BeefTx(tx, bumpIndex)
|
|
150
182
|
return beefTx
|
|
151
183
|
}
|
|
152
184
|
}
|
|
@@ -50,7 +50,7 @@ export default class MerklePath {
|
|
|
50
50
|
return MerklePath.fromBinary(toArray(hex, 'hex'))
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
static fromReader (reader: Reader): MerklePath {
|
|
53
|
+
static fromReader (reader: Reader, legalOffsetsOnly: boolean = true): MerklePath {
|
|
54
54
|
const blockHeight = reader.readVarIntNum()
|
|
55
55
|
const treeHeight = reader.readUInt8()
|
|
56
56
|
const path = Array(treeHeight).fill(0).map(() => ([]))
|
|
@@ -79,7 +79,7 @@ export default class MerklePath {
|
|
|
79
79
|
}
|
|
80
80
|
path[level].sort((a, b) => a.offset - b.offset)
|
|
81
81
|
}
|
|
82
|
-
return new MerklePath(blockHeight, path)
|
|
82
|
+
return new MerklePath(blockHeight, path, legalOffsetsOnly)
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
/**
|
|
@@ -114,7 +114,7 @@ export default class MerklePath {
|
|
|
114
114
|
hash?: string
|
|
115
115
|
txid?: boolean
|
|
116
116
|
duplicate?: boolean
|
|
117
|
-
}
|
|
117
|
+
}>>, legalOffsetsOnly: boolean = true) {
|
|
118
118
|
this.blockHeight = blockHeight
|
|
119
119
|
this.path = path
|
|
120
120
|
|
|
@@ -135,7 +135,7 @@ export default class MerklePath {
|
|
|
135
135
|
}
|
|
136
136
|
}
|
|
137
137
|
} else {
|
|
138
|
-
if (!legalOffsets[height].has(leaf.offset)) {
|
|
138
|
+
if (legalOffsetsOnly && !legalOffsets[height].has(leaf.offset)) {
|
|
139
139
|
throw new Error(`Invalid offset: ${leaf.offset}, at height: ${height}, with legal offsets: ${Array.from(legalOffsets[height]).join(', ')}`)
|
|
140
140
|
}
|
|
141
141
|
}
|
|
@@ -12,7 +12,7 @@ import Spend from '../script/Spend.js'
|
|
|
12
12
|
import ChainTracker from './ChainTracker.js'
|
|
13
13
|
import { defaultBroadcaster } from './broadcasters/DefaultBroadcaster.js'
|
|
14
14
|
import { defaultChainTracker } from './chaintrackers/DefaultChainTracker.js'
|
|
15
|
-
import { ATOMIC_BEEF,
|
|
15
|
+
import { ATOMIC_BEEF, BEEF_V1 } from './Beef.js'
|
|
16
16
|
import P2PKH from '../script/templates/P2PKH.js'
|
|
17
17
|
|
|
18
18
|
/**
|
|
@@ -59,6 +59,29 @@ export default class Transaction {
|
|
|
59
59
|
merklePath?: MerklePath
|
|
60
60
|
private cachedHash?: number[]
|
|
61
61
|
|
|
62
|
+
// Recursive function for adding merkle proofs or input transactions
|
|
63
|
+
private static addPathOrInputs (obj: { pathIndex?: number, tx: Transaction }, transactions: Record<string, {
|
|
64
|
+
pathIndex?: number
|
|
65
|
+
tx: Transaction
|
|
66
|
+
}>, BUMPs: MerklePath[]): void {
|
|
67
|
+
if (typeof obj.pathIndex === 'number') {
|
|
68
|
+
const path = BUMPs[obj.pathIndex]
|
|
69
|
+
if (typeof path !== 'object') {
|
|
70
|
+
throw new Error('Invalid merkle path index found in BEEF!')
|
|
71
|
+
}
|
|
72
|
+
obj.tx.merklePath = path
|
|
73
|
+
} else {
|
|
74
|
+
for (const input of obj.tx.inputs) {
|
|
75
|
+
const sourceObj = transactions[input.sourceTXID]
|
|
76
|
+
if (typeof sourceObj !== 'object') {
|
|
77
|
+
throw new Error(`Reference to unknown TXID in BEEF: ${input.sourceTXID}`)
|
|
78
|
+
}
|
|
79
|
+
input.sourceTransaction = sourceObj.tx
|
|
80
|
+
this.addPathOrInputs(sourceObj, transactions, BUMPs)
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
62
85
|
/**
|
|
63
86
|
* Creates a new transaction, linked to its inputs and their associated merkle paths, from a BEEF (BRC-62) structure.
|
|
64
87
|
* Optionally, you can provide a specific TXID to retrieve a particular transaction from the BEEF data.
|
|
@@ -81,28 +104,7 @@ export default class Transaction {
|
|
|
81
104
|
throw new Error(`Transaction with TXID ${targetTXID} not found in BEEF data.`)
|
|
82
105
|
}
|
|
83
106
|
|
|
84
|
-
|
|
85
|
-
const addPathOrInputs = (obj: { pathIndex?: number, tx: Transaction }): void => {
|
|
86
|
-
if (typeof obj.pathIndex === 'number') {
|
|
87
|
-
const path = BUMPs[obj.pathIndex]
|
|
88
|
-
if (typeof path !== 'object') {
|
|
89
|
-
throw new Error('Invalid merkle path index found in BEEF!')
|
|
90
|
-
}
|
|
91
|
-
obj.tx.merklePath = path
|
|
92
|
-
} else {
|
|
93
|
-
for (let i = 0; i < obj.tx.inputs.length; i++) {
|
|
94
|
-
const input = obj.tx.inputs[i]
|
|
95
|
-
const sourceObj = transactions[input.sourceTXID]
|
|
96
|
-
if (typeof sourceObj !== 'object') {
|
|
97
|
-
throw new Error(`Reference to unknown TXID in BEEF: ${input.sourceTXID}`)
|
|
98
|
-
}
|
|
99
|
-
input.sourceTransaction = sourceObj.tx
|
|
100
|
-
addPathOrInputs(sourceObj)
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
addPathOrInputs(transactions[targetTXID])
|
|
107
|
+
this.addPathOrInputs(transactions[targetTXID], transactions, BUMPs)
|
|
106
108
|
|
|
107
109
|
return transactions[targetTXID].tx
|
|
108
110
|
}
|
|
@@ -172,28 +174,7 @@ export default class Transaction {
|
|
|
172
174
|
throw new Error(`Unrelated transaction with TXID ${txid} found in Atomic BEEF data.`)
|
|
173
175
|
}
|
|
174
176
|
|
|
175
|
-
|
|
176
|
-
const addPathOrInputs = (obj: { pathIndex?: number, tx: Transaction }): void => {
|
|
177
|
-
if (typeof obj.pathIndex === 'number') {
|
|
178
|
-
const path = BUMPs[obj.pathIndex]
|
|
179
|
-
if (typeof path !== 'object') {
|
|
180
|
-
throw new Error('Invalid merkle path index found in BEEF!')
|
|
181
|
-
}
|
|
182
|
-
obj.tx.merklePath = path
|
|
183
|
-
} else {
|
|
184
|
-
for (let i = 0; i < obj.tx.inputs.length; i++) {
|
|
185
|
-
const input = obj.tx.inputs[i]
|
|
186
|
-
const sourceObj = transactions[input.sourceTXID]
|
|
187
|
-
if (typeof sourceObj !== 'object') {
|
|
188
|
-
throw new Error(`Reference to unknown TXID in BEEF: ${input.sourceTXID}`)
|
|
189
|
-
}
|
|
190
|
-
input.sourceTransaction = sourceObj.tx
|
|
191
|
-
addPathOrInputs(sourceObj)
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
addPathOrInputs(transactions[subjectTXID])
|
|
177
|
+
this.addPathOrInputs(transactions[subjectTXID], transactions, BUMPs)
|
|
197
178
|
|
|
198
179
|
return transactions[subjectTXID].tx
|
|
199
180
|
}
|
|
@@ -207,8 +188,8 @@ export default class Transaction {
|
|
|
207
188
|
private static parseBEEFData (reader: Reader): { transactions: Record<string, { pathIndex?: number, tx: Transaction }>, BUMPs: MerklePath[] } {
|
|
208
189
|
// Read the version
|
|
209
190
|
const version = reader.readUInt32LE()
|
|
210
|
-
if (version !==
|
|
211
|
-
throw new Error(`Invalid BEEF version. Expected ${
|
|
191
|
+
if (version !== BEEF_V1) {
|
|
192
|
+
throw new Error(`Invalid BEEF version. Expected ${BEEF_V1}, received ${version}.`)
|
|
212
193
|
}
|
|
213
194
|
|
|
214
195
|
// Read the BUMPs
|
|
@@ -511,7 +492,15 @@ export default class Transaction {
|
|
|
511
492
|
}
|
|
512
493
|
}
|
|
513
494
|
const fee = await modelOrFee.computeFee(this)
|
|
514
|
-
|
|
495
|
+
const change = this.calculateChange(fee)
|
|
496
|
+
if (change <= 0) {
|
|
497
|
+
this.outputs = this.outputs.filter(output => !output.change)
|
|
498
|
+
return
|
|
499
|
+
}
|
|
500
|
+
this.distributeChange(change, changeDistribution)
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
private calculateChange (fee: number): number {
|
|
515
504
|
let change = 0
|
|
516
505
|
for (const input of this.inputs) {
|
|
517
506
|
if (typeof input.sourceTransaction !== 'object') {
|
|
@@ -520,71 +509,60 @@ export default class Transaction {
|
|
|
520
509
|
change += input.sourceTransaction.outputs[input.sourceOutputIndex].satoshis
|
|
521
510
|
}
|
|
522
511
|
change -= fee
|
|
523
|
-
let changeCount = 0
|
|
524
512
|
for (const out of this.outputs) {
|
|
525
513
|
if (!out.change) {
|
|
526
514
|
change -= out.satoshis
|
|
527
|
-
} else {
|
|
528
|
-
changeCount++
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
if (change <= changeCount) {
|
|
533
|
-
// There is not enough change to distribute among the change outputs.
|
|
534
|
-
// We'll remove all change outputs and leave the extra for the miners.
|
|
535
|
-
for (let i = 0; i < this.outputs.length; i++) {
|
|
536
|
-
if (this.outputs[i].change) {
|
|
537
|
-
this.outputs.splice(i, 1)
|
|
538
|
-
i--
|
|
539
|
-
}
|
|
540
515
|
}
|
|
541
|
-
return
|
|
542
516
|
}
|
|
517
|
+
return change
|
|
518
|
+
}
|
|
543
519
|
|
|
544
|
-
|
|
520
|
+
private distributeChange (change: number, changeDistribution: 'equal' | 'random'): void {
|
|
545
521
|
let distributedChange = 0
|
|
522
|
+
const changeOutputs = this.outputs.filter(out => out.change)
|
|
546
523
|
if (changeDistribution === 'random') {
|
|
547
|
-
|
|
548
|
-
const changeOutputs = this.outputs.filter(out => out.change)
|
|
549
|
-
let changeToUse = change
|
|
550
|
-
|
|
551
|
-
// Helper function to generate a number approximating Benford's Law
|
|
552
|
-
const benfordNumber = (min: number, max: number): number => {
|
|
553
|
-
const d = Math.floor(Math.random() * 9) + 1
|
|
554
|
-
return Math.floor(min + (max - min) * Math.log10(1 + 1 / d) / Math.log10(10))
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
const benfordNumbers = Array(changeOutputs.length).fill(1)
|
|
558
|
-
changeToUse -= changeOutputs.length
|
|
559
|
-
distributedChange += changeOutputs.length
|
|
560
|
-
for (let i = 0; i < changeOutputs.length - 1; i++) {
|
|
561
|
-
const portion = benfordNumber(0, changeToUse)
|
|
562
|
-
benfordNumbers[i] += portion
|
|
563
|
-
distributedChange += portion
|
|
564
|
-
changeToUse -= portion
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
for (let i = 0; i < this.outputs.length; i++) {
|
|
568
|
-
if (this.outputs[i].change) {
|
|
569
|
-
const t = benfordNumbers.shift()
|
|
570
|
-
this.outputs[i].satoshis = t
|
|
571
|
-
}
|
|
572
|
-
}
|
|
524
|
+
distributedChange = this.distributeRandomChange(change, changeOutputs)
|
|
573
525
|
} else if (changeDistribution === 'equal') {
|
|
574
|
-
|
|
575
|
-
for (const out of this.outputs) {
|
|
576
|
-
if (out.change) {
|
|
577
|
-
distributedChange += perOutput
|
|
578
|
-
out.satoshis = perOutput
|
|
579
|
-
}
|
|
580
|
-
}
|
|
526
|
+
distributedChange = this.distributeEqualChange(change, changeOutputs)
|
|
581
527
|
}
|
|
582
|
-
// if there's any remaining change, add it to the last output
|
|
583
528
|
if (distributedChange < change) {
|
|
584
529
|
this.outputs[this.outputs.length - 1].satoshis += (change - distributedChange)
|
|
585
530
|
}
|
|
586
531
|
}
|
|
587
532
|
|
|
533
|
+
private distributeRandomChange (change: number, changeOutputs: TransactionOutput[]): number {
|
|
534
|
+
let distributedChange = 0
|
|
535
|
+
let changeToUse = change
|
|
536
|
+
const benfordNumbers = Array(changeOutputs.length).fill(1)
|
|
537
|
+
changeToUse -= changeOutputs.length
|
|
538
|
+
distributedChange += changeOutputs.length
|
|
539
|
+
for (let i = 0; i < changeOutputs.length - 1; i++) {
|
|
540
|
+
const portion = this.benfordNumber(0, changeToUse)
|
|
541
|
+
benfordNumbers[i] += portion
|
|
542
|
+
distributedChange += portion
|
|
543
|
+
changeToUse -= portion
|
|
544
|
+
}
|
|
545
|
+
for (const output of this.outputs) {
|
|
546
|
+
if (output.change) output.satoshis = benfordNumbers.shift()
|
|
547
|
+
}
|
|
548
|
+
return distributedChange
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
private distributeEqualChange (change: number, changeOutputs: TransactionOutput[]): number {
|
|
552
|
+
let distributedChange = 0
|
|
553
|
+
const perOutput = Math.floor(change / changeOutputs.length)
|
|
554
|
+
for (const out of changeOutputs) {
|
|
555
|
+
distributedChange += perOutput
|
|
556
|
+
out.satoshis = perOutput
|
|
557
|
+
}
|
|
558
|
+
return distributedChange
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
private benfordNumber (min: number, max: number): number {
|
|
562
|
+
const d = Math.floor(Math.random() * 9) + 1
|
|
563
|
+
return Math.floor(min + (max - min) * Math.log10(1 + 1 / d) / Math.log10(10))
|
|
564
|
+
}
|
|
565
|
+
|
|
588
566
|
/**
|
|
589
567
|
* Utility method that returns the current fee based on inputs and outputs
|
|
590
568
|
*
|
|
@@ -923,9 +901,8 @@ export default class Transaction {
|
|
|
923
901
|
* @throws Error if there are any missing sourceTransactions unless `allowPartial` is true.
|
|
924
902
|
*/
|
|
925
903
|
toBEEF (allowPartial?: boolean): number[] {
|
|
926
|
-
this.outputs.length
|
|
927
904
|
const writer = new Writer()
|
|
928
|
-
writer.writeUInt32LE(
|
|
905
|
+
writer.writeUInt32LE(BEEF_V1)
|
|
929
906
|
const BUMPs: MerklePath[] = []
|
|
930
907
|
const txs: Array<{ tx: Transaction, pathIndex?: number }> = []
|
|
931
908
|
|
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
// The following imports allow flag a large number type checking errors by the VsCode editor due to missing type information:
|
|
2
2
|
import BeefTx from "../../../dist/cjs/src/transaction/BeefTx"
|
|
3
|
-
import Beef from "../../../dist/cjs/src/transaction/Beef"
|
|
4
3
|
import BeefParty from "../../../dist/cjs/src/transaction/BeefParty"
|
|
5
|
-
import {
|
|
4
|
+
import Beef, { BEEF_V1, BEEF_V2 } from "../../../dist/cjs/src/transaction/Beef"
|
|
6
5
|
import Transaction from "../../../dist/cjs/src/transaction/Transaction"
|
|
6
|
+
import { fromBase58 } from '../../../dist/cjs/src/primitives/utils'
|
|
7
7
|
|
|
8
8
|
// The following imports allow full type checking by the VsCode editor, but tests will fail to run:
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
/*
|
|
10
|
+
import BeefTx from '../BeefTx'
|
|
11
|
+
import Beef from '../Beef'
|
|
12
|
+
import BeefParty from "../BeefParty"
|
|
13
|
+
import { BEEF_V1, BEEF_V2 } from "../Beef"
|
|
14
|
+
import Transaction from "../Transaction"
|
|
15
|
+
import { fromBase58 } from "../../primitives/utils"
|
|
16
|
+
*/
|
|
14
17
|
|
|
15
18
|
describe('Beef tests', () => {
|
|
16
19
|
jest.setTimeout(99999999)
|
|
@@ -19,6 +22,7 @@ describe('Beef tests', () => {
|
|
|
19
22
|
isValidRootForHeight: async (root: string, height: number) => {
|
|
20
23
|
switch (height) {
|
|
21
24
|
case 1631619: return root === "b3975a6b69b5ce7fa200649d879f79a11f4d95c054cfe024570be7d60306ecf6"
|
|
25
|
+
case 875732: return root === "a19c54129ab996c72cda7721b4555b47d11b21e1fe67aa63c59843edb302b6c2"
|
|
22
26
|
default: throw new Error(`unknown height ${height}`)
|
|
23
27
|
}
|
|
24
28
|
}
|
|
@@ -97,7 +101,7 @@ describe('Beef tests', () => {
|
|
|
97
101
|
{
|
|
98
102
|
const version = 4290641921
|
|
99
103
|
expect(() => Beef.fromString(beefs[1]))
|
|
100
|
-
.toThrow(`Serialized BEEF must start with ${
|
|
104
|
+
.toThrow(`Serialized BEEF must start with ${BEEF_V1} or ${BEEF_V2} but starts with ${version}`)
|
|
101
105
|
}
|
|
102
106
|
})
|
|
103
107
|
|
|
@@ -258,7 +262,7 @@ describe('Beef tests', () => {
|
|
|
258
262
|
{
|
|
259
263
|
const beef = Beef.fromString(beefs[0])
|
|
260
264
|
expect(beef.toHex()).toBe(beefs[0])
|
|
261
|
-
|
|
265
|
+
beef.sortTxs()
|
|
262
266
|
const beefHex = beef.toHex()
|
|
263
267
|
const tx = beef.txs[beef.txs.length - 1].tx!
|
|
264
268
|
expect(tx).toBeTruthy()
|
|
@@ -280,7 +284,7 @@ describe('Beef tests', () => {
|
|
|
280
284
|
const beef = Beef.fromString(beefs[0])
|
|
281
285
|
expect(beef.toHex()).toBe(beefs[0])
|
|
282
286
|
beef.mergeTransaction(Transaction.fromHex(txs[0]))
|
|
283
|
-
|
|
287
|
+
beef.sortTxs()
|
|
284
288
|
const beefHex = beef.toHex()
|
|
285
289
|
const tx = beef.txs[beef.txs.length - 1].tx!
|
|
286
290
|
expect(tx).toBeTruthy()
|
|
@@ -297,8 +301,8 @@ describe('Beef tests', () => {
|
|
|
297
301
|
const beef = Beef.fromString(beefs[0])
|
|
298
302
|
const tx = Transaction.fromHex(txs[0])
|
|
299
303
|
beef.mergeTransaction(tx)
|
|
300
|
-
|
|
301
|
-
|
|
304
|
+
beef.sortTxs()
|
|
305
|
+
beef.toLogString()
|
|
302
306
|
const atomic = beef.toBinaryAtomic(tx.id('hex'))
|
|
303
307
|
const t2 = Transaction.fromAtomicBEEF(atomic)
|
|
304
308
|
const beef2 = t2.toAtomicBEEF()
|
|
@@ -317,14 +321,29 @@ describe('Beef tests', () => {
|
|
|
317
321
|
{
|
|
318
322
|
const beef = new Beef()
|
|
319
323
|
const atx = beef.mergeTxidOnly('a')
|
|
320
|
-
|
|
324
|
+
beef.mergeTxidOnly('b')
|
|
321
325
|
atx.inputTxids = ['b']
|
|
322
326
|
beef.sortTxs()
|
|
323
327
|
expect(beef.txs[1].txid).toBe('a')
|
|
324
328
|
}
|
|
325
329
|
})
|
|
330
|
+
|
|
331
|
+
test('10_deserialize beef with extra leaves', async () => {
|
|
332
|
+
const b58Beef = b58Beef_10
|
|
333
|
+
|
|
334
|
+
const beef = Beef.fromBinary(fromBase58(b58Beef))
|
|
335
|
+
expect(beef.verifyValid().valid).toBe(true)
|
|
336
|
+
const abeef = beef.toBinaryAtomic(beef.txs[beef.txs.length -1].txid)
|
|
337
|
+
const pbeef = Beef.fromBinary(abeef)
|
|
338
|
+
pbeef.addComputedLeaves();
|
|
339
|
+
const pbeefBinary = pbeef.toBinary();
|
|
340
|
+
expect(pbeefBinary).toBeTruthy()
|
|
341
|
+
expect(pbeef.verifyValid().valid).toBe(true)
|
|
342
|
+
})
|
|
326
343
|
})
|
|
327
344
|
|
|
345
|
+
const b58Beef_10 = 'gno9MC7VXii1KoCkc2nsVyYJpqzN3dhBzYATETJcys62emMKfpBof4R7GozwYEaSapUtnNvqQ57aaYYjm3U2dv9eUJ1sV46boHkQgppYmAz9YH8FdZduV8aJayPViaKcyPmbDhEw6UW8TM5iFZLXNs7HBnJHUKCeTdNK4FUEL7vAugxAV9WUUZ43BZjJk2SmSeps9TCXjt1Ci9fKWp3d9QSoYvTpxwzyUFHjRKtbUgwq55ZfkBp5bV2Bpz9qSuKywKewW7Hh4S1nCUScwwzpKDozb3zic1V9p2k8rQxoPsRxjUJ8bjhNDdsN8d7KukFuc3n47fXzdWttvnxwsujLJRGnQbgJuknQqx3KLf5kJXHzwjG6TzigZk2t24qeB6d3hbYiaDr2fFkUJBL3tukTHhfNkQYRXuz3kucVDzvejHyqJaF51mXG8BjMN5aQj91ZJXCaPVqkMWCzmvyaqmXMdRiJdSAynhXbQK91xf6RwdNhz1tg5f9B6oJJMhsi9UYSVymmax8VLKD9AKzBCBDcfyD83m3jyS1VgKGZn3SkQmr6bsoWq88L3GsMnnmYUGogvdAYarTqg3pzkjCMxHzmJBMN6ofnUk8c1sRTXQue7BbyUaN5uZu3KW6CmFsEfpuqVvnqFW93TU1jrPP2S8yz8AexAnARPCKE8Yz7RfVaT6RCavwQKL3u5iookwRWEZXW1QWmM37yJWHD87SjVynyg327a1CLwcBxmE2CB48QeNVGyQki4CTQMqw2o8TMhDPJej1g68oniAjBcxBLSCs7KGvK3k7AfrHbCMULX9CTibYhCjdFjbsbBoocqJpxxcvkMo1fEEiAzZuiBVZQDYktDdTVbhKHvYkW25HcYX75NJrpNAhm7AjFeKLzEVxqAQkMfvTufpESNRZF4kQqg2Rg8h2ajcKTd5cpEPwXCrZLHm4EaZEmZVbg3QNfGhn7BJu1bHMtLqPD4y8eJxm2uGrW6saf6qKYmmu64F8A667NbD4yskPRQ1S863VzwGpxxmgLc1Ta3R46jEqsAoRDoZVUaCgBBZG3Yg1CTgi1EVBMXU7qvY4n3h8o2FLCEMWY4KadnV3iD4FbcdCmg4yxBosNAZgbPjhgGjCimjh4YsLd9zymGLmivmz2ZBg5m3xaiXT9NN81X9C1JUujd'
|
|
346
|
+
|
|
328
347
|
const txs: string[] = [
|
|
329
348
|
// 0
|
|
330
349
|
"0100000001074d631cc91344daf964cb8b1f43fd3439b804eb673cbe82d9bde3dcc6394abd020000006b48304502210096e24b07db278344a32c12667bac0d38ea87b28e9c52f3630ff7c11760d4002e02201b9dc879465c82152920f650828bfabba0d0bc8f7e15b568b196f1fa7e2cb4644121034e6dcc60f278b83e3e7b2b6570c13a184f4cf27a279199625c981adfe42de997ffffffff03b706000000000000fdc20421029b09fdddfae493e309d3d97b919f6ab2902a789158f6f78489ad903b7a14baeaac2131546f446f44744b7265457a6248594b466a6d6f42756475466d53585855475a474d3004a1edf497c9db470e7862d8c8319b4312f197a18db529a5839ba7c7e4f4dcee4ff4b4651a2a30040ef0aab1c2a4386620b96cb068c2ad2bc06ed90cebed65643710497f785f48c91482ac86893dd675c3de5d7db2e305fb30a588c75282f062367dee1dd4d930502f64f043c45cd1f220f875ff749f5449c99b29a200a86287a842a4f1df21dafda06a1b39b590b33b15109ee8a700728f1a80016ed370f639b35c9d079dea938d7fe451a52c744605a64a89ac7ebfcf692ef345a1e41fba055c197f7e3f608196001ed150bd56883970f3b8954a63aa70e4fa1c33799d43caaacf7ca30f8e873a6a20a04b1508f851633fcb4f30de75954747e702775b143c668c3b80cef96001980f05a8322726a567a379a2901db030057400bf0484c0852b1ec42cdaae6bf9f258ba0c914d83a96522d63b46a71571087fc4ad4ea0be0f6f354e7f638a7c316ceca3be69698104c1662ce0d5f4334b3327377497f9bb8605388c86a5a3928b22dbafd20a488442862c0ea4cd72778ae3b1bfd1d517299969f553b21173f75b530d1c19e23835dbc423c0ab5d3a894560a064951e237d2956db5d007d2132721a949849df49c5c5ab21f9eacb9ea5aa704c37cd101c17bee37954eae01472096f0e3e52b9f98d46426f35437c76c643c6f5d4d0a5ceb89d03a31c451d7882a527c9e78abf01f6178446cd74a7ad4190f364cc79fccc91583f3521e706bd548dc19633234fe96e40e19ef6424b252691e2183484cbf088e965066d4b93b80de927f3426e71f265152d898537c6112ed4500b8b7f23d033c18bad6c3ae5a2e069b03af5b619a334713f367082d41fdff8d5b17ab90a8aff224fbec3a8ca93828fe18c24b54fdcffdd9ee53c40808a4d233a56ed6f21bda0ebf1f4375785f2427da484ba9cdcd45bd28f8072e885bcb687c575ed00bea520508e795b32b7c882ffdd36062e9f748fddbb99a71e7afaed26c24e631ccce19ceb92f1920fb45ae30b58dde09389062eccf1292c55aca96e7eae53a3b6721a568b1bc8f1bad201c0e3b3fe7caec178e491dc12e97a0d0448136f7fb9970c7b053a85a553927a2273682eb2ae4c41c12f1d893d427f659048bbfa6c4be0682d8226d21e4b522e0a2d575655dea64d36d1e0f5719d4f6d06b47f4b4e32731652f0c12e28a9642e90d049b0fbc2c2c94079106c017ad85a81882344ddb71e3c5de31f5b42df9c269e1f64e36b3179ef5e9581fc57c1ed919962ba11686ef0e0a40694f8733bd1e2232196ca4f35c33df1b27872bd3aa0e258601192e50ec5bb64c6ae1a60f580cb6434048c7a419a76028f95442ce5c628f1324281ca3e8050c7ea8a90c3782a6df2cfee5d735e43f518bc215017cd036f70011312204bf885c615b1d1601626de7a1ff9ff21a44818fa19b7a8ed241b6f16380c9e376c6c7a2f48414269e4a3f130ac5c01a7ca8ad4af044ed4288657f59b675e89287c3811c8e81ba1c75f68b8ec85a35d15b5f1cee0fa25874730450221008d6e0b7d98442477d5637fc12fa871f224fd82194d2c3d8e524a12e389ddc35602206286a3b292ef3bac9f047c842a3e9b418102617546e9dc6f9dcc6653c82e645c6d75c8000000000000001976a9142e1ba3aade0562035e69630cb40024f403c0599e88ac77420000000000001976a914eeea6c76530f46d0f2a20ad522c4a6886bad686d88ac00000000"
|
|
@@ -20,7 +20,7 @@ async function measureTime(fn: () => Promise<void>): Promise<number> {
|
|
|
20
20
|
describe('Transaction Verification Benchmark', () => {
|
|
21
21
|
const privateKey = new PrivateKey(1)
|
|
22
22
|
const publicKey = privateKey.toPublicKey()
|
|
23
|
-
const publicKeyHash =
|
|
23
|
+
const publicKeyHash = publicKey.toHash()
|
|
24
24
|
const p2pkh = new P2PKH()
|
|
25
25
|
|
|
26
26
|
it('verifies a transaction with a deep input chain', async () => {
|
|
@@ -11,7 +11,7 @@ import Curve from '../../../dist/cjs/src/primitives/Curve'
|
|
|
11
11
|
import P2PKH from '../../../dist/cjs/src/script/templates/P2PKH'
|
|
12
12
|
import fromUtxo from '../../../dist/cjs/src/compat/Utxo'
|
|
13
13
|
import MerklePath from '../../../dist/cjs/src/transaction/MerklePath'
|
|
14
|
-
import {
|
|
14
|
+
import { BEEF_V1 } from '../../../dist/cjs/src/transaction/Beef'
|
|
15
15
|
|
|
16
16
|
import sighashVectors from '../../primitives/__tests/sighash.vectors'
|
|
17
17
|
import invalidTransactions from './tx.invalid.vectors'
|
|
@@ -817,7 +817,7 @@ describe('Transaction', () => {
|
|
|
817
817
|
const fakeTXID = toArray('00'.repeat(32), 'hex')
|
|
818
818
|
writer.write(fakeTXID)
|
|
819
819
|
// Write empty BEEF data
|
|
820
|
-
writer.writeUInt32LE(
|
|
820
|
+
writer.writeUInt32LE(BEEF_V1) // BEEF version
|
|
821
821
|
writer.writeVarIntNum(0) // No BUMPs
|
|
822
822
|
writer.writeVarIntNum(0) // No transactions
|
|
823
823
|
|
|
@@ -890,7 +890,7 @@ describe('Transaction', () => {
|
|
|
890
890
|
|
|
891
891
|
// Build BEEF data
|
|
892
892
|
const beefWriter = new Writer()
|
|
893
|
-
beefWriter.writeUInt32LE(
|
|
893
|
+
beefWriter.writeUInt32LE(BEEF_V1) // BEEF version
|
|
894
894
|
// BUMPs
|
|
895
895
|
beefWriter.writeVarIntNum(2)
|
|
896
896
|
beefWriter.write(sourceTx1.merklePath.toBinary())
|
|
@@ -46,8 +46,8 @@ describe('WhatsOnChainBroadcaster', () => {
|
|
|
46
46
|
it('should broadcast successfully using Node.js https', async () => {
|
|
47
47
|
// Mocking Node.js https module
|
|
48
48
|
mockedHttps(successResponse)
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
global.window = {} as any
|
|
50
|
+
|
|
51
51
|
const broadcaster = new WhatsOnChainBroadcaster(network)
|
|
52
52
|
const response = await broadcaster.broadcast(transaction)
|
|
53
53
|
|