@vbyte/btc-dev 1.0.0
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/LICENSE +121 -0
- package/README.md +5 -0
- package/dist/class/index.d.ts +5 -0
- package/dist/class/index.js +5 -0
- package/dist/class/signer.d.ts +18 -0
- package/dist/class/signer.js +32 -0
- package/dist/class/tx.d.ts +38 -0
- package/dist/class/tx.js +73 -0
- package/dist/class/txin.d.ts +29 -0
- package/dist/class/txin.js +68 -0
- package/dist/class/txout.d.ts +18 -0
- package/dist/class/txout.js +38 -0
- package/dist/class/witness.d.ts +20 -0
- package/dist/class/witness.js +57 -0
- package/dist/const.d.ts +22 -0
- package/dist/const.js +36 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +10 -0
- package/dist/lib/address/encode.d.ts +3 -0
- package/dist/lib/address/encode.js +79 -0
- package/dist/lib/address/index.d.ts +20 -0
- package/dist/lib/address/index.js +21 -0
- package/dist/lib/address/p2pkh.d.ts +10 -0
- package/dist/lib/address/p2pkh.js +33 -0
- package/dist/lib/address/p2sh.d.ts +10 -0
- package/dist/lib/address/p2sh.js +33 -0
- package/dist/lib/address/p2tr.d.ts +8 -0
- package/dist/lib/address/p2tr.js +26 -0
- package/dist/lib/address/p2wpkh.d.ts +10 -0
- package/dist/lib/address/p2wpkh.js +34 -0
- package/dist/lib/address/p2wsh.d.ts +10 -0
- package/dist/lib/address/p2wsh.js +33 -0
- package/dist/lib/address/script.d.ts +5 -0
- package/dist/lib/address/script.js +46 -0
- package/dist/lib/address/util.d.ts +4 -0
- package/dist/lib/address/util.js +57 -0
- package/dist/lib/meta/index.d.ts +2 -0
- package/dist/lib/meta/index.js +2 -0
- package/dist/lib/meta/pointer.d.ts +42 -0
- package/dist/lib/meta/pointer.js +69 -0
- package/dist/lib/meta/scribe.d.ts +7 -0
- package/dist/lib/meta/scribe.js +192 -0
- package/dist/lib/psbt/encoder.d.ts +5 -0
- package/dist/lib/psbt/encoder.js +21 -0
- package/dist/lib/psbt/index.d.ts +4 -0
- package/dist/lib/psbt/index.js +4 -0
- package/dist/lib/psbt/meta.d.ts +3 -0
- package/dist/lib/psbt/meta.js +11 -0
- package/dist/lib/psbt/util.d.ts +5 -0
- package/dist/lib/psbt/util.js +44 -0
- package/dist/lib/psbt/validate.d.ts +2 -0
- package/dist/lib/psbt/validate.js +11 -0
- package/dist/lib/script/decode.d.ts +2 -0
- package/dist/lib/script/decode.js +55 -0
- package/dist/lib/script/encode.d.ts +6 -0
- package/dist/lib/script/encode.js +80 -0
- package/dist/lib/script/index.d.ts +126 -0
- package/dist/lib/script/index.js +17 -0
- package/dist/lib/script/util.d.ts +2 -0
- package/dist/lib/script/util.js +10 -0
- package/dist/lib/script/words.d.ts +116 -0
- package/dist/lib/script/words.js +164 -0
- package/dist/lib/sighash/index.d.ts +5 -0
- package/dist/lib/sighash/index.js +5 -0
- package/dist/lib/sighash/segwit.d.ts +3 -0
- package/dist/lib/sighash/segwit.js +89 -0
- package/dist/lib/sighash/sign.d.ts +3 -0
- package/dist/lib/sighash/sign.js +20 -0
- package/dist/lib/sighash/taproot.d.ts +9 -0
- package/dist/lib/sighash/taproot.js +124 -0
- package/dist/lib/sighash/util.d.ts +3 -0
- package/dist/lib/sighash/util.js +16 -0
- package/dist/lib/sighash/verify.d.ts +1 -0
- package/dist/lib/sighash/verify.js +1 -0
- package/dist/lib/taproot/cblock.d.ts +3 -0
- package/dist/lib/taproot/cblock.js +55 -0
- package/dist/lib/taproot/encode.d.ts +6 -0
- package/dist/lib/taproot/encode.js +26 -0
- package/dist/lib/taproot/index.d.ts +4 -0
- package/dist/lib/taproot/index.js +4 -0
- package/dist/lib/taproot/parse.d.ts +11 -0
- package/dist/lib/taproot/parse.js +47 -0
- package/dist/lib/taproot/tree.d.ts +3 -0
- package/dist/lib/taproot/tree.js +49 -0
- package/dist/lib/tx/create.d.ts +6 -0
- package/dist/lib/tx/create.js +54 -0
- package/dist/lib/tx/decode.d.ts +9 -0
- package/dist/lib/tx/decode.js +109 -0
- package/dist/lib/tx/encode.d.ts +15 -0
- package/dist/lib/tx/encode.js +94 -0
- package/dist/lib/tx/index.d.ts +10 -0
- package/dist/lib/tx/index.js +10 -0
- package/dist/lib/tx/locktime.d.ts +7 -0
- package/dist/lib/tx/locktime.js +37 -0
- package/dist/lib/tx/meta.d.ts +8 -0
- package/dist/lib/tx/meta.js +48 -0
- package/dist/lib/tx/parse.d.ts +2 -0
- package/dist/lib/tx/parse.js +12 -0
- package/dist/lib/tx/sequence.d.ts +7 -0
- package/dist/lib/tx/sequence.js +65 -0
- package/dist/lib/tx/size.d.ts +10 -0
- package/dist/lib/tx/size.js +48 -0
- package/dist/lib/tx/validate.d.ts +7 -0
- package/dist/lib/tx/validate.js +21 -0
- package/dist/lib/tx/witness.d.ts +3 -0
- package/dist/lib/tx/witness.js +85 -0
- package/dist/main.cjs +14625 -0
- package/dist/main.cjs.map +1 -0
- package/dist/module.mjs +14610 -0
- package/dist/module.mjs.map +1 -0
- package/dist/package.json +106 -0
- package/dist/schema/index.d.ts +2 -0
- package/dist/schema/index.js +2 -0
- package/dist/schema/taproot.d.ts +18 -0
- package/dist/schema/taproot.js +9 -0
- package/dist/schema/tx.d.ts +278 -0
- package/dist/schema/tx.js +35 -0
- package/dist/script.js +15 -0
- package/dist/script.js.map +1 -0
- package/dist/types/address.d.ts +34 -0
- package/dist/types/address.js +1 -0
- package/dist/types/index.d.ts +9 -0
- package/dist/types/index.js +9 -0
- package/dist/types/meta.d.ts +10 -0
- package/dist/types/meta.js +1 -0
- package/dist/types/psbt.d.ts +9 -0
- package/dist/types/psbt.js +1 -0
- package/dist/types/sighash.d.ts +14 -0
- package/dist/types/sighash.js +1 -0
- package/dist/types/taproot.d.ts +35 -0
- package/dist/types/taproot.js +1 -0
- package/dist/types/transaction.d.ts +81 -0
- package/dist/types/transaction.js +1 -0
- package/dist/types/txdata.d.ts +45 -0
- package/dist/types/txdata.js +1 -0
- package/dist/types/txmeta.d.ts +19 -0
- package/dist/types/txmeta.js +1 -0
- package/dist/types/witness.d.ts +30 -0
- package/dist/types/witness.js +1 -0
- package/package.json +106 -0
- package/src/class/index.ts +5 -0
- package/src/class/signer.ts +47 -0
- package/src/class/tx.ts +118 -0
- package/src/class/txin.ts +95 -0
- package/src/class/txout.ts +57 -0
- package/src/class/witness.ts +85 -0
- package/src/const.ts +43 -0
- package/src/index.ts +14 -0
- package/src/lib/address/encode.ts +183 -0
- package/src/lib/address/index.ts +24 -0
- package/src/lib/address/p2pkh.ts +65 -0
- package/src/lib/address/p2sh.ts +65 -0
- package/src/lib/address/p2tr.ts +51 -0
- package/src/lib/address/p2wpkh.ts +67 -0
- package/src/lib/address/p2wsh.ts +65 -0
- package/src/lib/address/script.ts +63 -0
- package/src/lib/address/util.ts +102 -0
- package/src/lib/meta/index.ts +2 -0
- package/src/lib/meta/pointer.ts +107 -0
- package/src/lib/meta/scribe.ts +251 -0
- package/src/lib/psbt/encoder.ts +24 -0
- package/src/lib/psbt/index.ts +4 -0
- package/src/lib/psbt/meta.ts +15 -0
- package/src/lib/psbt/util.ts +62 -0
- package/src/lib/psbt/validate.ts +18 -0
- package/src/lib/script/decode.ts +75 -0
- package/src/lib/script/encode.ts +130 -0
- package/src/lib/script/index.ts +26 -0
- package/src/lib/script/util.ts +78 -0
- package/src/lib/script/words.ts +182 -0
- package/src/lib/sighash/index.ts +5 -0
- package/src/lib/sighash/segwit.ts +152 -0
- package/src/lib/sighash/sign.ts +35 -0
- package/src/lib/sighash/taproot.ts +236 -0
- package/src/lib/sighash/util.ts +29 -0
- package/src/lib/sighash/verify.ts +83 -0
- package/src/lib/taproot/cblock.ts +95 -0
- package/src/lib/taproot/encode.ts +49 -0
- package/src/lib/taproot/index.ts +4 -0
- package/src/lib/taproot/parse.ts +65 -0
- package/src/lib/taproot/tree.ts +94 -0
- package/src/lib/tx/create.ts +82 -0
- package/src/lib/tx/decode.ts +145 -0
- package/src/lib/tx/encode.ts +154 -0
- package/src/lib/tx/index.ts +10 -0
- package/src/lib/tx/locktime.ts +57 -0
- package/src/lib/tx/meta.ts +73 -0
- package/src/lib/tx/parse.ts +16 -0
- package/src/lib/tx/sequence.ts +146 -0
- package/src/lib/tx/size.ts +77 -0
- package/src/lib/tx/validate.ts +36 -0
- package/src/lib/tx/witness.ts +122 -0
- package/src/schema/index.ts +2 -0
- package/src/schema/taproot.ts +12 -0
- package/src/schema/tx.ts +42 -0
- package/src/types/address.ts +39 -0
- package/src/types/index.ts +9 -0
- package/src/types/meta.ts +10 -0
- package/src/types/psbt.ts +15 -0
- package/src/types/sighash.ts +16 -0
- package/src/types/taproot.ts +40 -0
- package/src/types/transaction.ts +98 -0
- package/src/types/txdata.ts +53 -0
- package/src/types/txmeta.ts +25 -0
- package/src/types/witness.ts +36 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { Buff, Stream } from '@vbyte/buff'
|
|
2
|
+
import { Assert, ECC } from '@vbyte/micro-lib'
|
|
3
|
+
import { parse_witness_data } from '@/lib/tx/witness.js'
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
encode_tapbranch,
|
|
7
|
+
encode_tapscript,
|
|
8
|
+
encode_taptweak,
|
|
9
|
+
} from './encode.js'
|
|
10
|
+
|
|
11
|
+
import type { ControlBlock } from '@/types/index.js'
|
|
12
|
+
|
|
13
|
+
export function parse_taproot_witness (witness : string[]) {
|
|
14
|
+
const { cblock, params, script } = parse_witness_data(witness)
|
|
15
|
+
|
|
16
|
+
Assert.exists(cblock, 'cblock is null')
|
|
17
|
+
Assert.exists(script, 'script is null')
|
|
18
|
+
|
|
19
|
+
const cblk = parse_cblock(cblock)
|
|
20
|
+
const target = encode_tapscript(script, cblk.version)
|
|
21
|
+
|
|
22
|
+
let branch = target.hex
|
|
23
|
+
|
|
24
|
+
for (const leaf of cblk.path) {
|
|
25
|
+
branch = encode_tapbranch(branch, leaf).hex
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const tweak = encode_taptweak(cblk.int_key, branch)
|
|
29
|
+
const tapkey = ECC.tweak_pubkey(cblk.int_key, tweak, 'bip340')
|
|
30
|
+
|
|
31
|
+
params.map(e => Buff.bytes(e).hex)
|
|
32
|
+
|
|
33
|
+
return { cblock: cblk, params, script, tapkey: tapkey.hex, tweak: tweak.hex }
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function parse_cblock (cblock : string | Uint8Array) : ControlBlock {
|
|
37
|
+
const buffer = new Stream(cblock)
|
|
38
|
+
const cbyte = buffer.read(1).num
|
|
39
|
+
const int_key = buffer.read(32).hex
|
|
40
|
+
const [ version, parity ] = parse_cblock_parity(cbyte)
|
|
41
|
+
const path = []
|
|
42
|
+
while (buffer.size >= 32) {
|
|
43
|
+
path.push(buffer.read(32).hex)
|
|
44
|
+
}
|
|
45
|
+
if (buffer.size !== 0) {
|
|
46
|
+
throw new Error('Non-empty buffer on control block: ' + String(buffer))
|
|
47
|
+
}
|
|
48
|
+
return { int_key, path, parity, version }
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function parse_cblock_parity (cbits : number) {
|
|
52
|
+
return (cbits % 2 === 0)
|
|
53
|
+
? [ cbits - 0, 0x02 ]
|
|
54
|
+
: [ cbits - 1, 0x03 ]
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function parse_pubkey_parity (
|
|
58
|
+
pubkey : string | Uint8Array
|
|
59
|
+
) : number {
|
|
60
|
+
Assert.size(pubkey, 33, 'invalid pubkey size')
|
|
61
|
+
const [ parity ] = Buff.bytes(pubkey)
|
|
62
|
+
if (parity === 0x02) return 0
|
|
63
|
+
if (parity === 0x03) return 1
|
|
64
|
+
throw new Error('Invalid parity bit: ' + String(parity))
|
|
65
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { Buff } from '@vbyte/buff'
|
|
2
|
+
import { encode_tapbranch } from './encode.js'
|
|
3
|
+
|
|
4
|
+
import type { TapTree, MerkleProof } from '@/types/index.js'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Get the root of a taproot tree.
|
|
8
|
+
* @param leaves - The leaves of the tree.
|
|
9
|
+
* @returns The root of the tree.
|
|
10
|
+
*/
|
|
11
|
+
export function get_merkle_root (leaves : TapTree) {
|
|
12
|
+
// Process the merkle tree, and return the root.
|
|
13
|
+
return merkleize(leaves)[0]
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Process a taproot tree into a merkle proof.
|
|
18
|
+
* @param taptree - The leaves of the tree.
|
|
19
|
+
* @param target - The target leaf of the tree.
|
|
20
|
+
* @param path - The recursive path of the tree.
|
|
21
|
+
* @returns The root of the tree.
|
|
22
|
+
*/
|
|
23
|
+
export function merkleize (
|
|
24
|
+
taptree : TapTree,
|
|
25
|
+
target ?: string,
|
|
26
|
+
path : string[] = []
|
|
27
|
+
) : MerkleProof {
|
|
28
|
+
// Initialize the leaves and tree arrays.
|
|
29
|
+
const leaves : string[] = []
|
|
30
|
+
const tree : string[] = []
|
|
31
|
+
// If there are no leaves, throw an error.
|
|
32
|
+
if (taptree.length < 1) {
|
|
33
|
+
throw new Error('Tree is empty!')
|
|
34
|
+
}
|
|
35
|
+
// Crawl through the tree, and find each leaf.
|
|
36
|
+
for (let i = 0; i < taptree.length; i++) {
|
|
37
|
+
// Get the current leaf as bytes.
|
|
38
|
+
const bytes = taptree[i]
|
|
39
|
+
// If the leaf is an array,
|
|
40
|
+
if (Array.isArray(bytes)) {
|
|
41
|
+
// Recursively process the nested tree.
|
|
42
|
+
let [ tapleaf, new_target, branches ] = merkleize(bytes, target)
|
|
43
|
+
// Update the target leaf.
|
|
44
|
+
target = new_target
|
|
45
|
+
// Add the nested tapleaf to the leaves array.
|
|
46
|
+
leaves.push(tapleaf)
|
|
47
|
+
// For each branch node,
|
|
48
|
+
for (const branch of branches) {
|
|
49
|
+
// Add the branch to the path.
|
|
50
|
+
path.push(branch)
|
|
51
|
+
}
|
|
52
|
+
} else {
|
|
53
|
+
// Convert the leaf to a hex string.
|
|
54
|
+
const leaf = Buff.bytes(bytes).hex
|
|
55
|
+
// Add the leaf to the leaves array.
|
|
56
|
+
leaves.push(leaf)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// If there is only one leaf,
|
|
61
|
+
if (leaves.length === 1) {
|
|
62
|
+
// Return the leaf as the root.
|
|
63
|
+
return [ leaves[0], target, path ]
|
|
64
|
+
}
|
|
65
|
+
// Ensure the tree is sorted at this point.
|
|
66
|
+
leaves.sort()
|
|
67
|
+
// Ensure the tree is balanced evenly.
|
|
68
|
+
if (leaves.length % 2 !== 0) {
|
|
69
|
+
// If uneven, duplicate the last leaf.
|
|
70
|
+
leaves.push(leaves[leaves.length - 1])
|
|
71
|
+
}
|
|
72
|
+
// Sort through the leaves (two at a time).
|
|
73
|
+
for (let i = 0; i < leaves.length - 1; i += 2) {
|
|
74
|
+
// Compute two leaves into a branch.
|
|
75
|
+
const branch = encode_tapbranch(leaves[i], leaves[i + 1]).hex
|
|
76
|
+
// Push our branch to the tree.
|
|
77
|
+
tree.push(branch)
|
|
78
|
+
// Check if a proof target is specified.
|
|
79
|
+
if (typeof target === 'string') {
|
|
80
|
+
// Check if this branch is part of our proofs.
|
|
81
|
+
if (target === leaves[i]) {
|
|
82
|
+
// If so, include right-side of branch.
|
|
83
|
+
path.push(leaves[i + 1])
|
|
84
|
+
target = branch
|
|
85
|
+
} else if (target === leaves[i + 1]) {
|
|
86
|
+
// If so, include left-side of branch.
|
|
87
|
+
path.push(leaves[i])
|
|
88
|
+
target = branch
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
// Recursively process the tree.
|
|
93
|
+
return merkleize(tree, target, path)
|
|
94
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { Assert } from '@vbyte/micro-lib'
|
|
2
|
+
import { COINBASE, DEFAULT } from '@/const.js'
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
assert_tx_output,
|
|
6
|
+
assert_tx_template,
|
|
7
|
+
assert_vin_template
|
|
8
|
+
} from './validate.js'
|
|
9
|
+
|
|
10
|
+
import type {
|
|
11
|
+
TxData,
|
|
12
|
+
TxInputTemplate,
|
|
13
|
+
TxInput,
|
|
14
|
+
TxOutput,
|
|
15
|
+
TxTemplate,
|
|
16
|
+
SpendInput,
|
|
17
|
+
CoinbaseInput
|
|
18
|
+
} from '@/types/index.js'
|
|
19
|
+
|
|
20
|
+
export function create_coinbase_input (
|
|
21
|
+
config : TxInputTemplate | TxInput
|
|
22
|
+
) : CoinbaseInput {
|
|
23
|
+
assert_vin_template(config)
|
|
24
|
+
Assert.exists(config.coinbase, 'coinbase is required')
|
|
25
|
+
const coinbase = config.coinbase
|
|
26
|
+
const prevout = null
|
|
27
|
+
const script_sig = null
|
|
28
|
+
const sequence = config.sequence ?? DEFAULT.SEQUENCE
|
|
29
|
+
const txid = COINBASE.TXID
|
|
30
|
+
const vout = COINBASE.VOUT
|
|
31
|
+
const witness = config.witness ?? []
|
|
32
|
+
return { coinbase, prevout, script_sig, sequence, witness, txid, vout }
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function create_spend_input (
|
|
36
|
+
config : TxInputTemplate | TxInput
|
|
37
|
+
) : SpendInput {
|
|
38
|
+
assert_vin_template(config)
|
|
39
|
+
Assert.exists(config.prevout, 'prevout is required')
|
|
40
|
+
const prevout = config.prevout
|
|
41
|
+
const coinbase = null
|
|
42
|
+
const script_sig = config.script_sig ?? null
|
|
43
|
+
const sequence = config.sequence ?? DEFAULT.SEQUENCE
|
|
44
|
+
const witness = config.witness ?? []
|
|
45
|
+
return { ...config, coinbase, prevout, script_sig, sequence, witness }
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function create_tx_input (
|
|
49
|
+
config : TxInputTemplate | TxInput
|
|
50
|
+
) : TxInput {
|
|
51
|
+
assert_vin_template(config)
|
|
52
|
+
Assert.exists(config.txid, 'txid is required')
|
|
53
|
+
Assert.exists(config.vout, 'vout is required')
|
|
54
|
+
const coinbase = config.coinbase ?? null
|
|
55
|
+
const prevout = config.prevout ?? null
|
|
56
|
+
const script_sig = config.script_sig ?? null
|
|
57
|
+
const sequence = config.sequence ?? DEFAULT.SEQUENCE
|
|
58
|
+
const witness = config.witness ?? []
|
|
59
|
+
if (coinbase !== null) return create_coinbase_input(config)
|
|
60
|
+
if (prevout !== null) return create_spend_input(config)
|
|
61
|
+
return { ...config, coinbase, prevout, script_sig, sequence, witness }
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function create_tx_output (
|
|
65
|
+
config : TxOutput
|
|
66
|
+
) : TxOutput {
|
|
67
|
+
assert_tx_output(config)
|
|
68
|
+
const { script_pk, value } = config
|
|
69
|
+
return { script_pk, value : BigInt(value) }
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function create_tx_data (
|
|
73
|
+
config: TxTemplate | TxData
|
|
74
|
+
) : TxData {
|
|
75
|
+
assert_tx_template(config)
|
|
76
|
+
const { vin = [], vout = [] } = config
|
|
77
|
+
const locktime = config.locktime ?? DEFAULT.LOCKTIME
|
|
78
|
+
const version = config.version ?? DEFAULT.VERSION
|
|
79
|
+
const inputs = vin.map(txin => create_tx_input(txin))
|
|
80
|
+
const outputs = vout.map(txout => create_tx_output(txout))
|
|
81
|
+
return { locktime, vin : inputs, vout : outputs, version }
|
|
82
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { Bytes, Stream } from '@vbyte/buff'
|
|
2
|
+
import { Assert } from '@vbyte/micro-lib'
|
|
3
|
+
import { parse_error } from '@vbyte/micro-lib/util'
|
|
4
|
+
import { COINBASE } from '@/const.js'
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
TxData,
|
|
8
|
+
TxInput,
|
|
9
|
+
TxOutput,
|
|
10
|
+
CoinbaseInput,
|
|
11
|
+
VirtualInput,
|
|
12
|
+
SpendInput
|
|
13
|
+
} from '@/types/index.js'
|
|
14
|
+
|
|
15
|
+
interface TxEncoderConfig {
|
|
16
|
+
prevouts : TxOutput[]
|
|
17
|
+
segwit : boolean
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const DEFAULT_CONFIG : TxEncoderConfig = {
|
|
21
|
+
prevouts : [],
|
|
22
|
+
segwit : true
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function decode_tx_data (
|
|
26
|
+
txbytes : Bytes,
|
|
27
|
+
options : Partial<TxEncoderConfig> = {}
|
|
28
|
+
) : TxData {
|
|
29
|
+
// Merge the options with the default config.
|
|
30
|
+
const config = { ...DEFAULT_CONFIG, ...options }
|
|
31
|
+
// Assert the txhex is a bytes object.
|
|
32
|
+
Assert.is_bytes(txbytes, 'txbytes must be hex or a unit array')
|
|
33
|
+
// Setup a byte-stream.
|
|
34
|
+
const stream = new Stream(txbytes)
|
|
35
|
+
// Parse tx version.
|
|
36
|
+
const version = read_version(stream)
|
|
37
|
+
// Check and enable any flags that are set.
|
|
38
|
+
const has_witness = (config.segwit)
|
|
39
|
+
? check_witness_flag(stream)
|
|
40
|
+
: false
|
|
41
|
+
// Parse our inputs and outputs.
|
|
42
|
+
const vin = read_inputs(stream, config.prevouts)
|
|
43
|
+
const vout = read_outputs(stream)
|
|
44
|
+
// If witness flag is set, parse witness data.
|
|
45
|
+
if (has_witness) {
|
|
46
|
+
for (const txin of vin) {
|
|
47
|
+
txin.witness = read_witness(stream)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
// Parse locktime.
|
|
51
|
+
const locktime = read_locktime(stream)
|
|
52
|
+
// Return transaction object with calculated fields.
|
|
53
|
+
return { version, vin, vout, locktime }
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function read_version (stream : Stream) : number {
|
|
57
|
+
return stream.read(4).reverse().to_num()
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function check_witness_flag (stream : Stream) : boolean {
|
|
61
|
+
const [ marker, flag ] : number[] = [ ...stream.peek(2) ]
|
|
62
|
+
if (marker === 0) {
|
|
63
|
+
stream.read(2)
|
|
64
|
+
if (flag === 1) {
|
|
65
|
+
return true
|
|
66
|
+
} else {
|
|
67
|
+
throw new Error(`Invalid witness flag: ${flag}`)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return false
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function read_inputs (stream : Stream, prevouts : TxOutput[]) : TxInput[] {
|
|
74
|
+
const inputs = []
|
|
75
|
+
const vinCount = stream.varint()
|
|
76
|
+
for (let i = 0; i < vinCount; i++) {
|
|
77
|
+
const txinput = read_vin(stream, prevouts.at(i))
|
|
78
|
+
inputs.push(txinput)
|
|
79
|
+
}
|
|
80
|
+
return inputs
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function read_vin (stream : Stream, prevout : TxOutput | null = null) : TxInput {
|
|
84
|
+
const txid = stream.read(32).reverse().hex
|
|
85
|
+
const vout = stream.read(4).reverse().num
|
|
86
|
+
const script_sig = read_script(stream, true)
|
|
87
|
+
const sequence = stream.read(4).reverse().num
|
|
88
|
+
const witness : string[] = []
|
|
89
|
+
if (txid === COINBASE.TXID && vout === COINBASE.VOUT) {
|
|
90
|
+
return { coinbase : script_sig, prevout: null, script_sig : null, sequence, txid, vout, witness } as CoinbaseInput
|
|
91
|
+
} else if (prevout !== null) {
|
|
92
|
+
return { coinbase : null, prevout, script_sig, sequence, txid, vout, witness } as SpendInput
|
|
93
|
+
} else {
|
|
94
|
+
return { coinbase : null, prevout, script_sig, sequence, txid, vout, witness } as VirtualInput
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function read_outputs (stream : Stream) : TxOutput[] {
|
|
99
|
+
const outputs = []
|
|
100
|
+
const vcount = stream.varint()
|
|
101
|
+
for (let i = 0; i < vcount; i++) {
|
|
102
|
+
try {
|
|
103
|
+
outputs.push(read_vout(stream))
|
|
104
|
+
} catch (error) {
|
|
105
|
+
throw new Error(`failed to decode output: ${i}: ${parse_error(error)}`)
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return outputs
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function read_vout (stream : Stream) : TxOutput {
|
|
112
|
+
const value = stream.read(8).reverse().big
|
|
113
|
+
const script_pk = read_script(stream, true)
|
|
114
|
+
Assert.exists(script_pk, 'failed to decode script_pk')
|
|
115
|
+
return { value, script_pk }
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function read_witness (stream : Stream) : string[] {
|
|
119
|
+
const stack = []
|
|
120
|
+
const count = stream.varint()
|
|
121
|
+
for (let i = 0; i < count; i++) {
|
|
122
|
+
const element = read_script(stream, true)
|
|
123
|
+
if (element === null) {
|
|
124
|
+
throw new Error('failed to decode witness element: ' + i)
|
|
125
|
+
}
|
|
126
|
+
stack.push(element)
|
|
127
|
+
}
|
|
128
|
+
return stack
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function read_script (
|
|
132
|
+
stream : Stream,
|
|
133
|
+
varint ?: boolean
|
|
134
|
+
) : string | null {
|
|
135
|
+
const size = (varint === true)
|
|
136
|
+
? stream.varint('le')
|
|
137
|
+
: stream.size
|
|
138
|
+
return size > 0
|
|
139
|
+
? stream.read(size).hex
|
|
140
|
+
: null
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function read_locktime (stream : Stream) : number {
|
|
144
|
+
return stream.read(4).reverse().to_num()
|
|
145
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { Buff } from '@vbyte/buff'
|
|
2
|
+
import { Assert } from '@vbyte/micro-lib'
|
|
3
|
+
import { parse_tx_data } from './parse.js'
|
|
4
|
+
import { COINBASE } from '@/const.js'
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
TxInput,
|
|
8
|
+
TxOutput,
|
|
9
|
+
TxData
|
|
10
|
+
} from '@/types/index.js'
|
|
11
|
+
|
|
12
|
+
export function encode_tx_data (
|
|
13
|
+
txdata : TxData,
|
|
14
|
+
segwit = true
|
|
15
|
+
) : Buff {
|
|
16
|
+
const tx = parse_tx_data(txdata)
|
|
17
|
+
// Unpack the transaction data.
|
|
18
|
+
const { version, vin, vout, locktime } = tx
|
|
19
|
+
// Create a buffer for the transaction.
|
|
20
|
+
const buffer : Buff[] = [ encode_tx_version(version) ]
|
|
21
|
+
// If the transaction is a segwit transaction,
|
|
22
|
+
if (segwit) {
|
|
23
|
+
// Add the segwit marker to the buffer.
|
|
24
|
+
buffer.push(Buff.hex('0001'))
|
|
25
|
+
}
|
|
26
|
+
// Add the inputs to the buffer.
|
|
27
|
+
buffer.push(encode_tx_inputs(vin))
|
|
28
|
+
// Add the outputs to the buffer.
|
|
29
|
+
buffer.push(encode_tx_outputs(vout))
|
|
30
|
+
// If the transaction is a segwit transaction,
|
|
31
|
+
if (segwit) {
|
|
32
|
+
// For each input in the transaction,
|
|
33
|
+
for (const input of vin) {
|
|
34
|
+
// Add the witness data to the buffer.
|
|
35
|
+
buffer.push(encode_vin_witness(input.witness))
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
// Add the locktime to the buffer.
|
|
39
|
+
buffer.push(encode_tx_locktime(locktime))
|
|
40
|
+
// Return the buffer as a single payload.
|
|
41
|
+
return Buff.join(buffer)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function encode_tx_version (num : number) : Buff {
|
|
45
|
+
// Encode the transaction version as a 4-byte little-endian number.
|
|
46
|
+
return Buff.num(num, 4).reverse()
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function encode_txin_txid (txid : string) : Buff {
|
|
50
|
+
// Encode the transaction ID as a 32-byte little-endian number.
|
|
51
|
+
return Buff.hex(txid, 32).reverse()
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function encode_txin_vout (vout : number) : Buff {
|
|
55
|
+
// Encode the output index as a 4-byte little-endian number.
|
|
56
|
+
return Buff.num(vout, 4).reverse()
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function encode_txin_sequence (sequence : number) : Buff {
|
|
60
|
+
// Encode the sequence number as a 4-byte little-endian number.
|
|
61
|
+
return Buff.num(sequence, 4).reverse()
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function encode_tx_inputs (vin : TxInput[]) : Buff {
|
|
65
|
+
// Create a buffer for the inputs, starting with the array length.
|
|
66
|
+
const raw : Buff[] = [ Buff.varint(vin.length, 'le') ]
|
|
67
|
+
// For each input in the array,
|
|
68
|
+
for (const input of vin) {
|
|
69
|
+
// Encode the input, and add it to the buffer.
|
|
70
|
+
raw.push(encode_vin(input))
|
|
71
|
+
}
|
|
72
|
+
// Return the buffer as a single payload.
|
|
73
|
+
return Buff.join(raw)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function encode_vin (txin : TxInput) : Buff {
|
|
77
|
+
// If the input is a coinbase,
|
|
78
|
+
if (txin.coinbase !== null) {
|
|
79
|
+
// Encode and return the coinbase as a single payload.
|
|
80
|
+
return Buff.join([
|
|
81
|
+
encode_txin_txid(COINBASE.TXID),
|
|
82
|
+
encode_txin_vout(COINBASE.VOUT),
|
|
83
|
+
encode_script_data(txin.coinbase),
|
|
84
|
+
encode_txin_sequence(txin.sequence)
|
|
85
|
+
])
|
|
86
|
+
} else {
|
|
87
|
+
// Encode and return the input as a single payload.
|
|
88
|
+
return Buff.join([
|
|
89
|
+
encode_txin_txid(txin.txid),
|
|
90
|
+
encode_txin_vout(txin.vout),
|
|
91
|
+
encode_script_data(txin.script_sig),
|
|
92
|
+
encode_txin_sequence(txin.sequence)
|
|
93
|
+
])
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function encode_vout_value (value : bigint) : Buff {
|
|
98
|
+
// Encode the value as an 8-byte little-endian number.
|
|
99
|
+
return Buff.big(value, 8).reverse()
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function encode_tx_outputs (vout : TxOutput[]) : Buff {
|
|
103
|
+
// Create a buffer for the outputs, starting with the array length.
|
|
104
|
+
const buffer : Buff[] = [ Buff.varint(vout.length, 'le') ]
|
|
105
|
+
// For each output in the array,
|
|
106
|
+
for (const output of vout) {
|
|
107
|
+
// Encode the output, and add it to the buffer.
|
|
108
|
+
buffer.push(encode_tx_vout(output))
|
|
109
|
+
}
|
|
110
|
+
// Return the buffer as a single payload.
|
|
111
|
+
return Buff.join(buffer)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function encode_tx_vout (txout : TxOutput) : Buff {
|
|
115
|
+
// Get the value and script pubkey from the output.
|
|
116
|
+
const { value, script_pk } = txout
|
|
117
|
+
// Return the data encoded as a single payload.
|
|
118
|
+
return Buff.join([
|
|
119
|
+
encode_vout_value(value),
|
|
120
|
+
encode_script_data(script_pk)
|
|
121
|
+
])
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function encode_vin_witness (data : string[]) : Buff {
|
|
125
|
+
// Create a buffer for the witness data, starting with the array length.
|
|
126
|
+
const buffer : Buff[] = [ Buff.varint(data.length) ]
|
|
127
|
+
// For each parameter in the witness array,
|
|
128
|
+
for (const param of data) {
|
|
129
|
+
// Encode the parameter, and add it to the buffer.
|
|
130
|
+
buffer.push(encode_script_data(param))
|
|
131
|
+
}
|
|
132
|
+
// Return the buffer as a single payload.
|
|
133
|
+
return Buff.join(buffer)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function encode_tx_locktime (locktime : number) : Buff {
|
|
137
|
+
// Encode the locktime as a 4-byte little-endian number.
|
|
138
|
+
return Buff.num(locktime, 4).reverse()
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function encode_script_data (
|
|
142
|
+
script : string | null
|
|
143
|
+
) : Buff {
|
|
144
|
+
// If the script is not null,
|
|
145
|
+
if (script !== null) {
|
|
146
|
+
// Assert that the script is a hex string.
|
|
147
|
+
Assert.is_hex(script)
|
|
148
|
+
// Encode the script, and add it to the buffer.
|
|
149
|
+
return Buff.hex(script).prefix_varint('le')
|
|
150
|
+
} else {
|
|
151
|
+
// Return a single byte of zero.
|
|
152
|
+
return Buff.hex('00')
|
|
153
|
+
}
|
|
154
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export * from './create.js'
|
|
2
|
+
export * from './decode.js'
|
|
3
|
+
export * from './encode.js'
|
|
4
|
+
export * from './locktime.js'
|
|
5
|
+
export * from './meta.js'
|
|
6
|
+
export * from './parse.js'
|
|
7
|
+
export * from './sequence.js'
|
|
8
|
+
export * from './size.js'
|
|
9
|
+
export * from './validate.js'
|
|
10
|
+
export * from './witness.js'
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { Assert } from '@vbyte/micro-lib'
|
|
2
|
+
|
|
3
|
+
import type { LocktimeInfo } from '@/types/index.js'
|
|
4
|
+
|
|
5
|
+
// The threshold between block height and timestamp.
|
|
6
|
+
const LOCKTIME_THRESHOLD = 500000000
|
|
7
|
+
|
|
8
|
+
export namespace Locktime {
|
|
9
|
+
export const encode = encode_locktime
|
|
10
|
+
export const decode = decode_locktime
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Encodes a LockTimeData object into a string representation.
|
|
15
|
+
* According to BIP-65, the value is simply the numeric value as a string.
|
|
16
|
+
*/
|
|
17
|
+
export function encode_locktime (
|
|
18
|
+
locktime : LocktimeInfo
|
|
19
|
+
) : number {
|
|
20
|
+
switch (locktime.type) {
|
|
21
|
+
case 'timelock':
|
|
22
|
+
Assert.ok(locktime.stamp >= LOCKTIME_THRESHOLD, 'Invalid timestamp')
|
|
23
|
+
return locktime.stamp
|
|
24
|
+
case 'heightlock':
|
|
25
|
+
Assert.ok(locktime.height > 0, 'height must be greater than 0')
|
|
26
|
+
Assert.ok(locktime.height < LOCKTIME_THRESHOLD, 'invalid block height')
|
|
27
|
+
return locktime.height
|
|
28
|
+
default:
|
|
29
|
+
throw new Error('Invalid locktime type')
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Parses a string or number into a LockTimeData object.
|
|
35
|
+
* According to BIP-65, values below LOCKTIME_THRESHOLD are interpreted as block heights,
|
|
36
|
+
* while values at or above this threshold are interpreted as timestamps.
|
|
37
|
+
*/
|
|
38
|
+
export function decode_locktime (
|
|
39
|
+
locktime : number
|
|
40
|
+
) : LocktimeInfo | null {
|
|
41
|
+
// Check if the value is valid (non-negative)
|
|
42
|
+
if (isNaN(locktime) || locktime <= 0) {
|
|
43
|
+
return null
|
|
44
|
+
}
|
|
45
|
+
// Return the appropriate locktime type.
|
|
46
|
+
if (locktime < LOCKTIME_THRESHOLD) {
|
|
47
|
+
return {
|
|
48
|
+
type : 'heightlock',
|
|
49
|
+
height : locktime
|
|
50
|
+
}
|
|
51
|
+
} else {
|
|
52
|
+
return {
|
|
53
|
+
type : 'timelock',
|
|
54
|
+
stamp : locktime
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { hash256 } from '@vbyte/micro-lib/hash'
|
|
2
|
+
import { LOCK_SCRIPT_REGEX } from '@/const.js'
|
|
3
|
+
import { encode_tx_data } from './encode.js'
|
|
4
|
+
import { parse_tx_data } from './parse.js'
|
|
5
|
+
|
|
6
|
+
import type {
|
|
7
|
+
TxData,
|
|
8
|
+
TxOutput,
|
|
9
|
+
TxOutputInfo,
|
|
10
|
+
TxOutputType,
|
|
11
|
+
TxValue,
|
|
12
|
+
WitnessVersion
|
|
13
|
+
} from '@/types/index.js'
|
|
14
|
+
|
|
15
|
+
export function is_return_script (script : string) : boolean {
|
|
16
|
+
return script.startsWith('6a')
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function get_vout_info (txout : TxOutput) : TxOutputInfo {
|
|
20
|
+
return {
|
|
21
|
+
type : get_vout_type(txout.script_pk),
|
|
22
|
+
version : get_vout_version(txout.script_pk)
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function get_vout_type (
|
|
27
|
+
script : string
|
|
28
|
+
) : TxOutputType {
|
|
29
|
+
for (const [ type, regex ] of Object.entries(LOCK_SCRIPT_REGEX)) {
|
|
30
|
+
if (regex.test(script)) return type as TxOutputType
|
|
31
|
+
}
|
|
32
|
+
return 'unknown'
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function get_vout_version (
|
|
36
|
+
script : string
|
|
37
|
+
) : WitnessVersion {
|
|
38
|
+
const wit_ver = script.slice(0, 4)
|
|
39
|
+
switch (wit_ver) {
|
|
40
|
+
case '0014':
|
|
41
|
+
return 0
|
|
42
|
+
case '5120':
|
|
43
|
+
return 1
|
|
44
|
+
default:
|
|
45
|
+
return null
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function get_txid (
|
|
50
|
+
txdata : string | TxData
|
|
51
|
+
) : string {
|
|
52
|
+
const json = parse_tx_data(txdata)
|
|
53
|
+
const data = encode_tx_data(json, false)
|
|
54
|
+
return hash256(data).reverse().hex
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function get_txhash (
|
|
58
|
+
txdata : string | TxData
|
|
59
|
+
) : string {
|
|
60
|
+
const json = parse_tx_data(txdata)
|
|
61
|
+
const data = encode_tx_data(json, true)
|
|
62
|
+
return hash256(data).reverse().hex
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function get_tx_value (
|
|
66
|
+
txdata : string | TxData
|
|
67
|
+
) : TxValue {
|
|
68
|
+
const tx = parse_tx_data(txdata)
|
|
69
|
+
const vin = tx.vin.reduce((acc, txin) => acc + (txin.prevout?.value ?? 0n), 0n)
|
|
70
|
+
const vout = tx.vout.reduce((acc, txout) => acc + txout.value, 0n)
|
|
71
|
+
const fees = (vin > vout) ? (vin - vout) : 0n
|
|
72
|
+
return { fees, vin, vout }
|
|
73
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { decode_tx_data } from './decode.js'
|
|
2
|
+
import { create_tx_data } from './create.js'
|
|
3
|
+
import { assert_tx_template } from './validate.js'
|
|
4
|
+
|
|
5
|
+
import type { TxData } from '@/types/index.js'
|
|
6
|
+
|
|
7
|
+
export function parse_tx_data (
|
|
8
|
+
txdata : unknown
|
|
9
|
+
) : TxData {
|
|
10
|
+
if (typeof txdata === 'string') {
|
|
11
|
+
return decode_tx_data(txdata)
|
|
12
|
+
} else {
|
|
13
|
+
assert_tx_template(txdata)
|
|
14
|
+
return create_tx_data(txdata)
|
|
15
|
+
}
|
|
16
|
+
}
|