@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.
Files changed (86) hide show
  1. package/dist/cjs/package.json +1 -1
  2. package/dist/cjs/src/primitives/Curve.js +7 -7
  3. package/dist/cjs/src/primitives/Curve.js.map +1 -1
  4. package/dist/cjs/src/primitives/ECDSA.js +394 -71
  5. package/dist/cjs/src/primitives/ECDSA.js.map +1 -1
  6. package/dist/cjs/src/primitives/Point.js +103 -23
  7. package/dist/cjs/src/primitives/Point.js.map +1 -1
  8. package/dist/cjs/src/primitives/TransactionSignature.js +4 -3
  9. package/dist/cjs/src/primitives/TransactionSignature.js.map +1 -1
  10. package/dist/cjs/src/primitives/utils.js +14 -15
  11. package/dist/cjs/src/primitives/utils.js.map +1 -1
  12. package/dist/cjs/src/script/Spend.js +4 -4
  13. package/dist/cjs/src/script/Spend.js.map +1 -1
  14. package/dist/cjs/src/totp/totp.js +1 -1
  15. package/dist/cjs/src/totp/totp.js.map +1 -1
  16. package/dist/cjs/src/transaction/Beef.js +492 -0
  17. package/dist/cjs/src/transaction/Beef.js.map +1 -0
  18. package/dist/cjs/src/transaction/BeefParty.js +97 -0
  19. package/dist/cjs/src/transaction/BeefParty.js.map +1 -0
  20. package/dist/cjs/src/transaction/BeefTx.js +123 -0
  21. package/dist/cjs/src/transaction/BeefTx.js.map +1 -0
  22. package/dist/cjs/src/transaction/Transaction.js +81 -66
  23. package/dist/cjs/src/transaction/Transaction.js.map +1 -1
  24. package/dist/cjs/src/transaction/index.js +7 -1
  25. package/dist/cjs/src/transaction/index.js.map +1 -1
  26. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  27. package/dist/esm/src/primitives/Curve.js +7 -7
  28. package/dist/esm/src/primitives/Curve.js.map +1 -1
  29. package/dist/esm/src/primitives/ECDSA.js +394 -71
  30. package/dist/esm/src/primitives/ECDSA.js.map +1 -1
  31. package/dist/esm/src/primitives/Point.js +103 -23
  32. package/dist/esm/src/primitives/Point.js.map +1 -1
  33. package/dist/esm/src/primitives/TransactionSignature.js +4 -3
  34. package/dist/esm/src/primitives/TransactionSignature.js.map +1 -1
  35. package/dist/esm/src/primitives/utils.js +14 -15
  36. package/dist/esm/src/primitives/utils.js.map +1 -1
  37. package/dist/esm/src/script/Spend.js +4 -4
  38. package/dist/esm/src/script/Spend.js.map +1 -1
  39. package/dist/esm/src/totp/totp.js +1 -1
  40. package/dist/esm/src/totp/totp.js.map +1 -1
  41. package/dist/esm/src/transaction/Beef.js +485 -0
  42. package/dist/esm/src/transaction/Beef.js.map +1 -0
  43. package/dist/esm/src/transaction/BeefParty.js +93 -0
  44. package/dist/esm/src/transaction/BeefParty.js.map +1 -0
  45. package/dist/esm/src/transaction/BeefTx.js +121 -0
  46. package/dist/esm/src/transaction/BeefTx.js.map +1 -0
  47. package/dist/esm/src/transaction/Transaction.js +81 -66
  48. package/dist/esm/src/transaction/Transaction.js.map +1 -1
  49. package/dist/esm/src/transaction/index.js +3 -0
  50. package/dist/esm/src/transaction/index.js.map +1 -1
  51. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  52. package/dist/types/src/primitives/ECDSA.d.ts.map +1 -1
  53. package/dist/types/src/primitives/Point.d.ts +5 -0
  54. package/dist/types/src/primitives/Point.d.ts.map +1 -1
  55. package/dist/types/src/primitives/TransactionSignature.d.ts.map +1 -1
  56. package/dist/types/src/primitives/utils.d.ts.map +1 -1
  57. package/dist/types/src/transaction/Beef.d.ts +143 -0
  58. package/dist/types/src/transaction/Beef.d.ts.map +1 -0
  59. package/dist/types/src/transaction/BeefParty.d.ts +62 -0
  60. package/dist/types/src/transaction/BeefParty.d.ts.map +1 -0
  61. package/dist/types/src/transaction/BeefTx.d.ts +35 -0
  62. package/dist/types/src/transaction/BeefTx.d.ts.map +1 -0
  63. package/dist/types/src/transaction/Transaction.d.ts.map +1 -1
  64. package/dist/types/src/transaction/TransactionInput.d.ts +1 -1
  65. package/dist/types/src/transaction/TransactionInput.d.ts.map +1 -1
  66. package/dist/types/src/transaction/index.d.ts +3 -0
  67. package/dist/types/src/transaction/index.d.ts.map +1 -1
  68. package/dist/types/tsconfig.types.tsbuildinfo +1 -1
  69. package/docs/primitives.md +373 -55
  70. package/docs/transaction.md +467 -1
  71. package/package.json +1 -1
  72. package/src/primitives/Curve.ts +7 -7
  73. package/src/primitives/ECDSA.ts +485 -75
  74. package/src/primitives/Point.ts +110 -25
  75. package/src/primitives/TransactionSignature.ts +4 -3
  76. package/src/primitives/utils.ts +15 -11
  77. package/src/script/Spend.ts +4 -4
  78. package/src/totp/totp.ts +1 -1
  79. package/src/transaction/Beef.ts +533 -0
  80. package/src/transaction/BeefParty.ts +100 -0
  81. package/src/transaction/BeefTx.ts +121 -0
  82. package/src/transaction/Transaction.ts +95 -69
  83. package/src/transaction/TransactionInput.ts +1 -1
  84. package/src/transaction/__tests/Beef.test.ts +290 -0
  85. package/src/transaction/__tests/Transaction.benchmarks.test.ts +222 -0
  86. 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 !== 4022206465) {
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 (chainTracker: ChainTracker | 'scripts only' = defaultChainTracker(), feeModel?: FeeModel): Promise<boolean> {
639
- // If the transaction has a valid merkle path, verification is complete.
640
- if (typeof this.merklePath === 'object') {
641
- if (chainTracker === 'scripts only') {
642
- return true
643
- } else {
644
- const proofValid = await this.merklePath.verify(
645
- this.id('hex'),
646
- chainTracker
647
- )
648
- // Note that if the proof is provided but not valid, the transaction could still be verified by proving all inputs (if they are available) and checking the associated spends.
649
- if (proofValid) {
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
- // Verify each input transaction and evaluate the spend events.
664
- // Also, keep a total of the input amounts for later.
665
- let inputTotal = 0
666
- for (let i = 0; i < this.inputs.length; i++) {
667
- const input = this.inputs[i]
668
- if (typeof input.sourceTransaction !== 'object') {
669
- throw new Error(`Verification failed because the input at index ${i} of transaction ${this.id('hex')} 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.`)
670
- }
671
- if (typeof input.unlockingScript !== 'object') {
672
- throw new Error(`Verification failed because the input at index ${i} of transaction ${this.id('hex')} 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.`)
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
- const sourceOutput = input.sourceTransaction.outputs[input.sourceOutputIndex]
675
- inputTotal += sourceOutput.satoshis
676
- const inputVerified = await input.sourceTransaction.verify(chainTracker, feeModel)
677
- if (!inputVerified) {
678
- return false
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
- const otherInputs = [...this.inputs]
681
- otherInputs.splice(i, 1)
682
- if (typeof input.sourceTXID === 'undefined') {
683
- input.sourceTXID = input.sourceTransaction.id('hex')
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
- const spend = new Spend({
687
- sourceTXID: input.sourceTXID,
688
- sourceOutputIndex: input.sourceOutputIndex,
689
- lockingScript: sourceOutput.lockingScript,
690
- sourceSatoshis: sourceOutput.satoshis,
691
- transactionVersion: this.version,
692
- otherInputs,
693
- unlockingScript: input.unlockingScript,
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 (!spendValid) {
734
+ if (outputTotal > inputTotal) {
702
735
  return false
703
736
  }
704
- }
705
737
 
706
- // Total the outputs to ensure they don't amount to more than the inputs
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 outputTotal <= inputTotal
741
+ return true
716
742
  }
717
743
 
718
744
  /**
@@ -59,5 +59,5 @@ export default interface TransactionInput {
59
59
  sign: (tx: Transaction, inputIndex: number) => Promise<UnlockingScript>
60
60
  estimateLength: (tx: Transaction, inputIndex: number) => Promise<number>
61
61
  }
62
- sequence: number
62
+ sequence?: number
63
63
  }