@bsv/sdk 1.1.23 → 1.1.25
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/Curve.js +7 -7
- package/dist/cjs/src/primitives/Curve.js.map +1 -1
- package/dist/cjs/src/primitives/ECDSA.js +394 -71
- package/dist/cjs/src/primitives/ECDSA.js.map +1 -1
- package/dist/cjs/src/primitives/Point.js +103 -23
- package/dist/cjs/src/primitives/Point.js.map +1 -1
- package/dist/cjs/src/primitives/TransactionSignature.js +4 -3
- package/dist/cjs/src/primitives/TransactionSignature.js.map +1 -1
- package/dist/cjs/src/primitives/utils.js +14 -15
- package/dist/cjs/src/primitives/utils.js.map +1 -1
- package/dist/cjs/src/script/Spend.js +4 -4
- package/dist/cjs/src/script/Spend.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 +492 -0
- package/dist/cjs/src/transaction/Beef.js.map +1 -0
- package/dist/cjs/src/transaction/BeefParty.js +97 -0
- package/dist/cjs/src/transaction/BeefParty.js.map +1 -0
- package/dist/cjs/src/transaction/BeefTx.js +123 -0
- package/dist/cjs/src/transaction/BeefTx.js.map +1 -0
- package/dist/cjs/src/transaction/Transaction.js +81 -66
- package/dist/cjs/src/transaction/Transaction.js.map +1 -1
- package/dist/cjs/src/transaction/index.js +7 -1
- package/dist/cjs/src/transaction/index.js.map +1 -1
- package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/esm/src/primitives/Curve.js +7 -7
- package/dist/esm/src/primitives/Curve.js.map +1 -1
- package/dist/esm/src/primitives/ECDSA.js +394 -71
- package/dist/esm/src/primitives/ECDSA.js.map +1 -1
- package/dist/esm/src/primitives/Point.js +103 -23
- package/dist/esm/src/primitives/Point.js.map +1 -1
- package/dist/esm/src/primitives/TransactionSignature.js +4 -3
- package/dist/esm/src/primitives/TransactionSignature.js.map +1 -1
- package/dist/esm/src/primitives/utils.js +14 -15
- package/dist/esm/src/primitives/utils.js.map +1 -1
- package/dist/esm/src/script/Spend.js +4 -4
- package/dist/esm/src/script/Spend.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 +485 -0
- package/dist/esm/src/transaction/Beef.js.map +1 -0
- package/dist/esm/src/transaction/BeefParty.js +93 -0
- package/dist/esm/src/transaction/BeefParty.js.map +1 -0
- package/dist/esm/src/transaction/BeefTx.js +121 -0
- package/dist/esm/src/transaction/BeefTx.js.map +1 -0
- package/dist/esm/src/transaction/Transaction.js +81 -66
- package/dist/esm/src/transaction/Transaction.js.map +1 -1
- package/dist/esm/src/transaction/index.js +3 -0
- package/dist/esm/src/transaction/index.js.map +1 -1
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
- package/dist/types/src/primitives/ECDSA.d.ts.map +1 -1
- package/dist/types/src/primitives/Point.d.ts +5 -0
- package/dist/types/src/primitives/Point.d.ts.map +1 -1
- package/dist/types/src/primitives/TransactionSignature.d.ts.map +1 -1
- package/dist/types/src/primitives/utils.d.ts.map +1 -1
- package/dist/types/src/transaction/Beef.d.ts +143 -0
- package/dist/types/src/transaction/Beef.d.ts.map +1 -0
- package/dist/types/src/transaction/BeefParty.d.ts +62 -0
- package/dist/types/src/transaction/BeefParty.d.ts.map +1 -0
- package/dist/types/src/transaction/BeefTx.d.ts +35 -0
- package/dist/types/src/transaction/BeefTx.d.ts.map +1 -0
- package/dist/types/src/transaction/Transaction.d.ts.map +1 -1
- package/dist/types/src/transaction/TransactionInput.d.ts +1 -1
- package/dist/types/src/transaction/TransactionInput.d.ts.map +1 -1
- package/dist/types/src/transaction/index.d.ts +3 -0
- package/dist/types/src/transaction/index.d.ts.map +1 -1
- package/dist/types/tsconfig.types.tsbuildinfo +1 -1
- package/docs/primitives.md +373 -55
- package/docs/transaction.md +467 -1
- package/package.json +1 -1
- package/src/primitives/Curve.ts +7 -7
- package/src/primitives/ECDSA.ts +485 -75
- package/src/primitives/Point.ts +110 -25
- package/src/primitives/TransactionSignature.ts +4 -3
- package/src/primitives/utils.ts +15 -11
- package/src/script/Spend.ts +4 -4
- package/src/totp/totp.ts +1 -1
- package/src/transaction/Beef.ts +533 -0
- package/src/transaction/BeefParty.ts +100 -0
- package/src/transaction/BeefTx.ts +121 -0
- package/src/transaction/Transaction.ts +95 -69
- package/src/transaction/TransactionInput.ts +1 -1
- package/src/transaction/__tests/Beef.test.ts +290 -0
- package/src/transaction/__tests/Transaction.benchmarks.test.ts +222 -0
- package/src/transaction/index.ts +3 -0
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { hash256 } from "../primitives/Hash.js"
|
|
2
|
+
import { Reader, Writer, toHex, toArray } from "../primitives/utils.js"
|
|
3
|
+
import Transaction from "./Transaction.js"
|
|
4
|
+
import { BEEF_MAGIC_TXID_ONLY_EXTENSION } from "./Beef.js"
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A single bitcoin transaction associated with a `Beef` validity proof set.
|
|
8
|
+
*
|
|
9
|
+
* Simple case is transaction data included directly, either as raw bytes or fully parsed data, or both.
|
|
10
|
+
*
|
|
11
|
+
* Supports 'known' transactions which are represented by just their txid.
|
|
12
|
+
* It is assumed that intended consumer of this beef already has validity proof for such a transaction,
|
|
13
|
+
* which they can merge if necessary to create a valid beef.
|
|
14
|
+
*/
|
|
15
|
+
export default class BeefTx {
|
|
16
|
+
_bumpIndex?: number
|
|
17
|
+
_tx?: Transaction
|
|
18
|
+
_rawTx?: number[]
|
|
19
|
+
_txid?: string
|
|
20
|
+
inputTxids: string[] = []
|
|
21
|
+
degree: number = 0
|
|
22
|
+
|
|
23
|
+
get bumpIndex() : number | undefined { return this._bumpIndex}
|
|
24
|
+
|
|
25
|
+
set bumpIndex(v: number | undefined) {
|
|
26
|
+
this._bumpIndex = v
|
|
27
|
+
this.updateInputTxids()
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
get hasProof() : boolean {
|
|
31
|
+
return this._bumpIndex !== undefined
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
get isTxidOnly() : boolean {
|
|
35
|
+
return !!this._txid && !this._rawTx && !this._tx
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
get txid() {
|
|
39
|
+
if (this._txid) return this._txid
|
|
40
|
+
if (this._tx) return this._txid = this._tx.id('hex')
|
|
41
|
+
if (this._rawTx) return this._txid = toHex(hash256(this._rawTx))
|
|
42
|
+
throw new Error('Internal')
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
get tx() {
|
|
46
|
+
if (this._tx) return this._tx
|
|
47
|
+
if (this._rawTx) return this._tx = Transaction.fromBinary(this._rawTx)
|
|
48
|
+
return undefined
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
get rawTx() {
|
|
52
|
+
if (this._rawTx) return this._rawTx
|
|
53
|
+
if (this._tx) return this._rawTx = this._tx.toBinary()
|
|
54
|
+
return undefined
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* @param tx If string, must be a valid txid. If `number[]` must be a valid serialized transaction.
|
|
59
|
+
* @param bumpIndex If transaction already has a proof in the beef to which it will be added.
|
|
60
|
+
*/
|
|
61
|
+
constructor (tx: Transaction | number[] | string, bumpIndex?: number) {
|
|
62
|
+
if (typeof tx === 'string') {
|
|
63
|
+
this._txid = tx
|
|
64
|
+
} else {
|
|
65
|
+
if (Array.isArray(tx)) {
|
|
66
|
+
this._rawTx = tx
|
|
67
|
+
} else {
|
|
68
|
+
this._tx = <Transaction>tx
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
this.bumpIndex = bumpIndex
|
|
72
|
+
this.updateInputTxids()
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
private updateInputTxids() {
|
|
76
|
+
if (this.hasProof || !this.tx)
|
|
77
|
+
// If we have a proof, or don't have a parsed transaction
|
|
78
|
+
this.inputTxids = []
|
|
79
|
+
else {
|
|
80
|
+
const inputTxids = {};
|
|
81
|
+
for (const input of this.tx.inputs)
|
|
82
|
+
inputTxids[input.sourceTXID!] = true;
|
|
83
|
+
this.inputTxids = Object.keys(inputTxids);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
toWriter(writer: Writer) : void {
|
|
88
|
+
if (this.isTxidOnly) {
|
|
89
|
+
// Encode just the txid of a known transaction using the txid
|
|
90
|
+
writer.writeUInt32LE(BEEF_MAGIC_TXID_ONLY_EXTENSION)
|
|
91
|
+
writer.writeReverse(toArray(this._txid, 'hex'))
|
|
92
|
+
} else if (this._rawTx)
|
|
93
|
+
writer.write(this._rawTx)
|
|
94
|
+
else if (this._tx)
|
|
95
|
+
writer.write(this._tx.toBinary())
|
|
96
|
+
else
|
|
97
|
+
throw new Error('a valid serialized Transaction is expected')
|
|
98
|
+
if (this.bumpIndex === undefined) {
|
|
99
|
+
writer.writeUInt8(0)
|
|
100
|
+
} else {
|
|
101
|
+
writer.writeUInt8(1)
|
|
102
|
+
writer.writeVarIntNum(this.bumpIndex)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
static fromReader (br: Reader): BeefTx {
|
|
107
|
+
let tx: Transaction | number[] | string | undefined = undefined
|
|
108
|
+
const version = br.readUInt32LE()
|
|
109
|
+
if (version === BEEF_MAGIC_TXID_ONLY_EXTENSION) {
|
|
110
|
+
// This is the extension to support known transactions
|
|
111
|
+
tx = toHex(br.readReverse(32))
|
|
112
|
+
} else {
|
|
113
|
+
br.pos -= 4 // Unread the version...
|
|
114
|
+
tx = Transaction.fromReader(br)
|
|
115
|
+
}
|
|
116
|
+
const bumpIndex = br.readUInt8() ? br.readVarIntNum() : undefined
|
|
117
|
+
const beefTx = new BeefTx(tx, bumpIndex)
|
|
118
|
+
return beefTx
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
}
|
|
@@ -12,6 +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 { BEEF_MAGIC } from './Beef.js'
|
|
15
16
|
|
|
16
17
|
/**
|
|
17
18
|
* Represents a complete Bitcoin transaction. This class encapsulates all the details
|
|
@@ -66,7 +67,7 @@ export default class Transaction {
|
|
|
66
67
|
const reader = new Reader(beef)
|
|
67
68
|
// Read the version
|
|
68
69
|
const version = reader.readUInt32LE()
|
|
69
|
-
if (version !==
|
|
70
|
+
if (version !== BEEF_MAGIC) {
|
|
70
71
|
throw new Error(`Invalid BEEF version. Expected 4022206465, received ${version}.`)
|
|
71
72
|
}
|
|
72
73
|
|
|
@@ -593,9 +594,8 @@ export default class Transaction {
|
|
|
593
594
|
}
|
|
594
595
|
if (enc === 'hex') {
|
|
595
596
|
return toHex(hash)
|
|
596
|
-
} else {
|
|
597
|
-
return hash
|
|
598
597
|
}
|
|
598
|
+
return hash
|
|
599
599
|
}
|
|
600
600
|
|
|
601
601
|
/**
|
|
@@ -635,84 +635,110 @@ export default class Transaction {
|
|
|
635
635
|
*
|
|
636
636
|
* @example tx.verify(new WhatsOnChain(), new SatoshisPerKilobyte(1))
|
|
637
637
|
*/
|
|
638
|
-
async verify (
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
return true
|
|
651
|
-
}
|
|
638
|
+
async verify (
|
|
639
|
+
chainTracker: ChainTracker | 'scripts only' = defaultChainTracker(),
|
|
640
|
+
feeModel?: FeeModel
|
|
641
|
+
): Promise<boolean> {
|
|
642
|
+
const verifiedTxids = new Set<string>()
|
|
643
|
+
const txQueue: Transaction[] = [this]
|
|
644
|
+
|
|
645
|
+
while (txQueue.length > 0) {
|
|
646
|
+
const tx = txQueue.shift()
|
|
647
|
+
const txid = tx.id('hex')
|
|
648
|
+
if (verifiedTxids.has(txid)) {
|
|
649
|
+
continue
|
|
652
650
|
}
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
if (typeof feeModel !== 'undefined') {
|
|
656
|
-
const cpTx = Transaction.fromHexEF(this.toHexEF())
|
|
657
|
-
delete cpTx.outputs[0].satoshis
|
|
658
|
-
cpTx.outputs[0].change = true
|
|
659
|
-
await cpTx.fee(feeModel)
|
|
660
|
-
if (this.getFee() < cpTx.getFee()) throw new Error(`Verification failed because the transaction ${this.id('hex')} has an insufficient fee and has not been mined.`)
|
|
661
|
-
}
|
|
662
651
|
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
652
|
+
// If the transaction has a valid merkle path, verification is complete.
|
|
653
|
+
if (typeof tx.merklePath === 'object') {
|
|
654
|
+
if (chainTracker === 'scripts only') {
|
|
655
|
+
verifiedTxids.add(txid)
|
|
656
|
+
continue
|
|
657
|
+
} else {
|
|
658
|
+
const proofValid = await tx.merklePath.verify(
|
|
659
|
+
txid,
|
|
660
|
+
chainTracker
|
|
661
|
+
)
|
|
662
|
+
// If the proof is valid, no need to verify inputs.
|
|
663
|
+
if (proofValid) {
|
|
664
|
+
verifiedTxids.add(txid)
|
|
665
|
+
continue
|
|
666
|
+
}
|
|
667
|
+
}
|
|
673
668
|
}
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
669
|
+
|
|
670
|
+
// Verify fee if feeModel is provided
|
|
671
|
+
if (typeof feeModel !== 'undefined') {
|
|
672
|
+
const cpTx = Transaction.fromEF(tx.toEF())
|
|
673
|
+
delete cpTx.outputs[0].satoshis
|
|
674
|
+
cpTx.outputs[0].change = true
|
|
675
|
+
await cpTx.fee(feeModel)
|
|
676
|
+
if (tx.getFee() < cpTx.getFee()) {
|
|
677
|
+
throw new Error(`Verification failed because the transaction ${txid} has an insufficient fee and has not been mined.`)
|
|
678
|
+
}
|
|
679
679
|
}
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
680
|
+
|
|
681
|
+
// Verify each input transaction and evaluate the spend events.
|
|
682
|
+
// Also, keep a total of the input amounts for later.
|
|
683
|
+
let inputTotal = 0
|
|
684
|
+
for (let i = 0; i < tx.inputs.length; i++) {
|
|
685
|
+
const input = tx.inputs[i]
|
|
686
|
+
if (typeof input.sourceTransaction !== 'object') {
|
|
687
|
+
throw new Error(`Verification failed because the input at index ${i} of transaction ${txid} is missing an associated source transaction. This source transaction is required for transaction verification because there is no merkle proof for the transaction spending a UTXO it contains.`)
|
|
688
|
+
}
|
|
689
|
+
if (typeof input.unlockingScript !== 'object') {
|
|
690
|
+
throw new Error(`Verification failed because the input at index ${i} of transaction ${txid} is missing an associated unlocking script. This script is required for transaction verification because there is no merkle proof for the transaction spending the UTXO.`)
|
|
691
|
+
}
|
|
692
|
+
const sourceOutput = input.sourceTransaction.outputs[input.sourceOutputIndex]
|
|
693
|
+
inputTotal += sourceOutput.satoshis
|
|
694
|
+
|
|
695
|
+
const sourceTxid = input.sourceTransaction.id('hex')
|
|
696
|
+
if (!verifiedTxids.has(sourceTxid)) {
|
|
697
|
+
txQueue.push(input.sourceTransaction)
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
const otherInputs = tx.inputs.filter((_, idx) => idx !== i)
|
|
701
|
+
if (typeof input.sourceTXID === 'undefined') {
|
|
702
|
+
input.sourceTXID = sourceTxid
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
const spend = new Spend({
|
|
706
|
+
sourceTXID: input.sourceTXID,
|
|
707
|
+
sourceOutputIndex: input.sourceOutputIndex,
|
|
708
|
+
lockingScript: sourceOutput.lockingScript,
|
|
709
|
+
sourceSatoshis: sourceOutput.satoshis,
|
|
710
|
+
transactionVersion: tx.version,
|
|
711
|
+
otherInputs,
|
|
712
|
+
unlockingScript: input.unlockingScript,
|
|
713
|
+
inputSequence: input.sequence,
|
|
714
|
+
inputIndex: i,
|
|
715
|
+
outputs: tx.outputs,
|
|
716
|
+
lockTime: tx.lockTime
|
|
717
|
+
})
|
|
718
|
+
const spendValid = spend.validate()
|
|
719
|
+
|
|
720
|
+
if (!spendValid) {
|
|
721
|
+
return false
|
|
722
|
+
}
|
|
684
723
|
}
|
|
685
724
|
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
inputSequence: input.sequence,
|
|
695
|
-
inputIndex: i,
|
|
696
|
-
outputs: this.outputs,
|
|
697
|
-
lockTime: this.lockTime
|
|
698
|
-
})
|
|
699
|
-
const spendValid = spend.validate()
|
|
725
|
+
// Total the outputs to ensure they don't amount to more than the inputs
|
|
726
|
+
let outputTotal = 0
|
|
727
|
+
for (const out of tx.outputs) {
|
|
728
|
+
if (typeof out.satoshis !== 'number') {
|
|
729
|
+
throw new Error('Every output must have a defined amount during transaction verification.')
|
|
730
|
+
}
|
|
731
|
+
outputTotal += out.satoshis
|
|
732
|
+
}
|
|
700
733
|
|
|
701
|
-
if (
|
|
734
|
+
if (outputTotal > inputTotal) {
|
|
702
735
|
return false
|
|
703
736
|
}
|
|
704
|
-
}
|
|
705
737
|
|
|
706
|
-
|
|
707
|
-
let outputTotal = 0
|
|
708
|
-
for (const out of this.outputs) {
|
|
709
|
-
if (typeof out.satoshis !== 'number') {
|
|
710
|
-
throw new Error('Every output must have a defined amount during transaction verification.')
|
|
711
|
-
}
|
|
712
|
-
outputTotal += out.satoshis
|
|
738
|
+
verifiedTxids.add(txid)
|
|
713
739
|
}
|
|
714
740
|
|
|
715
|
-
return
|
|
741
|
+
return true
|
|
716
742
|
}
|
|
717
743
|
|
|
718
744
|
/**
|