@bsv/sdk 1.0.0 → 1.0.1

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 (123) hide show
  1. package/README.md +25 -3
  2. package/package.json +9 -5
  3. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -40
  4. package/.github/ISSUE_TEMPLATE/discussion.md +0 -24
  5. package/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md +0 -23
  6. package/CHANGELOG.md +0 -72
  7. package/CONTRIBUTING.md +0 -85
  8. package/ROADMAP.md +0 -3
  9. package/docs/getting-started/COMMONJS.md +0 -94
  10. package/docs/getting-started/REACT-TS.md +0 -131
  11. package/docs/getting-started/TS-NODE.md +0 -106
  12. package/docs/getting-started/VUE.md +0 -103
  13. package/jest.config.js +0 -6
  14. package/mod.ts +0 -8
  15. package/src/compat/BSM.ts +0 -51
  16. package/src/compat/ECIES.ts +0 -557
  17. package/src/compat/HD.ts +0 -348
  18. package/src/compat/Mnemonic.ts +0 -295
  19. package/src/compat/__tests/BSM.test.ts +0 -38
  20. package/src/compat/__tests/ECIES.test.ts +0 -90
  21. package/src/compat/__tests/HD.test.ts +0 -405
  22. package/src/compat/__tests/Mnemonic.test.ts +0 -177
  23. package/src/compat/__tests/Mnemonic.vectors.ts +0 -172
  24. package/src/compat/bip-39-wordlist-en.ts +0 -2053
  25. package/src/compat/index.ts +0 -4
  26. package/src/messages/EncryptedMessage.ts +0 -70
  27. package/src/messages/SignedMessage.ts +0 -87
  28. package/src/messages/__tests/EncryptedMessage.test.ts +0 -36
  29. package/src/messages/__tests/SignedMessage.test.ts +0 -53
  30. package/src/messages/index.ts +0 -2
  31. package/src/primitives/AESGCM.ts +0 -479
  32. package/src/primitives/BasePoint.ts +0 -21
  33. package/src/primitives/BigNumber.ts +0 -4619
  34. package/src/primitives/Curve.ts +0 -1163
  35. package/src/primitives/DRBG.ts +0 -102
  36. package/src/primitives/ECDSA.ts +0 -164
  37. package/src/primitives/Hash.ts +0 -1420
  38. package/src/primitives/JacobianPoint.ts +0 -410
  39. package/src/primitives/K256.ts +0 -116
  40. package/src/primitives/Mersenne.ts +0 -123
  41. package/src/primitives/MontgomoryMethod.ts +0 -160
  42. package/src/primitives/Point.ts +0 -852
  43. package/src/primitives/PrivateKey.ts +0 -195
  44. package/src/primitives/PublicKey.ts +0 -154
  45. package/src/primitives/Random.ts +0 -55
  46. package/src/primitives/ReductionContext.ts +0 -528
  47. package/src/primitives/Signature.ts +0 -235
  48. package/src/primitives/SymmetricKey.ts +0 -75
  49. package/src/primitives/TransactionSignature.ts +0 -189
  50. package/src/primitives/__tests/AESGCM.test.ts +0 -338
  51. package/src/primitives/__tests/BRC42.private.vectors.ts +0 -33
  52. package/src/primitives/__tests/BRC42.public.vectors.ts +0 -33
  53. package/src/primitives/__tests/BigNumber.arithmatic.test.ts +0 -572
  54. package/src/primitives/__tests/BigNumber.binary.test.ts +0 -203
  55. package/src/primitives/__tests/BigNumber.constructor.test.ts +0 -176
  56. package/src/primitives/__tests/BigNumber.dhGroup.test.ts +0 -18
  57. package/src/primitives/__tests/BigNumber.fixtures.ts +0 -264
  58. package/src/primitives/__tests/BigNumber.serializers.test.ts +0 -157
  59. package/src/primitives/__tests/BigNumber.utils.test.ts +0 -347
  60. package/src/primitives/__tests/Curve.unit.test.ts +0 -192
  61. package/src/primitives/__tests/DRBG.test.ts +0 -18
  62. package/src/primitives/__tests/DRBG.vectors.ts +0 -167
  63. package/src/primitives/__tests/ECDH.test.ts +0 -31
  64. package/src/primitives/__tests/ECDSA.test.ts +0 -58
  65. package/src/primitives/__tests/HMAC.test.ts +0 -59
  66. package/src/primitives/__tests/Hash.test.ts +0 -121
  67. package/src/primitives/__tests/PBKDF2.vectors.ts +0 -119
  68. package/src/primitives/__tests/PrivateKey.test.ts +0 -17
  69. package/src/primitives/__tests/PublicKey.test.ts +0 -66
  70. package/src/primitives/__tests/Random.test.ts +0 -14
  71. package/src/primitives/__tests/Reader.test.ts +0 -296
  72. package/src/primitives/__tests/ReductionContext.test.ts +0 -279
  73. package/src/primitives/__tests/SymmetricKey.test.ts +0 -58
  74. package/src/primitives/__tests/SymmetricKey.vectors.ts +0 -40
  75. package/src/primitives/__tests/Writer.test.ts +0 -198
  76. package/src/primitives/__tests/sighash.vectors.ts +0 -3503
  77. package/src/primitives/__tests/utils.test.ts +0 -108
  78. package/src/primitives/index.ts +0 -8
  79. package/src/primitives/utils.ts +0 -665
  80. package/src/script/LockingScript.ts +0 -30
  81. package/src/script/OP.ts +0 -219
  82. package/src/script/Script.ts +0 -426
  83. package/src/script/ScriptChunk.ts +0 -7
  84. package/src/script/ScriptTemplate.ts +0 -36
  85. package/src/script/Spend.ts +0 -1379
  86. package/src/script/UnlockingScript.ts +0 -30
  87. package/src/script/__tests/Script.test.ts +0 -369
  88. package/src/script/__tests/Spend.test.ts +0 -248
  89. package/src/script/__tests/script.invalid.vectors.ts +0 -925
  90. package/src/script/__tests/script.valid.vectors.ts +0 -1120
  91. package/src/script/__tests/scriptFromVector.ts +0 -42
  92. package/src/script/__tests/spend.valid.vectors.ts +0 -2288
  93. package/src/script/index.ts +0 -7
  94. package/src/script/templates/P2PKH.ts +0 -109
  95. package/src/script/templates/RPuzzle.ts +0 -140
  96. package/src/script/templates/index.ts +0 -2
  97. package/src/transaction/Broadcaster.ts +0 -42
  98. package/src/transaction/ChainTracker.ts +0 -22
  99. package/src/transaction/FeeModel.ts +0 -13
  100. package/src/transaction/MerklePath.ts +0 -259
  101. package/src/transaction/Transaction.ts +0 -602
  102. package/src/transaction/TransactionInput.ts +0 -63
  103. package/src/transaction/TransactionOutput.ts +0 -37
  104. package/src/transaction/__tests/MerklePath.test.ts +0 -181
  105. package/src/transaction/__tests/Transaction.test.ts +0 -413
  106. package/src/transaction/__tests/bigtx.vectors.ts +0 -4
  107. package/src/transaction/__tests/bump.invalid.vectors.ts +0 -8
  108. package/src/transaction/__tests/bump.valid.vectors.ts +0 -4
  109. package/src/transaction/__tests/tx.invalid.vectors.ts +0 -281
  110. package/src/transaction/__tests/tx.valid.vectors.ts +0 -364
  111. package/src/transaction/broadcasters/ARC.ts +0 -106
  112. package/src/transaction/broadcasters/__tests/ARC.test.ts +0 -115
  113. package/src/transaction/broadcasters/index.ts +0 -1
  114. package/src/transaction/fee-models/SatoshisPerKilobyte.ts +0 -71
  115. package/src/transaction/fee-models/index.ts +0 -1
  116. package/src/transaction/index.ts +0 -6
  117. package/ts2md.json +0 -5
  118. package/tsconfig.base.json +0 -26
  119. package/tsconfig.cjs.json +0 -11
  120. package/tsconfig.eslint.json +0 -12
  121. package/tsconfig.esm.json +0 -9
  122. package/tsconfig.json +0 -17
  123. package/tsconfig.types.json +0 -11
