@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,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bitcoin Transaction Sequence Field Manipulation
|
|
3
|
+
*
|
|
4
|
+
* This module provides functionality for encoding and decoding the sequence field in Bitcoin transactions.
|
|
5
|
+
* The sequence field is a 32-bit integer that can be used for various purposes:
|
|
6
|
+
*
|
|
7
|
+
* 1. Relative timelocks (BIP-68).
|
|
8
|
+
* 2. Custom protocol data.
|
|
9
|
+
*
|
|
10
|
+
* The implementation follows BIP-68 for timelock functionality, and extends it with a custom protocol
|
|
11
|
+
* that allows additional metadata to be encoded in the sequence field (to be used by on-chain indexers).
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import type { SequenceConfig, SequenceInfo } from '@/types/index.js'
|
|
15
|
+
|
|
16
|
+
/* ===== [ Constants ] ===================================================== */
|
|
17
|
+
|
|
18
|
+
const TIMELOCK_DISABLE = 0x80000000 // Bit 31: When set, disables relative timelock per BIP-68.
|
|
19
|
+
const TIMELOCK_TYPE = 0x00400000 // Bit 22: When set, indicates timestamp-based lock; when clear, indicates block-height-based lock.
|
|
20
|
+
const TIMELOCK_VALUE_MASK = 0x0000FFFF // Bits 0-15: Mask for extracting timelock value (16 bits).
|
|
21
|
+
const TIMELOCK_VALUE_MAX = 0xFFFF // Maximum value for timelock (2^16 - 1).
|
|
22
|
+
const TIMELOCK_GRANULARITY = 512 // Seconds per timestamp unit (BIP-68 specification).
|
|
23
|
+
|
|
24
|
+
/* ===== [ API ] ============================================================ */
|
|
25
|
+
|
|
26
|
+
export namespace Sequence {
|
|
27
|
+
export const encode = encode_sequence
|
|
28
|
+
export const decode = decode_sequence
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/* ===== [ Encoder ] ======================================================== */
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Encodes a SequenceData object into a 32-bit integer sequence value
|
|
35
|
+
*
|
|
36
|
+
* @param data - The sequence data to encode
|
|
37
|
+
* @returns A 32-bit integer representing the encoded sequence
|
|
38
|
+
* @throws Error if the input data is invalid or exceeds maximum values
|
|
39
|
+
*/
|
|
40
|
+
export function encode_sequence (data : SequenceConfig): number {
|
|
41
|
+
// If the timelock is based on a block height,
|
|
42
|
+
if (data.mode === 'height') {
|
|
43
|
+
// Validate the height value.
|
|
44
|
+
const height = parse_height(data.height)
|
|
45
|
+
// For heightlock, only encode the height value (TIMELOCK_TYPE bit remains clear)
|
|
46
|
+
return (height & TIMELOCK_VALUE_MASK) >>> 0
|
|
47
|
+
}
|
|
48
|
+
// If the timelock is based on a timestamp,
|
|
49
|
+
if (data.mode === 'stamp') {
|
|
50
|
+
// Convert timestamp to 512-second granularity units as per BIP-68.
|
|
51
|
+
const stamp = parse_stamp(data.stamp)
|
|
52
|
+
// Set the TIMELOCK_TYPE bit and encode the timestamp value.
|
|
53
|
+
return (TIMELOCK_TYPE | (stamp & TIMELOCK_VALUE_MASK)) >>> 0
|
|
54
|
+
}
|
|
55
|
+
// Throw an error if the mode is unrecognized.
|
|
56
|
+
throw new Error('invalid timelock mode: ' + data.mode)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/* ===== [ Decoder ] ========================================================= */
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Decodes a 32-bit sequence value into a SequenceData object
|
|
63
|
+
*
|
|
64
|
+
* @param sequence - The 32-bit sequence value to decode
|
|
65
|
+
* @returns A SequenceData object or null if the sequence doesn't represent special data
|
|
66
|
+
* @throws Error if the sequence value is invalid or exceeds maximum values
|
|
67
|
+
*/
|
|
68
|
+
export function decode_sequence (sequence: number | string) : SequenceInfo | null {
|
|
69
|
+
// Parse and validate the sequence value.
|
|
70
|
+
const seq = parse_sequence(sequence)
|
|
71
|
+
// If the sequence is disabled, return null.
|
|
72
|
+
if (seq & TIMELOCK_DISABLE) return null
|
|
73
|
+
// Extract the value.
|
|
74
|
+
const value = seq & TIMELOCK_VALUE_MASK
|
|
75
|
+
// Check for timestamp-based lock (TIMELOCK_TYPE bit is set).
|
|
76
|
+
if (seq & TIMELOCK_TYPE) {
|
|
77
|
+
// Convert granularity units back to seconds for timestamp.
|
|
78
|
+
const stamp = value * TIMELOCK_GRANULARITY
|
|
79
|
+
// Validate the timestamp value.
|
|
80
|
+
if (stamp > 0xFFFFFFFF) {
|
|
81
|
+
throw new Error('Decoded timestamp exceeds 32-bit limit')
|
|
82
|
+
}
|
|
83
|
+
// Return the decoded timelock.
|
|
84
|
+
return { mode: 'stamp', stamp }
|
|
85
|
+
} else {
|
|
86
|
+
// Validate the height value.
|
|
87
|
+
if (value > TIMELOCK_VALUE_MAX) {
|
|
88
|
+
throw new Error('Decoded height exceeds maximum')
|
|
89
|
+
}
|
|
90
|
+
// Return the decoded heightlock.
|
|
91
|
+
return { mode: 'height', height: value }
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/* ===== [ Helpers ] ========================================================= */
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Parses a sequence value into a number.
|
|
99
|
+
*
|
|
100
|
+
* @param sequence - The sequence value to parse.
|
|
101
|
+
* @returns The parsed sequence value.
|
|
102
|
+
* @throws Error if the sequence value is invalid.
|
|
103
|
+
*/
|
|
104
|
+
function parse_sequence (sequence: number | string): number {
|
|
105
|
+
const seq = (typeof sequence === 'string')
|
|
106
|
+
? parseInt(sequence, 16)
|
|
107
|
+
: sequence
|
|
108
|
+
if (!Number.isInteger(seq) || seq < 0 || seq > 0xFFFFFFFF) {
|
|
109
|
+
throw new Error(`invalid sequence value: ${seq}`)
|
|
110
|
+
}
|
|
111
|
+
return seq
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Parses a timestamp value into a 512-second granularity units.
|
|
116
|
+
*
|
|
117
|
+
* @param stamp - The timestamp value to parse.
|
|
118
|
+
* @returns The parsed timestamp value.
|
|
119
|
+
* @throws Error if the timestamp value is invalid.
|
|
120
|
+
*/
|
|
121
|
+
function parse_stamp (stamp? : number) : number {
|
|
122
|
+
if (stamp === undefined || !Number.isInteger(stamp)) {
|
|
123
|
+
throw new Error(`timestamp must be a number`)
|
|
124
|
+
}
|
|
125
|
+
// Convert timestamp to 512-second granularity units as per BIP-68.
|
|
126
|
+
const ts = Math.floor(stamp / TIMELOCK_GRANULARITY)
|
|
127
|
+
// Validate the timestamp value.
|
|
128
|
+
if (!Number.isInteger(ts) || ts < 0 || ts > TIMELOCK_VALUE_MAX) {
|
|
129
|
+
throw new Error(`timelock value must be an integer between 0 and ${TIMELOCK_VALUE_MAX} (in 512-second increments)`)
|
|
130
|
+
}
|
|
131
|
+
return ts
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Parses a height value into a number.
|
|
136
|
+
*
|
|
137
|
+
* @param height - The height value to parse.
|
|
138
|
+
* @returns The parsed height value.
|
|
139
|
+
* @throws Error if the height value is invalid.
|
|
140
|
+
*/
|
|
141
|
+
function parse_height (height? : number) : number {
|
|
142
|
+
if (height === undefined || !Number.isInteger(height) || height < 0 || height > TIMELOCK_VALUE_MAX) {
|
|
143
|
+
throw new Error(`Heightlock value must be an integer between 0 and ${TIMELOCK_VALUE_MAX}`)
|
|
144
|
+
}
|
|
145
|
+
return height
|
|
146
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { Buff, Bytes } from '@vbyte/buff'
|
|
2
|
+
import { parse_tx_data } from './parse.js'
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
encode_tx_data,
|
|
6
|
+
encode_tx_inputs,
|
|
7
|
+
encode_tx_outputs,
|
|
8
|
+
encode_tx_vout,
|
|
9
|
+
encode_vin,
|
|
10
|
+
encode_vin_witness
|
|
11
|
+
} from './encode.js'
|
|
12
|
+
|
|
13
|
+
import type {
|
|
14
|
+
TxData,
|
|
15
|
+
TxInput,
|
|
16
|
+
TxOutput,
|
|
17
|
+
TxSize,
|
|
18
|
+
WitnessSize,
|
|
19
|
+
} from '@/types/index.js'
|
|
20
|
+
|
|
21
|
+
const WIT_FLAG_BYTES = 2
|
|
22
|
+
const WIT_LENGTH_BYTE = 1
|
|
23
|
+
|
|
24
|
+
export function get_vsize (
|
|
25
|
+
bytes : Bytes
|
|
26
|
+
) : number {
|
|
27
|
+
const weight = Buff.bytes(bytes).length
|
|
28
|
+
const remain = (weight % 4 > 0) ? 1 : 0
|
|
29
|
+
return Math.floor(weight / 4) + remain
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function get_txsize (
|
|
33
|
+
txdata : string | TxData
|
|
34
|
+
) : TxSize {
|
|
35
|
+
const json = parse_tx_data(txdata)
|
|
36
|
+
const base = encode_tx_data(json, false).length
|
|
37
|
+
const size = encode_tx_data(json, true).length
|
|
38
|
+
const weight = base * 3 + size
|
|
39
|
+
const remain = (weight % 4 > 0) ? 1 : 0
|
|
40
|
+
const vsize = Math.floor(weight / 4) + remain
|
|
41
|
+
return { base, real: size, vsize, weight }
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function get_vin_size (vin : TxInput[]) : number {
|
|
45
|
+
const bytes = encode_tx_inputs(vin)
|
|
46
|
+
return bytes.length
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function get_vout_size (vout : TxOutput[]) : number {
|
|
50
|
+
const bytes = encode_tx_outputs(vout)
|
|
51
|
+
return bytes.length
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function get_segwit_size (txinputs : TxInput[]) : number {
|
|
55
|
+
const segwit_data = txinputs
|
|
56
|
+
.filter(e => e.witness.length > 0)
|
|
57
|
+
.map(e => e.witness)
|
|
58
|
+
return WIT_FLAG_BYTES + segwit_data
|
|
59
|
+
.reduce((acc, e) => acc + encode_vin_witness(e).length, 0)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function get_txin_size (txinput : TxInput) : number {
|
|
63
|
+
const bytes = encode_vin(txinput)
|
|
64
|
+
return bytes.length
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function get_txout_size (txoutput : TxOutput) : number {
|
|
68
|
+
const bytes = encode_tx_vout(txoutput)
|
|
69
|
+
return bytes.length
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function get_witness_size (witness : Bytes[]) : WitnessSize {
|
|
73
|
+
const stack = witness.map(e => Buff.bytes(e))
|
|
74
|
+
const size = stack.reduce((prev, next) => prev + next.length, 0)
|
|
75
|
+
const vsize = Math.ceil(WIT_LENGTH_BYTE + size / 4)
|
|
76
|
+
return { size, vsize }
|
|
77
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import * as Schema from '@/schema/index.js'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
SpendInput,
|
|
5
|
+
TxData,
|
|
6
|
+
TxInput,
|
|
7
|
+
TxInputTemplate,
|
|
8
|
+
TxOutput,
|
|
9
|
+
TxTemplate,
|
|
10
|
+
} from '@/types/index.js'
|
|
11
|
+
|
|
12
|
+
export function assert_tx_template (txdata : unknown) : asserts txdata is TxTemplate {
|
|
13
|
+
Schema.tx.tx_template.parse(txdata)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function assert_has_prevouts (vin : TxInput[]) : asserts vin is SpendInput[] {
|
|
17
|
+
if (vin.some(txin => txin.prevout === null)) {
|
|
18
|
+
throw new Error('transaction missing prevouts')
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function assert_tx_data (txdata : unknown) : asserts txdata is TxData {
|
|
23
|
+
Schema.tx.tx_data.parse(txdata)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function assert_tx_input (tx_input : unknown) : asserts tx_input is TxInput {
|
|
27
|
+
Schema.tx.tx_input.parse(tx_input)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function assert_tx_output (tx_output : unknown) : asserts tx_output is TxOutput {
|
|
31
|
+
Schema.tx.tx_output.parse(tx_output)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function assert_vin_template (vin_template : unknown) : asserts vin_template is TxInputTemplate {
|
|
35
|
+
Schema.tx.vin_template.parse(vin_template)
|
|
36
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { Buff, Bytes } from '@vbyte/buff'
|
|
2
|
+
import { is_valid_script } from '@/lib/script/decode.js'
|
|
3
|
+
import { TAPLEAF_VERSIONS } from '@/const.js'
|
|
4
|
+
|
|
5
|
+
import type {
|
|
6
|
+
WitnessInfo,
|
|
7
|
+
WitnessType
|
|
8
|
+
} from '@/types/index.js'
|
|
9
|
+
|
|
10
|
+
export function parse_witness_data (
|
|
11
|
+
witness : Bytes[]
|
|
12
|
+
) : WitnessInfo {
|
|
13
|
+
// Parse the witness data.
|
|
14
|
+
const elems = witness.map(e => Buff.bytes(e))
|
|
15
|
+
const annex = parse_annex_data(elems)
|
|
16
|
+
if (annex !== null) elems.pop()
|
|
17
|
+
const cblock = parse_cblock_data(elems)
|
|
18
|
+
if (cblock !== null) elems.pop()
|
|
19
|
+
const type = parse_witness_type(elems, cblock)
|
|
20
|
+
const version = parse_witness_version(type)
|
|
21
|
+
const script = parse_witness_script(elems, type)
|
|
22
|
+
if (script !== null) elems.pop()
|
|
23
|
+
const params = elems.map(e => e.hex)
|
|
24
|
+
return { annex, cblock, params, script, type, version }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function parse_annex_data (
|
|
28
|
+
data : Uint8Array[]
|
|
29
|
+
) : string | null {
|
|
30
|
+
// Get the last element of the array.
|
|
31
|
+
let elem = data.at(-1)
|
|
32
|
+
// Check if the element fits the annex format.
|
|
33
|
+
if (
|
|
34
|
+
data.length > 1 &&
|
|
35
|
+
elem instanceof Uint8Array &&
|
|
36
|
+
elem[0] === 0x50
|
|
37
|
+
) {
|
|
38
|
+
// Return the element.
|
|
39
|
+
return new Buff(elem).hex
|
|
40
|
+
} else {
|
|
41
|
+
// Return null.
|
|
42
|
+
return null
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function parse_cblock_data (
|
|
47
|
+
data : Uint8Array[]
|
|
48
|
+
) : string | null {
|
|
49
|
+
let elem = data.at(-1)
|
|
50
|
+
if (
|
|
51
|
+
data.length > 1 &&
|
|
52
|
+
elem instanceof Uint8Array &&
|
|
53
|
+
elem.length > 32 &&
|
|
54
|
+
TAPLEAF_VERSIONS.includes(elem[0] & 0xfe)
|
|
55
|
+
) {
|
|
56
|
+
// Return the element.
|
|
57
|
+
return new Buff(elem).hex
|
|
58
|
+
} else {
|
|
59
|
+
// Return null.
|
|
60
|
+
return null
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function parse_witness_script (
|
|
65
|
+
elems : Uint8Array[],
|
|
66
|
+
type : WitnessType
|
|
67
|
+
) {
|
|
68
|
+
let script : Uint8Array | undefined
|
|
69
|
+
switch (type) {
|
|
70
|
+
case 'p2tr-ts':
|
|
71
|
+
script = elems.at(-1)
|
|
72
|
+
case 'p2w-sh':
|
|
73
|
+
script = elems.at(-1)
|
|
74
|
+
}
|
|
75
|
+
return (script !== undefined) ? new Buff(script).hex : null
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function parse_witness_type (
|
|
79
|
+
elems : Uint8Array[],
|
|
80
|
+
cblock : string | null
|
|
81
|
+
) : WitnessType {
|
|
82
|
+
// Get the important elements of the witness.
|
|
83
|
+
let param_0 = elems.at(0),
|
|
84
|
+
param_1 = elems.at(1),
|
|
85
|
+
param_x = elems.at(-1)
|
|
86
|
+
// If the cblock is present and the last element exists:
|
|
87
|
+
if (cblock !== null && param_x !== undefined) {
|
|
88
|
+
return 'p2tr-ts'
|
|
89
|
+
// If the witness elements match the profile of a p2w-pkh:
|
|
90
|
+
} else if (
|
|
91
|
+
elems.length === 2 &&
|
|
92
|
+
param_0 !== undefined &&
|
|
93
|
+
param_1 !== undefined &&
|
|
94
|
+
param_0.length >= 64 &&
|
|
95
|
+
param_1.length === 33
|
|
96
|
+
) {
|
|
97
|
+
return 'p2w-pkh'
|
|
98
|
+
// If the witness elements match the profile of a p2tr-pk:
|
|
99
|
+
} else if (
|
|
100
|
+
elems.length === 1 &&
|
|
101
|
+
param_0 !== undefined &&
|
|
102
|
+
param_0.length === 64
|
|
103
|
+
) {
|
|
104
|
+
return 'p2tr-pk'
|
|
105
|
+
// If there is at least two witness elements:
|
|
106
|
+
} else if (
|
|
107
|
+
elems.length > 1 &&
|
|
108
|
+
param_x !== undefined &&
|
|
109
|
+
is_valid_script(param_x)
|
|
110
|
+
) {
|
|
111
|
+
return 'p2w-sh'
|
|
112
|
+
// If the witness elements don't match any known profile:
|
|
113
|
+
} else {
|
|
114
|
+
return 'unknown'
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function parse_witness_version (type : WitnessType) : number | null {
|
|
119
|
+
if (type.startsWith('p2tr')) return 1
|
|
120
|
+
if (type.startsWith('p2w')) return 0
|
|
121
|
+
return null
|
|
122
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
|
|
3
|
+
import { byte32, uint } from '@vbyte/micro-lib/schema'
|
|
4
|
+
|
|
5
|
+
export const taptree = z.union([ z.array(byte32), byte32 ])
|
|
6
|
+
|
|
7
|
+
export const config = z.object({
|
|
8
|
+
pubkey : byte32,
|
|
9
|
+
leaves : taptree.array().optional(),
|
|
10
|
+
target : byte32.optional(),
|
|
11
|
+
version : uint.optional(),
|
|
12
|
+
})
|
package/src/schema/tx.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
|
|
3
|
+
import { big, hex, hex32, uint } from '@vbyte/micro-lib/schema'
|
|
4
|
+
|
|
5
|
+
export const sats = big.max(2_100_000_000_000_000n)
|
|
6
|
+
|
|
7
|
+
export const tx_output = z.object({
|
|
8
|
+
value : sats,
|
|
9
|
+
script_pk : hex,
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
export const tx_input = z.object({
|
|
13
|
+
coinbase : hex.nullable(),
|
|
14
|
+
txid : hex32,
|
|
15
|
+
vout : uint,
|
|
16
|
+
prevout : tx_output.nullable(),
|
|
17
|
+
script_sig : hex.nullable(),
|
|
18
|
+
sequence : uint,
|
|
19
|
+
witness : z.array(hex)
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
export const tx_data = z.object({
|
|
23
|
+
version : uint,
|
|
24
|
+
vin : z.array(tx_input),
|
|
25
|
+
vout : z.array(tx_output),
|
|
26
|
+
locktime : uint,
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
export const vin_template = tx_input.extend({
|
|
30
|
+
coinbase : hex.nullable().optional(),
|
|
31
|
+
prevout : tx_output.nullable().optional(),
|
|
32
|
+
script_sig : hex.nullable().optional(),
|
|
33
|
+
sequence : uint.optional(),
|
|
34
|
+
witness : z.array(hex).optional(),
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
export const tx_template = z.object({
|
|
38
|
+
version : uint.optional(),
|
|
39
|
+
vin : z.array(vin_template).default([]),
|
|
40
|
+
vout : z.array(tx_output).default([]),
|
|
41
|
+
locktime : uint.optional(),
|
|
42
|
+
})
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export type AddressFormat = 'base58' | 'bech32' | 'bech32m'
|
|
2
|
+
export type AddressType = 'p2pkh' | 'p2sh' | 'p2w-pkh' | 'p2w-sh' | 'p2tr'
|
|
3
|
+
export type ChainNetwork = 'main' | 'testnet' | 'regtest'
|
|
4
|
+
export type AddressData = AddressContext & ScriptData
|
|
5
|
+
|
|
6
|
+
export type AddressConfigEntry = [
|
|
7
|
+
prefix : string,
|
|
8
|
+
type : AddressType,
|
|
9
|
+
network : ChainNetwork,
|
|
10
|
+
size : number,
|
|
11
|
+
format : AddressFormat,
|
|
12
|
+
version : number
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
export interface DecodedAddress {
|
|
16
|
+
format : AddressFormat
|
|
17
|
+
data : Uint8Array
|
|
18
|
+
prefix? : string
|
|
19
|
+
version? : number
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface AddressConfig {
|
|
23
|
+
format : AddressFormat
|
|
24
|
+
network : ChainNetwork
|
|
25
|
+
prefix : string
|
|
26
|
+
size : number
|
|
27
|
+
type : AddressType
|
|
28
|
+
version : number
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface AddressContext extends AddressConfig {
|
|
32
|
+
data : Uint8Array
|
|
33
|
+
hex : string
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface ScriptData {
|
|
37
|
+
script_asm : string[]
|
|
38
|
+
script_hex : string
|
|
39
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export * from './address.js'
|
|
2
|
+
export * from './meta.js'
|
|
3
|
+
export * from './psbt.js'
|
|
4
|
+
export * from './sighash.js'
|
|
5
|
+
export * from './taproot.js'
|
|
6
|
+
export * from './transaction.js'
|
|
7
|
+
export * from './txdata.js'
|
|
8
|
+
export * from './txmeta.js'
|
|
9
|
+
export * from './witness.js'
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Transaction } from '@scure/btc-signer'
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
TransactionInput,
|
|
5
|
+
TransactionOutput
|
|
6
|
+
} from '@scure/btc-signer/psbt'
|
|
7
|
+
|
|
8
|
+
export type PSBTData = Transaction
|
|
9
|
+
export type PSBTInput = TransactionInput
|
|
10
|
+
export type PSBTOutput = TransactionOutput
|
|
11
|
+
|
|
12
|
+
export interface PSBTPrevouts {
|
|
13
|
+
amounts : bigint[]
|
|
14
|
+
scripts : Uint8Array[]
|
|
15
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { TxInput } from './txdata.js'
|
|
2
|
+
|
|
3
|
+
export type SigHashType = 'segwit' | 'taproot'
|
|
4
|
+
|
|
5
|
+
export interface SigHashOptions {
|
|
6
|
+
extension ?: string // Extend hash with additional commitment (for taproot).
|
|
7
|
+
extflag ?: number // Set the extention version flag (future use).
|
|
8
|
+
txindex ?: number // Index value of the input you wish to sign for.
|
|
9
|
+
key_version ?: number // Set the key version flag (future use).
|
|
10
|
+
pubkey ?: string // Verify using this pubkey instead of the tapkey.
|
|
11
|
+
script ?: string // Use this script for signing and validation.
|
|
12
|
+
separator_pos ?: number // If using OP_CODESEPARATOR, specify the latest opcode position.
|
|
13
|
+
sigflag ?: number // Set the signature type flag.
|
|
14
|
+
throws ?: boolean // Should throw an exception on failure.
|
|
15
|
+
txinput ?: TxInput // Use this txinput for signing and validation.
|
|
16
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
type Bytes = string | Uint8Array
|
|
2
|
+
|
|
3
|
+
export type TapTree = Array<Bytes | Bytes[]>
|
|
4
|
+
|
|
5
|
+
export type MerkleProof = [
|
|
6
|
+
root : string,
|
|
7
|
+
target : string | undefined,
|
|
8
|
+
path : string[]
|
|
9
|
+
]
|
|
10
|
+
|
|
11
|
+
export interface TaprootConfig {
|
|
12
|
+
pubkey : Bytes
|
|
13
|
+
leaves ?: TapTree
|
|
14
|
+
target ?: Bytes
|
|
15
|
+
version ?: number
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface TaprootContext {
|
|
19
|
+
cblock : string
|
|
20
|
+
int_key : string
|
|
21
|
+
parity : number
|
|
22
|
+
taproot : string | null
|
|
23
|
+
tapkey : string
|
|
24
|
+
taptweak : string
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface ControlBlock {
|
|
28
|
+
int_key : string
|
|
29
|
+
parity : number
|
|
30
|
+
path : string[]
|
|
31
|
+
version : number
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface ProofData {
|
|
35
|
+
cblock : ControlBlock
|
|
36
|
+
params : string[]
|
|
37
|
+
script : string
|
|
38
|
+
tapkey : string
|
|
39
|
+
tweak : string
|
|
40
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { TxOutput } from './txdata.js'
|
|
2
|
+
import { LocktimeInfo, SequenceInfo } from './txmeta.js'
|
|
3
|
+
|
|
4
|
+
import type {
|
|
5
|
+
WitnessType,
|
|
6
|
+
WitnessVersion
|
|
7
|
+
} from './witness.js'
|
|
8
|
+
|
|
9
|
+
export type TxOutputType = WitnessType | 'p2pkh' | 'p2sh' | 'opreturn'
|
|
10
|
+
|
|
11
|
+
export interface TxOutputInfo {
|
|
12
|
+
type : TxOutputType
|
|
13
|
+
version : WitnessVersion
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface TxInputInfo {
|
|
17
|
+
type : WitnessType
|
|
18
|
+
version : WitnessVersion
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface LocktimeField {
|
|
22
|
+
hex : string
|
|
23
|
+
data : LocktimeInfo | null
|
|
24
|
+
value : number
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface SequenceField {
|
|
28
|
+
hex : string
|
|
29
|
+
data : SequenceInfo | null
|
|
30
|
+
value : number
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface ScriptField {
|
|
34
|
+
asm : string[]
|
|
35
|
+
hex : string
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface TxSize {
|
|
39
|
+
base : number
|
|
40
|
+
real : number
|
|
41
|
+
weight : number
|
|
42
|
+
vsize : number
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface TxValue {
|
|
46
|
+
fees : bigint
|
|
47
|
+
vin : bigint
|
|
48
|
+
vout : bigint
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface WitnessSize {
|
|
52
|
+
size : number
|
|
53
|
+
vsize : number
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface TransactionData {
|
|
57
|
+
hash : string
|
|
58
|
+
locktime : LocktimeField
|
|
59
|
+
return : TxOutput | null
|
|
60
|
+
size : TxSize
|
|
61
|
+
spends : TxOutputField[]
|
|
62
|
+
txid : string
|
|
63
|
+
value : TxValue
|
|
64
|
+
version : number
|
|
65
|
+
vin : TxInputField[]
|
|
66
|
+
vout : TxOutputField[]
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface WitnessField {
|
|
70
|
+
annex : string | null
|
|
71
|
+
cblock : string | null
|
|
72
|
+
params : string[]
|
|
73
|
+
script : ScriptField | null
|
|
74
|
+
size : number
|
|
75
|
+
stack : string[]
|
|
76
|
+
type : WitnessType
|
|
77
|
+
version : WitnessVersion
|
|
78
|
+
vsize : number
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface TxInputField {
|
|
82
|
+
coinbase? : string | null
|
|
83
|
+
prevout? : TxOutputField | null
|
|
84
|
+
script_sig? : ScriptField | null
|
|
85
|
+
sequence : SequenceField
|
|
86
|
+
size : number
|
|
87
|
+
txid : string
|
|
88
|
+
vout : number
|
|
89
|
+
witness? : WitnessField | null
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export interface TxOutputField {
|
|
93
|
+
script_pk : ScriptField
|
|
94
|
+
size : number
|
|
95
|
+
type : TxOutputType
|
|
96
|
+
value : bigint
|
|
97
|
+
version : WitnessVersion | null
|
|
98
|
+
}
|