@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.
- package/README.md +25 -3
- package/package.json +9 -5
- package/.github/ISSUE_TEMPLATE/bug_report.md +0 -40
- package/.github/ISSUE_TEMPLATE/discussion.md +0 -24
- package/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md +0 -23
- package/CHANGELOG.md +0 -72
- package/CONTRIBUTING.md +0 -85
- package/ROADMAP.md +0 -3
- package/docs/getting-started/COMMONJS.md +0 -94
- package/docs/getting-started/REACT-TS.md +0 -131
- package/docs/getting-started/TS-NODE.md +0 -106
- package/docs/getting-started/VUE.md +0 -103
- package/jest.config.js +0 -6
- package/mod.ts +0 -8
- package/src/compat/BSM.ts +0 -51
- package/src/compat/ECIES.ts +0 -557
- package/src/compat/HD.ts +0 -348
- package/src/compat/Mnemonic.ts +0 -295
- package/src/compat/__tests/BSM.test.ts +0 -38
- package/src/compat/__tests/ECIES.test.ts +0 -90
- package/src/compat/__tests/HD.test.ts +0 -405
- package/src/compat/__tests/Mnemonic.test.ts +0 -177
- package/src/compat/__tests/Mnemonic.vectors.ts +0 -172
- package/src/compat/bip-39-wordlist-en.ts +0 -2053
- package/src/compat/index.ts +0 -4
- package/src/messages/EncryptedMessage.ts +0 -70
- package/src/messages/SignedMessage.ts +0 -87
- package/src/messages/__tests/EncryptedMessage.test.ts +0 -36
- package/src/messages/__tests/SignedMessage.test.ts +0 -53
- package/src/messages/index.ts +0 -2
- package/src/primitives/AESGCM.ts +0 -479
- package/src/primitives/BasePoint.ts +0 -21
- package/src/primitives/BigNumber.ts +0 -4619
- package/src/primitives/Curve.ts +0 -1163
- package/src/primitives/DRBG.ts +0 -102
- package/src/primitives/ECDSA.ts +0 -164
- package/src/primitives/Hash.ts +0 -1420
- package/src/primitives/JacobianPoint.ts +0 -410
- package/src/primitives/K256.ts +0 -116
- package/src/primitives/Mersenne.ts +0 -123
- package/src/primitives/MontgomoryMethod.ts +0 -160
- package/src/primitives/Point.ts +0 -852
- package/src/primitives/PrivateKey.ts +0 -195
- package/src/primitives/PublicKey.ts +0 -154
- package/src/primitives/Random.ts +0 -55
- package/src/primitives/ReductionContext.ts +0 -528
- package/src/primitives/Signature.ts +0 -235
- package/src/primitives/SymmetricKey.ts +0 -75
- package/src/primitives/TransactionSignature.ts +0 -189
- package/src/primitives/__tests/AESGCM.test.ts +0 -338
- package/src/primitives/__tests/BRC42.private.vectors.ts +0 -33
- package/src/primitives/__tests/BRC42.public.vectors.ts +0 -33
- package/src/primitives/__tests/BigNumber.arithmatic.test.ts +0 -572
- package/src/primitives/__tests/BigNumber.binary.test.ts +0 -203
- package/src/primitives/__tests/BigNumber.constructor.test.ts +0 -176
- package/src/primitives/__tests/BigNumber.dhGroup.test.ts +0 -18
- package/src/primitives/__tests/BigNumber.fixtures.ts +0 -264
- package/src/primitives/__tests/BigNumber.serializers.test.ts +0 -157
- package/src/primitives/__tests/BigNumber.utils.test.ts +0 -347
- package/src/primitives/__tests/Curve.unit.test.ts +0 -192
- package/src/primitives/__tests/DRBG.test.ts +0 -18
- package/src/primitives/__tests/DRBG.vectors.ts +0 -167
- package/src/primitives/__tests/ECDH.test.ts +0 -31
- package/src/primitives/__tests/ECDSA.test.ts +0 -58
- package/src/primitives/__tests/HMAC.test.ts +0 -59
- package/src/primitives/__tests/Hash.test.ts +0 -121
- package/src/primitives/__tests/PBKDF2.vectors.ts +0 -119
- package/src/primitives/__tests/PrivateKey.test.ts +0 -17
- package/src/primitives/__tests/PublicKey.test.ts +0 -66
- package/src/primitives/__tests/Random.test.ts +0 -14
- package/src/primitives/__tests/Reader.test.ts +0 -296
- package/src/primitives/__tests/ReductionContext.test.ts +0 -279
- package/src/primitives/__tests/SymmetricKey.test.ts +0 -58
- package/src/primitives/__tests/SymmetricKey.vectors.ts +0 -40
- package/src/primitives/__tests/Writer.test.ts +0 -198
- package/src/primitives/__tests/sighash.vectors.ts +0 -3503
- package/src/primitives/__tests/utils.test.ts +0 -108
- package/src/primitives/index.ts +0 -8
- package/src/primitives/utils.ts +0 -665
- package/src/script/LockingScript.ts +0 -30
- package/src/script/OP.ts +0 -219
- package/src/script/Script.ts +0 -426
- package/src/script/ScriptChunk.ts +0 -7
- package/src/script/ScriptTemplate.ts +0 -36
- package/src/script/Spend.ts +0 -1379
- package/src/script/UnlockingScript.ts +0 -30
- package/src/script/__tests/Script.test.ts +0 -369
- package/src/script/__tests/Spend.test.ts +0 -248
- package/src/script/__tests/script.invalid.vectors.ts +0 -925
- package/src/script/__tests/script.valid.vectors.ts +0 -1120
- package/src/script/__tests/scriptFromVector.ts +0 -42
- package/src/script/__tests/spend.valid.vectors.ts +0 -2288
- package/src/script/index.ts +0 -7
- package/src/script/templates/P2PKH.ts +0 -109
- package/src/script/templates/RPuzzle.ts +0 -140
- package/src/script/templates/index.ts +0 -2
- package/src/transaction/Broadcaster.ts +0 -42
- package/src/transaction/ChainTracker.ts +0 -22
- package/src/transaction/FeeModel.ts +0 -13
- package/src/transaction/MerklePath.ts +0 -259
- package/src/transaction/Transaction.ts +0 -602
- package/src/transaction/TransactionInput.ts +0 -63
- package/src/transaction/TransactionOutput.ts +0 -37
- package/src/transaction/__tests/MerklePath.test.ts +0 -181
- package/src/transaction/__tests/Transaction.test.ts +0 -413
- package/src/transaction/__tests/bigtx.vectors.ts +0 -4
- package/src/transaction/__tests/bump.invalid.vectors.ts +0 -8
- package/src/transaction/__tests/bump.valid.vectors.ts +0 -4
- package/src/transaction/__tests/tx.invalid.vectors.ts +0 -281
- package/src/transaction/__tests/tx.valid.vectors.ts +0 -364
- package/src/transaction/broadcasters/ARC.ts +0 -106
- package/src/transaction/broadcasters/__tests/ARC.test.ts +0 -115
- package/src/transaction/broadcasters/index.ts +0 -1
- package/src/transaction/fee-models/SatoshisPerKilobyte.ts +0 -71
- package/src/transaction/fee-models/index.ts +0 -1
- package/src/transaction/index.ts +0 -6
- package/ts2md.json +0 -5
- package/tsconfig.base.json +0 -26
- package/tsconfig.cjs.json +0 -11
- package/tsconfig.eslint.json +0 -12
- package/tsconfig.esm.json +0 -9
- package/tsconfig.json +0 -17
- package/tsconfig.types.json +0 -11
package/src/script/index.ts
DELETED
|
@@ -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,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
|
-
}
|