@@ -1,7 +0,0 @@
1
- export { default as OP } from './OP.js'
2
- export { default as Script } from './Script.js'
3
- export { default as LockingScript } from './LockingScript.js'
4
- export { default as UnlockingScript } from './UnlockingScript.js'
5
- export { default as Spend } from './Spend.js'
6
- export type { default as ScriptTemplate } from './ScriptTemplate.js'
7
- export * from './templates/index.js'
@@ -1,109 +0,0 @@
1
- import OP from '../OP.js'
2
- import ScriptTemplate from '../ScriptTemplate.js'
3
- import LockingScript from '../LockingScript.js'
4
- import UnlockingScript from '../UnlockingScript.js'
5
- import Transaction from '../../transaction/Transaction.js'
6
- import PrivateKey from '../../primitives/PrivateKey.js'
7
- import TransactionSignature from '../../primitives/TransactionSignature.js'
8
- import { sha256 } from '../../primitives/Hash.js'
9
-
10
- /**
11
- * P2PKH (Pay To Public Key Hash) class implementing ScriptTemplate.
12
- *
13
- * This class provides methods to create Pay To Public Key Hash locking and unlocking scripts, including the unlocking of P2PKH UTXOs with the private key.
14
- */
15
- export default class P2PKH implements ScriptTemplate {
16
- /**
17
- * Creates a P2PKH locking script for a given public key hash.
18
- *
19
- * @param {number[]} pubkeyhash - An array representing the public key hash.
20
- * @returns {LockingScript} - A P2PKH locking script.
21
- */
22
- lock(pubkeyhash: number[]): LockingScript {
23
- return new LockingScript([
24
- { op: OP.OP_DUP },
25
- { op: OP.OP_HASH160 },
26
- { op: pubkeyhash.length, data: pubkeyhash },
27
- { op: OP.OP_EQUALVERIFY },
28
- { op: OP.OP_CHECKSIG }
29
- ])
30
- }
31
-
32
- /**
33
- * Creates a function that generates a P2PKH unlocking script along with its signature and length estimation.
34
- *
35
- * The returned object contains:
36
- * 1. `sign` - A function that, when invoked with a transaction and an input index,
37
- * produces an unlocking script suitable for a P2PKH locked output.
38
- * 2. `estimateLength` - A function that returns the estimated length of the unlocking script in bytes.
39
- *
40
- * @param {PrivateKey} privateKey - The private key used for signing the transaction.
41
- * @param {'all'|'none'|'single'} signOutputs - The signature scope for outputs.
42
- * @param {boolean} anyoneCanPay - Flag indicating if the signature allows for other inputs to be added later.
43
- * @returns {Object} - An object containing the `sign` and `estimateLength` functions.
44
- */
45
- unlock(
46
- privateKey: PrivateKey,
47
- signOutputs: 'all' | 'none' | 'single' = 'all',
48
- anyoneCanPay: boolean = false
49
- ): {
50
- sign: (tx: Transaction, inputIndex: number) => Promise<UnlockingScript>
51
- estimateLength: () => Promise<106>
52
- } {
53
- return {
54
- sign: async (tx: Transaction, inputIndex: number) => {
55
- let signatureScope = TransactionSignature.SIGHASH_FORKID
56
- if (signOutputs === 'all') {
57
- signatureScope |= TransactionSignature.SIGHASH_ALL
58
- }
59
- if (signOutputs === 'none') {
60
- signatureScope |= TransactionSignature.SIGHASH_NONE
61
- }
62
- if (signOutputs === 'single') {
63
- signatureScope |= TransactionSignature.SIGHASH_SINGLE
64
- }
65
- if (anyoneCanPay) {
66
- signatureScope |= TransactionSignature.SIGHASH_ANYONECANPAY
67
- }
68
- const otherInputs = [...tx.inputs]
69
- const [input] = otherInputs.splice(inputIndex, 1)
70
- if (typeof input.sourceTransaction !== 'object') {
71
- // Question: Should the library support use-cases where the source transaction is not provided? This is to say, is it ever acceptable for someone to sign an input spending some output from a transaction they have not provided? Some elements (such as the satoshi value and output script) are always required. A merkle proof is also always required, and verifying it (while also verifying that the claimed output is contained within the claimed transaction) is also always required. This seems to require the entire input transaction.
72
- throw new Error(
73
- 'The source transaction is needed for transaction signing.'
74
- )
75
- }
76
- const preimage = TransactionSignature.format({
77
- sourceTXID: input.sourceTransaction.id('hex') as string,
78
- sourceOutputIndex: input.sourceOutputIndex,
79
- sourceSatoshis: input.sourceTransaction.outputs[input.sourceOutputIndex].satoshis,
80
- transactionVersion: tx.version,
81
- otherInputs,
82
- inputIndex,
83
- outputs: tx.outputs,
84
- inputSequence: input.sequence,
85
- subscript: input.sourceTransaction.outputs[input.sourceOutputIndex].lockingScript,
86
- lockTime: tx.lockTime,
87
- scope: signatureScope
88
- })
89
- const rawSignature = privateKey.sign(sha256(preimage))
90
- const sig = new TransactionSignature(
91
- rawSignature.r,
92
- rawSignature.s,
93
- signatureScope
94
- )
95
- const sigForScript = sig.toChecksigFormat()
96
- const pubkeyForScript = privateKey.toPublicKey().encode(true) as number[]
97
- return new UnlockingScript([
98
- { op: sigForScript.length, data: sigForScript },
99
- { op: pubkeyForScript.length, data: pubkeyForScript }
100
- ])
101
- },
102
- estimateLength: async () => {
103
- // public key (1+33) + signature (1+71)
104
- // Note: We add 1 to each element's length because of the associated OP_PUSH
105
- return 106
106
- }
107
- }
108
- }
109
- }
@@ -1,140 +0,0 @@
1
- import OP from '../OP.js'
2
- import ScriptTemplate from '../ScriptTemplate.js'
3
- import LockingScript from '../LockingScript.js'
4
- import UnlockingScript from '../UnlockingScript.js'
5
- import Transaction from '../../transaction/Transaction.js'
6
- import PrivateKey from '../../primitives/PrivateKey.js'
7
- import TransactionSignature from '../../primitives/TransactionSignature.js'
8
- import { sha256 } from '../../primitives/Hash.js'
9
- import ScriptChunk from '../ScriptChunk.js'
10
- import BigNumber from '../../primitives/BigNumber.js'
11
-
12
- /**
13
- * RPuzzle class implementing ScriptTemplate.
14
- *
15
- * This class provides methods to create R Puzzle and R Puzzle Hash locking and unlocking scripts, including the unlocking of UTXOs with the correct K value.
16
- */
17
- export default class RPuzzle implements ScriptTemplate {
18
- type: 'raw' | 'SHA1' | 'SHA256' | 'HASH256' | 'RIPEMD160' | 'HASH160' = 'raw'
19
-
20
- /**
21
- * @constructor
22
- * Constructs an R Puzzle template instance for a given puzzle type
23
- *
24
- * @param {'raw'|'SHA1'|'SHA256'|'HASH256'|'RIPEMD160'|'HASH160'} type Denotes the type of puzzle to create
25
- */
26
- constructor (type: 'raw' | 'SHA1' | 'SHA256' | 'HASH256' | 'RIPEMD160' | 'HASH160' = 'raw') {
27
- this.type = type
28
- }
29
-
30
- /**
31
- * Creates an R puzzle locking script for a given R value or R value hash.
32
- *
33
- * @param {number[]} value - An array representing the R value or its hash.
34
- * @returns {LockingScript} - An R puzzle locking script.
35
- */
36
- lock (value: number[]): LockingScript {
37
- const chunks: ScriptChunk[] = [
38
- { op: OP.OP_OVER },
39
- { op: OP.OP_3 },
40
- { op: OP.OP_SPLIT },
41
- { op: OP.OP_NIP },
42
- { op: OP.OP_1 },
43
- { op: OP.OP_SPLIT },
44
- { op: OP.OP_SWAP },
45
- { op: OP.OP_SPLIT },
46
- { op: OP.OP_DROP }
47
- ]
48
- if (this.type !== 'raw') {
49
- chunks.push({
50
- op: OP['OP_' + this.type]
51
- })
52
- }
53
- chunks.push({ op: value.length, data: value })
54
- chunks.push({ op: OP.OP_EQUALVERIFY })
55
- chunks.push({ op: OP.OP_CHECKSIG })
56
- return new LockingScript(chunks)
57
- }
58
-
59
- /**
60
- * Creates a function that generates an R puzzle unlocking script along with its signature and length estimation.
61
- *
62
- * The returned object contains:
63
- * 1. `sign` - A function that, when invoked with a transaction and an input index,
64
- * produces an unlocking script suitable for an R puzzle locked output.
65
- * 2. `estimateLength` - A function that returns the estimated length of the unlocking script in bytes.
66
- *
67
- * @param {BigNumber} k — The K-value used to unlock the R-puzzle.
68
- * @param {PrivateKey} privateKey - The private key used for signing the transaction. If not provided, a random key will be generated.
69
- * @param {'all'|'none'|'single'} signOutputs - The signature scope for outputs.
70
- * @param {boolean} anyoneCanPay - Flag indicating if the signature allows for other inputs to be added later.
71
- * @returns {Object} - An object containing the `sign` and `estimateLength` functions.
72
- */
73
- unlock (
74
- k: BigNumber,
75
- privateKey: PrivateKey,
76
- signOutputs: 'all' | 'none' | 'single' = 'all',
77
- anyoneCanPay: boolean = false
78
- ): {
79
- sign: (tx: Transaction, inputIndex: number) => Promise<UnlockingScript>
80
- estimateLength: () => Promise<106>
81
- } {
82
- return {
83
- sign: async (tx: Transaction, inputIndex: number) => {
84
- if (typeof privateKey === 'undefined') {
85
- privateKey = PrivateKey.fromRandom()
86
- }
87
- let signatureScope = TransactionSignature.SIGHASH_FORKID
88
- if (signOutputs === 'all') {
89
- signatureScope |= TransactionSignature.SIGHASH_ALL
90
- }
91
- if (signOutputs === 'none') {
92
- signatureScope |= TransactionSignature.SIGHASH_NONE
93
- }
94
- if (signOutputs === 'single') {
95
- signatureScope |= TransactionSignature.SIGHASH_SINGLE
96
- }
97
- if (anyoneCanPay) {
98
- signatureScope |= TransactionSignature.SIGHASH_ANYONECANPAY
99
- }
100
- const otherInputs = [...tx.inputs]
101
- const [input] = otherInputs.splice(inputIndex, 1)
102
- if (typeof input.sourceTransaction !== 'object') {
103
- throw new Error(
104
- 'The source transaction is needed for transaction signing.'
105
- )
106
- }
107
- const preimage = TransactionSignature.format({
108
- sourceTXID: input.sourceTransaction.id('hex') as string,
109
- sourceOutputIndex: input.sourceOutputIndex,
110
- sourceSatoshis: input.sourceTransaction.outputs[input.sourceOutputIndex].satoshis,
111
- transactionVersion: tx.version,
112
- otherInputs,
113
- inputIndex,
114
- outputs: tx.outputs,
115
- inputSequence: input.sequence,
116
- subscript: input.sourceTransaction.outputs[input.sourceOutputIndex].lockingScript,
117
- lockTime: tx.lockTime,
118
- scope: signatureScope
119
- })
120
- const rawSignature = privateKey.sign(sha256(preimage), undefined, true, k)
121
- const sig = new TransactionSignature(
122
- rawSignature.r,
123
- rawSignature.s,
124
- signatureScope
125
- )
126
- const sigForScript = sig.toChecksigFormat()
127
- const pubkeyForScript = privateKey.toPublicKey().encode(true) as number[]
128
- return new UnlockingScript([
129
- { op: sigForScript.length, data: sigForScript },
130
- { op: pubkeyForScript.length, data: pubkeyForScript }
131
- ])
132
- },
133
- estimateLength: async () => {
134
- // public key (1+33) + signature (1+71)
135
- // Note: We add 1 to each element's length because of the associated OP_PUSH
136
- return 106
137
- }
138
- }
139
- }
140
- }
@@ -1,2 +0,0 @@
1
- export { default as P2PKH } from './P2PKH.js'
2
- export { default as RPuzzle } from './RPuzzle.js'
@@ -1,42 +0,0 @@
1
- import Transaction from './Transaction.js'
2
-
3
- /**
4
- * Defines the structure of a successful broadcast response.
5
- *
6
- * @interface
7
- * @property {string} status - The status of the response, indicating success.
8
- * @property {string} txid - The transaction ID of the broadcasted transaction.
9
- * @property {string} message - A human-readable success message.
10
- */
11
- export interface BroadcastResponse {
12
- status: 'success'
13
- txid: string
14
- message: string
15
- }
16
-
17
- /**
18
- * Defines the structure of a failed broadcast response.
19
- *
20
- * @interface
21
- * @property {string} status - The status of the response, indicating an error.
22
- * @property {string} code - A machine-readable error code representing the type of error encountered.
23
- * @property {string} description - A detailed description of the error.
24
- */
25
- export interface BroadcastFailure {
26
- status: 'error'
27
- code: string
28
- description: string
29
- }
30
-
31
- /**
32
- * Represents the interface for a transaction broadcaster.
33
- * This interface defines a standard method for broadcasting transactions.
34
- *
35
- * @interface
36
- * @property {function} broadcast - A function that takes a Transaction object and returns a promise.
37
- * The promise resolves to either a BroadcastResponse or a BroadcastFailure.
38
- */
39
- export interface Broadcaster {
40
- broadcast: (transaction: Transaction) =>
41
- Promise<BroadcastResponse | BroadcastFailure>
42
- }
@@ -1,22 +0,0 @@
1
- /**
2
- * The Chain Tracker is responsible for verifying the validity of a given Merkle root
3
- * for a specific block height within the blockchain.
4
- *
5
- * Chain Trackers ensure the integrity of the blockchain by
6
- * validating new headers against the chain's history. They use accumulated
7
- * proof-of-work and protocol adherence as metrics to assess the legitimacy of blocks.
8
- *
9
- * @interface ChainTracker
10
- * @function isValidRootForHeight - A method to verify the validity of a Merkle root
11
- * for a given block height.
12
- *
13
- * @example
14
- * const chainTracker = {
15
- * isValidRootForHeight: async (root, height) => {
16
- * // Implementation to check if the Merkle root is valid for the specified block height.
17
- * }
18
- * };
19
- */
20
- export default interface ChainTracker {
21
- isValidRootForHeight: (root: string, height: number) => Promise<boolean>
22
- }
@@ -1,13 +0,0 @@
1
- import Transaction from './Transaction.js'
2
- import BigNumber from '../primitives/BigNumber.js'
3
-
4
- /**
5
- * Represents the interface for a transaction fee model.
6
- * This interface defines a standard method for computing a fee when given a transaction.
7
- *
8
- * @interface
9
- * @property {function} computeFee - A function that takes a Transaction object and returns a BigNumber representing the number of satoshis the transaction should cost.
10
- */
11
- export default interface FeeModel {
12
- computeFee: (transaction: Transaction) => Promise<number>
13
- }
@@ -1,259 +0,0 @@
1
- import { Reader, Writer, toHex, toArray } from '../primitives/utils.js'
2
- import { hash256 } from '../primitives/Hash.js'
3
- import ChainTracker from './ChainTracker.js'
4
-
5
- /**
6
- * Represents a Merkle Path, which is used to provide a compact proof of inclusion for a
7
- * transaction in a block. This class encapsulates all the details required for creating
8
- * and verifying Merkle Proofs.
9
- *
10
- * @class MerklePath
11
- * @property {number} blockHeight - The height of the block in which the transaction is included.
12
- * @property {Array<Array<{offset: number, hash?: string, txid?: boolean, duplicate?: boolean}>>} path -
13
- * A tree structure representing the Merkle Path, with each level containing information
14
- * about the nodes involved in constructing the proof.
15
- *
16
- * @example
17
- * // Creating and verifying a Merkle Path
18
- * const merklePath = MerklePath.fromHex('...');
19
- * const isValid = merklePath.verify(txid, chainTracker);
20
- *
21
- * @description
22
- * The MerklePath class is useful for verifying transactions in a lightweight and efficient manner without
23
- * needing the entire block data. This class offers functionalities for creating, converting,
24
- * and verifying these proofs.
25
- */
26
- export default class MerklePath {
27
- blockHeight: number
28
- path: Array<Array<{
29
- offset: number
30
- hash?: string
31
- txid?: boolean
32
- duplicate?: boolean
33
- }>>
34
-
35
- /**
36
- * Creates a MerklePath instance from a hexadecimal string.
37
- *
38
- * @static
39
- * @param {string} hex - The hexadecimal string representation of the Merkle Path.
40
- * @returns {MerklePath} - A new MerklePath instance.
41
- */
42
- static fromHex (hex: string): MerklePath {
43
- return MerklePath.fromBinary(toArray(hex, 'hex'))
44
- }
45
-
46
- static fromReader (reader: Reader): MerklePath {
47
- const blockHeight = reader.readVarIntNum()
48
- const treeHeight = reader.readUInt8()
49
- const path = Array(treeHeight).fill(0).map(() => ([]))
50
- let flags, offset, nLeavesAtThisHeight
51
- for (let level = 0; level < treeHeight; level++) {
52
- nLeavesAtThisHeight = reader.readVarIntNum()
53
- while (nLeavesAtThisHeight) {
54
- offset = reader.readVarIntNum()
55
- flags = reader.readUInt8()
56
- const leaf: {
57
- offset: number
58
- hash?: string
59
- txid?: boolean
60
- duplicate?: boolean
61
- } = { offset }
62
- if (flags & 1) {
63
- leaf.duplicate = true
64
- } else {
65
- if (flags & 2) {
66
- leaf.txid = true
67
- }
68
- leaf.hash = toHex(reader.read(32).reverse())
69
- }
70
- path[level].push(leaf)
71
- nLeavesAtThisHeight--
72
- }
73
- path[level].sort((a, b) => a.offset - b.offset)
74
- }
75
- return new MerklePath(blockHeight, path)
76
- }
77
-
78
- /**
79
- * Creates a MerklePath instance from a binary array.
80
- *
81
- * @static
82
- * @param {number[]} bump - The binary array representation of the Merkle Path.
83
- * @returns {MerklePath} - A new MerklePath instance.
84
- */
85
- static fromBinary (bump: number[]): MerklePath {
86
- const reader = new Reader(bump)
87
- return MerklePath.fromReader(reader)
88
- }
89
-
90
- constructor (blockHeight: number, path: Array<Array<{
91
- offset: number
92
- hash?: string
93
- txid?: boolean
94
- duplicate?: boolean
95
- }>>) {
96
- this.blockHeight = blockHeight
97
- this.path = path
98
-
99
- // store all of the legal offsets which we expect given the txid indices.
100
- let legalOffsets = Array(this.path.length).fill(0).map(() => new Set())
101
- this.path.map((leaves, height) => {
102
- if (leaves.length === 0) {
103
- throw new Error(`Empty level at height: ${height}`)
104
- }
105
- const offsetsAtThisHeight = new Set()
106
- leaves.map(leaf => {
107
- if (offsetsAtThisHeight.has(leaf.offset)) throw new Error(`Duplicate offset: ${leaf.offset}, at height: ${height}`)
108
- offsetsAtThisHeight.add(leaf.offset)
109
- if (height === 0) {
110
- if (leaf.duplicate !== true) {
111
- for (let h = 1; h < this.path.length; h++) {
112
- legalOffsets[h].add(leaf.offset >> h ^ 1)
113
- }
114
- }
115
- } else {
116
- if (!legalOffsets[height].has(leaf.offset)) {
117
- throw new Error(`Invalid offset: ${leaf.offset}, at height: ${height}, with legal offsets: ${Array.from(legalOffsets[height]).join(', ')}`)
118
- }
119
- }
120
- })
121
- })
122
-
123
- let root: string
124
- // every txid must calculate to the same root.
125
- this.path[0].map((leaf, idx) => {
126
- if (idx === 0) root = this.computeRoot(leaf.hash)
127
- if (root !== this.computeRoot(leaf.hash)) {
128
- throw new Error('Mismatched roots')
129
- }
130
- })
131
- }
132
-
133
- /**
134
- * Converts the MerklePath to a binary array format.
135
- *
136
- * @returns {number[]} - The binary array representation of the Merkle Path.
137
- */
138
- toBinary (): number[] {
139
- const writer = new Writer()
140
- writer.writeVarIntNum(this.blockHeight)
141
- const treeHeight = this.path.length
142
- writer.writeUInt8(treeHeight)
143
- for (let level = 0; level < treeHeight; level++) {
144
- const nLeaves = Object.keys(this.path[level]).length
145
- writer.writeVarIntNum(nLeaves)
146
- for (const leaf of this.path[level]) {
147
- writer.writeVarIntNum(leaf.offset)
148
- let flags = 0
149
- if (leaf?.duplicate) {
150
- flags |= 1
151
- }
152
- if (leaf?.txid) {
153
- flags |= 2
154
- }
155
- writer.writeUInt8(flags)
156
- if ((flags & 1) === 0) {
157
- writer.write(toArray(leaf.hash, 'hex').reverse())
158
- }
159
- }
160
- }
161
- return writer.toArray()
162
- }
163
-
164
- /**
165
- * Converts the MerklePath to a hexadecimal string format.
166
- *
167
- * @returns {string} - The hexadecimal string representation of the Merkle Path.
168
- */
169
- toHex (): string {
170
- return toHex(this.toBinary())
171
- }
172
-
173
- /**
174
- * Computes the Merkle root from the provided transaction ID.
175
- *
176
- * @param {string} txid - The transaction ID to compute the Merkle root for. If not provided, the root will be computed from an unspecified branch, and not all branches will be validated!
177
- * @returns {string} - The computed Merkle root as a hexadecimal string.
178
- * @throws {Error} - If the transaction ID is not part of the Merkle Path.
179
- */
180
- computeRoot (txid?: string): string {
181
- if (typeof txid !== 'string') {
182
- txid = this.path[0].find(leaf => Boolean(leaf?.hash)).hash
183
- }
184
- // Find the index of the txid at the lowest level of the Merkle tree
185
- const index = this.path[0].find(l => l.hash === txid).offset
186
- if (typeof index !== 'number') {
187
- throw Error(`This proof does not contain the txid: ${txid}`)
188
- }
189
- // Calculate the root using the index as a way to determine which direction to concatenate.
190
- const hash = (m: string): string => toHex((
191
- hash256(toArray(m, 'hex').reverse()) as number[]
192
- ).reverse())
193
- let workingHash = txid
194
- for (let height = 0; height < this.path.length; height++) {
195
- const leaves = this.path[height]
196
- const offset = index >> height ^ 1
197
- const leaf = leaves.find(l => l.offset === offset)
198
- if (typeof leaf !== 'object') {
199
- throw new Error(`Missing hash for index ${index} at height ${height}`)
200
- }
201
- if (leaf.duplicate) {
202
- workingHash = hash(workingHash + workingHash)
203
- } else if (offset % 2 !== 0) {
204
- workingHash = hash(leaf.hash + workingHash)
205
- } else {
206
- workingHash = hash(workingHash + leaf.hash)
207
- }
208
- }
209
- return workingHash
210
- }
211
-
212
- /**
213
- * Verifies if the given transaction ID is part of the Merkle tree at the specified block height.
214
- *
215
- * @param {string} txid - The transaction ID to verify.
216
- * @param {ChainTracker} chainTracker - The ChainTracker instance used to verify the Merkle root.
217
- * @returns {boolean} - True if the transaction ID is valid within the Merkle Path at the specified block height.
218
- */
219
- async verify (txid: string, chainTracker: ChainTracker): Promise<boolean> {
220
- const root = this.computeRoot(txid)
221
- // Use the chain tracker to determine whether this is a valid merkle root at the given block height
222
- return await chainTracker.isValidRootForHeight(root, this.blockHeight)
223
- }
224
-
225
- /**
226
- * Combines this MerklePath with another to create a compound proof.
227
- *
228
- * @param {MerklePath} other - Another MerklePath to combine with this path.
229
- * @throws {Error} - If the paths have different block heights or roots.
230
- */
231
- combine (other: MerklePath): void {
232
- if (this.blockHeight !== other.blockHeight) {
233
- throw Error('You cannot combine paths which do not have the same block height.')
234
- }
235
- const root1 = this.computeRoot()
236
- const root2 = other.computeRoot()
237
- if (root1 !== root2) {
238
- throw Error('You cannot combine paths which do not have the same root.')
239
- }
240
- const combinedPath = []
241
- for (let h = 0; h < this.path.length; h++) {
242
- combinedPath.push([])
243
- for (let l = 0; l < this.path[h].length; l++) {
244
- combinedPath[h].push(this.path[h][l])
245
- }
246
- for (let l = 0; l < other.path[h].length; l++) {
247
- if (!(combinedPath[h].find(leaf => leaf.offset === other.path[h][l].offset) as boolean)) {
248
- combinedPath[h].push(other.path[h][l])
249
- } else {
250
- // Ensure that any elements which appear in both are not downgraded to a non txid.
251
- if (other.path[h][l]?.txid) {
252
- combinedPath[h].find(leaf => leaf.offset === other.path[h][l]).txid = true
253
- }
254
- }
255
- }
256
- }
257
- this.path = combinedPath
258
- }
259
- }