@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.
Files changed (205) hide show
  1. package/LICENSE +121 -0
  2. package/README.md +5 -0
  3. package/dist/class/index.d.ts +5 -0
  4. package/dist/class/index.js +5 -0
  5. package/dist/class/signer.d.ts +18 -0
  6. package/dist/class/signer.js +32 -0
  7. package/dist/class/tx.d.ts +38 -0
  8. package/dist/class/tx.js +73 -0
  9. package/dist/class/txin.d.ts +29 -0
  10. package/dist/class/txin.js +68 -0
  11. package/dist/class/txout.d.ts +18 -0
  12. package/dist/class/txout.js +38 -0
  13. package/dist/class/witness.d.ts +20 -0
  14. package/dist/class/witness.js +57 -0
  15. package/dist/const.d.ts +22 -0
  16. package/dist/const.js +36 -0
  17. package/dist/index.d.ts +11 -0
  18. package/dist/index.js +10 -0
  19. package/dist/lib/address/encode.d.ts +3 -0
  20. package/dist/lib/address/encode.js +79 -0
  21. package/dist/lib/address/index.d.ts +20 -0
  22. package/dist/lib/address/index.js +21 -0
  23. package/dist/lib/address/p2pkh.d.ts +10 -0
  24. package/dist/lib/address/p2pkh.js +33 -0
  25. package/dist/lib/address/p2sh.d.ts +10 -0
  26. package/dist/lib/address/p2sh.js +33 -0
  27. package/dist/lib/address/p2tr.d.ts +8 -0
  28. package/dist/lib/address/p2tr.js +26 -0
  29. package/dist/lib/address/p2wpkh.d.ts +10 -0
  30. package/dist/lib/address/p2wpkh.js +34 -0
  31. package/dist/lib/address/p2wsh.d.ts +10 -0
  32. package/dist/lib/address/p2wsh.js +33 -0
  33. package/dist/lib/address/script.d.ts +5 -0
  34. package/dist/lib/address/script.js +46 -0
  35. package/dist/lib/address/util.d.ts +4 -0
  36. package/dist/lib/address/util.js +57 -0
  37. package/dist/lib/meta/index.d.ts +2 -0
  38. package/dist/lib/meta/index.js +2 -0
  39. package/dist/lib/meta/pointer.d.ts +42 -0
  40. package/dist/lib/meta/pointer.js +69 -0
  41. package/dist/lib/meta/scribe.d.ts +7 -0
  42. package/dist/lib/meta/scribe.js +192 -0
  43. package/dist/lib/psbt/encoder.d.ts +5 -0
  44. package/dist/lib/psbt/encoder.js +21 -0
  45. package/dist/lib/psbt/index.d.ts +4 -0
  46. package/dist/lib/psbt/index.js +4 -0
  47. package/dist/lib/psbt/meta.d.ts +3 -0
  48. package/dist/lib/psbt/meta.js +11 -0
  49. package/dist/lib/psbt/util.d.ts +5 -0
  50. package/dist/lib/psbt/util.js +44 -0
  51. package/dist/lib/psbt/validate.d.ts +2 -0
  52. package/dist/lib/psbt/validate.js +11 -0
  53. package/dist/lib/script/decode.d.ts +2 -0
  54. package/dist/lib/script/decode.js +55 -0
  55. package/dist/lib/script/encode.d.ts +6 -0
  56. package/dist/lib/script/encode.js +80 -0
  57. package/dist/lib/script/index.d.ts +126 -0
  58. package/dist/lib/script/index.js +17 -0
  59. package/dist/lib/script/util.d.ts +2 -0
  60. package/dist/lib/script/util.js +10 -0
  61. package/dist/lib/script/words.d.ts +116 -0
  62. package/dist/lib/script/words.js +164 -0
  63. package/dist/lib/sighash/index.d.ts +5 -0
  64. package/dist/lib/sighash/index.js +5 -0
  65. package/dist/lib/sighash/segwit.d.ts +3 -0
  66. package/dist/lib/sighash/segwit.js +89 -0
  67. package/dist/lib/sighash/sign.d.ts +3 -0
  68. package/dist/lib/sighash/sign.js +20 -0
  69. package/dist/lib/sighash/taproot.d.ts +9 -0
  70. package/dist/lib/sighash/taproot.js +124 -0
  71. package/dist/lib/sighash/util.d.ts +3 -0
  72. package/dist/lib/sighash/util.js +16 -0
  73. package/dist/lib/sighash/verify.d.ts +1 -0
  74. package/dist/lib/sighash/verify.js +1 -0
  75. package/dist/lib/taproot/cblock.d.ts +3 -0
  76. package/dist/lib/taproot/cblock.js +55 -0
  77. package/dist/lib/taproot/encode.d.ts +6 -0
  78. package/dist/lib/taproot/encode.js +26 -0
  79. package/dist/lib/taproot/index.d.ts +4 -0
  80. package/dist/lib/taproot/index.js +4 -0
  81. package/dist/lib/taproot/parse.d.ts +11 -0
  82. package/dist/lib/taproot/parse.js +47 -0
  83. package/dist/lib/taproot/tree.d.ts +3 -0
  84. package/dist/lib/taproot/tree.js +49 -0
  85. package/dist/lib/tx/create.d.ts +6 -0
  86. package/dist/lib/tx/create.js +54 -0
  87. package/dist/lib/tx/decode.d.ts +9 -0
  88. package/dist/lib/tx/decode.js +109 -0
  89. package/dist/lib/tx/encode.d.ts +15 -0
  90. package/dist/lib/tx/encode.js +94 -0
  91. package/dist/lib/tx/index.d.ts +10 -0
  92. package/dist/lib/tx/index.js +10 -0
  93. package/dist/lib/tx/locktime.d.ts +7 -0
  94. package/dist/lib/tx/locktime.js +37 -0
  95. package/dist/lib/tx/meta.d.ts +8 -0
  96. package/dist/lib/tx/meta.js +48 -0
  97. package/dist/lib/tx/parse.d.ts +2 -0
  98. package/dist/lib/tx/parse.js +12 -0
  99. package/dist/lib/tx/sequence.d.ts +7 -0
  100. package/dist/lib/tx/sequence.js +65 -0
  101. package/dist/lib/tx/size.d.ts +10 -0
  102. package/dist/lib/tx/size.js +48 -0
  103. package/dist/lib/tx/validate.d.ts +7 -0
  104. package/dist/lib/tx/validate.js +21 -0
  105. package/dist/lib/tx/witness.d.ts +3 -0
  106. package/dist/lib/tx/witness.js +85 -0
  107. package/dist/main.cjs +14625 -0
  108. package/dist/main.cjs.map +1 -0
  109. package/dist/module.mjs +14610 -0
  110. package/dist/module.mjs.map +1 -0
  111. package/dist/package.json +106 -0
  112. package/dist/schema/index.d.ts +2 -0
  113. package/dist/schema/index.js +2 -0
  114. package/dist/schema/taproot.d.ts +18 -0
  115. package/dist/schema/taproot.js +9 -0
  116. package/dist/schema/tx.d.ts +278 -0
  117. package/dist/schema/tx.js +35 -0
  118. package/dist/script.js +15 -0
  119. package/dist/script.js.map +1 -0
  120. package/dist/types/address.d.ts +34 -0
  121. package/dist/types/address.js +1 -0
  122. package/dist/types/index.d.ts +9 -0
  123. package/dist/types/index.js +9 -0
  124. package/dist/types/meta.d.ts +10 -0
  125. package/dist/types/meta.js +1 -0
  126. package/dist/types/psbt.d.ts +9 -0
  127. package/dist/types/psbt.js +1 -0
  128. package/dist/types/sighash.d.ts +14 -0
  129. package/dist/types/sighash.js +1 -0
  130. package/dist/types/taproot.d.ts +35 -0
  131. package/dist/types/taproot.js +1 -0
  132. package/dist/types/transaction.d.ts +81 -0
  133. package/dist/types/transaction.js +1 -0
  134. package/dist/types/txdata.d.ts +45 -0
  135. package/dist/types/txdata.js +1 -0
  136. package/dist/types/txmeta.d.ts +19 -0
  137. package/dist/types/txmeta.js +1 -0
  138. package/dist/types/witness.d.ts +30 -0
  139. package/dist/types/witness.js +1 -0
  140. package/package.json +106 -0
  141. package/src/class/index.ts +5 -0
  142. package/src/class/signer.ts +47 -0
  143. package/src/class/tx.ts +118 -0
  144. package/src/class/txin.ts +95 -0
  145. package/src/class/txout.ts +57 -0
  146. package/src/class/witness.ts +85 -0
  147. package/src/const.ts +43 -0
  148. package/src/index.ts +14 -0
  149. package/src/lib/address/encode.ts +183 -0
  150. package/src/lib/address/index.ts +24 -0
  151. package/src/lib/address/p2pkh.ts +65 -0
  152. package/src/lib/address/p2sh.ts +65 -0
  153. package/src/lib/address/p2tr.ts +51 -0
  154. package/src/lib/address/p2wpkh.ts +67 -0
  155. package/src/lib/address/p2wsh.ts +65 -0
  156. package/src/lib/address/script.ts +63 -0
  157. package/src/lib/address/util.ts +102 -0
  158. package/src/lib/meta/index.ts +2 -0
  159. package/src/lib/meta/pointer.ts +107 -0
  160. package/src/lib/meta/scribe.ts +251 -0
  161. package/src/lib/psbt/encoder.ts +24 -0
  162. package/src/lib/psbt/index.ts +4 -0
  163. package/src/lib/psbt/meta.ts +15 -0
  164. package/src/lib/psbt/util.ts +62 -0
  165. package/src/lib/psbt/validate.ts +18 -0
  166. package/src/lib/script/decode.ts +75 -0
  167. package/src/lib/script/encode.ts +130 -0
  168. package/src/lib/script/index.ts +26 -0
  169. package/src/lib/script/util.ts +78 -0
  170. package/src/lib/script/words.ts +182 -0
  171. package/src/lib/sighash/index.ts +5 -0
  172. package/src/lib/sighash/segwit.ts +152 -0
  173. package/src/lib/sighash/sign.ts +35 -0
  174. package/src/lib/sighash/taproot.ts +236 -0
  175. package/src/lib/sighash/util.ts +29 -0
  176. package/src/lib/sighash/verify.ts +83 -0
  177. package/src/lib/taproot/cblock.ts +95 -0
  178. package/src/lib/taproot/encode.ts +49 -0
  179. package/src/lib/taproot/index.ts +4 -0
  180. package/src/lib/taproot/parse.ts +65 -0
  181. package/src/lib/taproot/tree.ts +94 -0
  182. package/src/lib/tx/create.ts +82 -0
  183. package/src/lib/tx/decode.ts +145 -0
  184. package/src/lib/tx/encode.ts +154 -0
  185. package/src/lib/tx/index.ts +10 -0
  186. package/src/lib/tx/locktime.ts +57 -0
  187. package/src/lib/tx/meta.ts +73 -0
  188. package/src/lib/tx/parse.ts +16 -0
  189. package/src/lib/tx/sequence.ts +146 -0
  190. package/src/lib/tx/size.ts +77 -0
  191. package/src/lib/tx/validate.ts +36 -0
  192. package/src/lib/tx/witness.ts +122 -0
  193. package/src/schema/index.ts +2 -0
  194. package/src/schema/taproot.ts +12 -0
  195. package/src/schema/tx.ts +42 -0
  196. package/src/types/address.ts +39 -0
  197. package/src/types/index.ts +9 -0
  198. package/src/types/meta.ts +10 -0
  199. package/src/types/psbt.ts +15 -0
  200. package/src/types/sighash.ts +16 -0
  201. package/src/types/taproot.ts +40 -0
  202. package/src/types/transaction.ts +98 -0
  203. package/src/types/txdata.ts +53 -0
  204. package/src/types/txmeta.ts +25 -0
  205. 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
+ }