@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,11 @@
|
|
|
1
|
+
import type { ControlBlock } from '../../types/index.js';
|
|
2
|
+
export declare function parse_taproot_witness(witness: string[]): {
|
|
3
|
+
cblock: ControlBlock;
|
|
4
|
+
params: string[];
|
|
5
|
+
script: string;
|
|
6
|
+
tapkey: string;
|
|
7
|
+
tweak: string;
|
|
8
|
+
};
|
|
9
|
+
export declare function parse_cblock(cblock: string | Uint8Array): ControlBlock;
|
|
10
|
+
export declare function parse_cblock_parity(cbits: number): number[];
|
|
11
|
+
export declare function parse_pubkey_parity(pubkey: string | Uint8Array): number;
|
|
@@ -0,0 +1,47 @@
|
|
|
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
|
+
import { encode_tapbranch, encode_tapscript, encode_taptweak, } from './encode.js';
|
|
5
|
+
export function parse_taproot_witness(witness) {
|
|
6
|
+
const { cblock, params, script } = parse_witness_data(witness);
|
|
7
|
+
Assert.exists(cblock, 'cblock is null');
|
|
8
|
+
Assert.exists(script, 'script is null');
|
|
9
|
+
const cblk = parse_cblock(cblock);
|
|
10
|
+
const target = encode_tapscript(script, cblk.version);
|
|
11
|
+
let branch = target.hex;
|
|
12
|
+
for (const leaf of cblk.path) {
|
|
13
|
+
branch = encode_tapbranch(branch, leaf).hex;
|
|
14
|
+
}
|
|
15
|
+
const tweak = encode_taptweak(cblk.int_key, branch);
|
|
16
|
+
const tapkey = ECC.tweak_pubkey(cblk.int_key, tweak, 'bip340');
|
|
17
|
+
params.map(e => Buff.bytes(e).hex);
|
|
18
|
+
return { cblock: cblk, params, script, tapkey: tapkey.hex, tweak: tweak.hex };
|
|
19
|
+
}
|
|
20
|
+
export function parse_cblock(cblock) {
|
|
21
|
+
const buffer = new Stream(cblock);
|
|
22
|
+
const cbyte = buffer.read(1).num;
|
|
23
|
+
const int_key = buffer.read(32).hex;
|
|
24
|
+
const [version, parity] = parse_cblock_parity(cbyte);
|
|
25
|
+
const path = [];
|
|
26
|
+
while (buffer.size >= 32) {
|
|
27
|
+
path.push(buffer.read(32).hex);
|
|
28
|
+
}
|
|
29
|
+
if (buffer.size !== 0) {
|
|
30
|
+
throw new Error('Non-empty buffer on control block: ' + String(buffer));
|
|
31
|
+
}
|
|
32
|
+
return { int_key, path, parity, version };
|
|
33
|
+
}
|
|
34
|
+
export function parse_cblock_parity(cbits) {
|
|
35
|
+
return (cbits % 2 === 0)
|
|
36
|
+
? [cbits - 0, 0x02]
|
|
37
|
+
: [cbits - 1, 0x03];
|
|
38
|
+
}
|
|
39
|
+
export function parse_pubkey_parity(pubkey) {
|
|
40
|
+
Assert.size(pubkey, 33, 'invalid pubkey size');
|
|
41
|
+
const [parity] = Buff.bytes(pubkey);
|
|
42
|
+
if (parity === 0x02)
|
|
43
|
+
return 0;
|
|
44
|
+
if (parity === 0x03)
|
|
45
|
+
return 1;
|
|
46
|
+
throw new Error('Invalid parity bit: ' + String(parity));
|
|
47
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { Buff } from '@vbyte/buff';
|
|
2
|
+
import { encode_tapbranch } from './encode.js';
|
|
3
|
+
export function get_merkle_root(leaves) {
|
|
4
|
+
return merkleize(leaves)[0];
|
|
5
|
+
}
|
|
6
|
+
export function merkleize(taptree, target, path = []) {
|
|
7
|
+
const leaves = [];
|
|
8
|
+
const tree = [];
|
|
9
|
+
if (taptree.length < 1) {
|
|
10
|
+
throw new Error('Tree is empty!');
|
|
11
|
+
}
|
|
12
|
+
for (let i = 0; i < taptree.length; i++) {
|
|
13
|
+
const bytes = taptree[i];
|
|
14
|
+
if (Array.isArray(bytes)) {
|
|
15
|
+
let [tapleaf, new_target, branches] = merkleize(bytes, target);
|
|
16
|
+
target = new_target;
|
|
17
|
+
leaves.push(tapleaf);
|
|
18
|
+
for (const branch of branches) {
|
|
19
|
+
path.push(branch);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
const leaf = Buff.bytes(bytes).hex;
|
|
24
|
+
leaves.push(leaf);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
if (leaves.length === 1) {
|
|
28
|
+
return [leaves[0], target, path];
|
|
29
|
+
}
|
|
30
|
+
leaves.sort();
|
|
31
|
+
if (leaves.length % 2 !== 0) {
|
|
32
|
+
leaves.push(leaves[leaves.length - 1]);
|
|
33
|
+
}
|
|
34
|
+
for (let i = 0; i < leaves.length - 1; i += 2) {
|
|
35
|
+
const branch = encode_tapbranch(leaves[i], leaves[i + 1]).hex;
|
|
36
|
+
tree.push(branch);
|
|
37
|
+
if (typeof target === 'string') {
|
|
38
|
+
if (target === leaves[i]) {
|
|
39
|
+
path.push(leaves[i + 1]);
|
|
40
|
+
target = branch;
|
|
41
|
+
}
|
|
42
|
+
else if (target === leaves[i + 1]) {
|
|
43
|
+
path.push(leaves[i]);
|
|
44
|
+
target = branch;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return merkleize(tree, target, path);
|
|
49
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { TxData, TxInputTemplate, TxInput, TxOutput, TxTemplate, SpendInput, CoinbaseInput } from '../../types/index.js';
|
|
2
|
+
export declare function create_coinbase_input(config: TxInputTemplate | TxInput): CoinbaseInput;
|
|
3
|
+
export declare function create_spend_input(config: TxInputTemplate | TxInput): SpendInput;
|
|
4
|
+
export declare function create_tx_input(config: TxInputTemplate | TxInput): TxInput;
|
|
5
|
+
export declare function create_tx_output(config: TxOutput): TxOutput;
|
|
6
|
+
export declare function create_tx_data(config: TxTemplate | TxData): TxData;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { Assert } from '@vbyte/micro-lib';
|
|
2
|
+
import { COINBASE, DEFAULT } from '../../const.js';
|
|
3
|
+
import { assert_tx_output, assert_tx_template, assert_vin_template } from './validate.js';
|
|
4
|
+
export function create_coinbase_input(config) {
|
|
5
|
+
assert_vin_template(config);
|
|
6
|
+
Assert.exists(config.coinbase, 'coinbase is required');
|
|
7
|
+
const coinbase = config.coinbase;
|
|
8
|
+
const prevout = null;
|
|
9
|
+
const script_sig = null;
|
|
10
|
+
const sequence = config.sequence ?? DEFAULT.SEQUENCE;
|
|
11
|
+
const txid = COINBASE.TXID;
|
|
12
|
+
const vout = COINBASE.VOUT;
|
|
13
|
+
const witness = config.witness ?? [];
|
|
14
|
+
return { coinbase, prevout, script_sig, sequence, witness, txid, vout };
|
|
15
|
+
}
|
|
16
|
+
export function create_spend_input(config) {
|
|
17
|
+
assert_vin_template(config);
|
|
18
|
+
Assert.exists(config.prevout, 'prevout is required');
|
|
19
|
+
const prevout = config.prevout;
|
|
20
|
+
const coinbase = null;
|
|
21
|
+
const script_sig = config.script_sig ?? null;
|
|
22
|
+
const sequence = config.sequence ?? DEFAULT.SEQUENCE;
|
|
23
|
+
const witness = config.witness ?? [];
|
|
24
|
+
return { ...config, coinbase, prevout, script_sig, sequence, witness };
|
|
25
|
+
}
|
|
26
|
+
export function create_tx_input(config) {
|
|
27
|
+
assert_vin_template(config);
|
|
28
|
+
Assert.exists(config.txid, 'txid is required');
|
|
29
|
+
Assert.exists(config.vout, 'vout is required');
|
|
30
|
+
const coinbase = config.coinbase ?? null;
|
|
31
|
+
const prevout = config.prevout ?? null;
|
|
32
|
+
const script_sig = config.script_sig ?? null;
|
|
33
|
+
const sequence = config.sequence ?? DEFAULT.SEQUENCE;
|
|
34
|
+
const witness = config.witness ?? [];
|
|
35
|
+
if (coinbase !== null)
|
|
36
|
+
return create_coinbase_input(config);
|
|
37
|
+
if (prevout !== null)
|
|
38
|
+
return create_spend_input(config);
|
|
39
|
+
return { ...config, coinbase, prevout, script_sig, sequence, witness };
|
|
40
|
+
}
|
|
41
|
+
export function create_tx_output(config) {
|
|
42
|
+
assert_tx_output(config);
|
|
43
|
+
const { script_pk, value } = config;
|
|
44
|
+
return { script_pk, value: BigInt(value) };
|
|
45
|
+
}
|
|
46
|
+
export function create_tx_data(config) {
|
|
47
|
+
assert_tx_template(config);
|
|
48
|
+
const { vin = [], vout = [] } = config;
|
|
49
|
+
const locktime = config.locktime ?? DEFAULT.LOCKTIME;
|
|
50
|
+
const version = config.version ?? DEFAULT.VERSION;
|
|
51
|
+
const inputs = vin.map(txin => create_tx_input(txin));
|
|
52
|
+
const outputs = vout.map(txout => create_tx_output(txout));
|
|
53
|
+
return { locktime, vin: inputs, vout: outputs, version };
|
|
54
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Bytes, Stream } from '@vbyte/buff';
|
|
2
|
+
import { TxData, TxOutput } from '../../types/index.js';
|
|
3
|
+
interface TxEncoderConfig {
|
|
4
|
+
prevouts: TxOutput[];
|
|
5
|
+
segwit: boolean;
|
|
6
|
+
}
|
|
7
|
+
export declare function decode_tx_data(txbytes: Bytes, options?: Partial<TxEncoderConfig>): TxData;
|
|
8
|
+
export declare function read_script(stream: Stream, varint?: boolean): string | null;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { 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
|
+
const DEFAULT_CONFIG = {
|
|
6
|
+
prevouts: [],
|
|
7
|
+
segwit: true
|
|
8
|
+
};
|
|
9
|
+
export function decode_tx_data(txbytes, options = {}) {
|
|
10
|
+
const config = { ...DEFAULT_CONFIG, ...options };
|
|
11
|
+
Assert.is_bytes(txbytes, 'txbytes must be hex or a unit array');
|
|
12
|
+
const stream = new Stream(txbytes);
|
|
13
|
+
const version = read_version(stream);
|
|
14
|
+
const has_witness = (config.segwit)
|
|
15
|
+
? check_witness_flag(stream)
|
|
16
|
+
: false;
|
|
17
|
+
const vin = read_inputs(stream, config.prevouts);
|
|
18
|
+
const vout = read_outputs(stream);
|
|
19
|
+
if (has_witness) {
|
|
20
|
+
for (const txin of vin) {
|
|
21
|
+
txin.witness = read_witness(stream);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
const locktime = read_locktime(stream);
|
|
25
|
+
return { version, vin, vout, locktime };
|
|
26
|
+
}
|
|
27
|
+
function read_version(stream) {
|
|
28
|
+
return stream.read(4).reverse().to_num();
|
|
29
|
+
}
|
|
30
|
+
function check_witness_flag(stream) {
|
|
31
|
+
const [marker, flag] = [...stream.peek(2)];
|
|
32
|
+
if (marker === 0) {
|
|
33
|
+
stream.read(2);
|
|
34
|
+
if (flag === 1) {
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
throw new Error(`Invalid witness flag: ${flag}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
function read_inputs(stream, prevouts) {
|
|
44
|
+
const inputs = [];
|
|
45
|
+
const vinCount = stream.varint();
|
|
46
|
+
for (let i = 0; i < vinCount; i++) {
|
|
47
|
+
const txinput = read_vin(stream, prevouts.at(i));
|
|
48
|
+
inputs.push(txinput);
|
|
49
|
+
}
|
|
50
|
+
return inputs;
|
|
51
|
+
}
|
|
52
|
+
function read_vin(stream, prevout = null) {
|
|
53
|
+
const txid = stream.read(32).reverse().hex;
|
|
54
|
+
const vout = stream.read(4).reverse().num;
|
|
55
|
+
const script_sig = read_script(stream, true);
|
|
56
|
+
const sequence = stream.read(4).reverse().num;
|
|
57
|
+
const witness = [];
|
|
58
|
+
if (txid === COINBASE.TXID && vout === COINBASE.VOUT) {
|
|
59
|
+
return { coinbase: script_sig, prevout: null, script_sig: null, sequence, txid, vout, witness };
|
|
60
|
+
}
|
|
61
|
+
else if (prevout !== null) {
|
|
62
|
+
return { coinbase: null, prevout, script_sig, sequence, txid, vout, witness };
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
return { coinbase: null, prevout, script_sig, sequence, txid, vout, witness };
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function read_outputs(stream) {
|
|
69
|
+
const outputs = [];
|
|
70
|
+
const vcount = stream.varint();
|
|
71
|
+
for (let i = 0; i < vcount; i++) {
|
|
72
|
+
try {
|
|
73
|
+
outputs.push(read_vout(stream));
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
throw new Error(`failed to decode output: ${i}: ${parse_error(error)}`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return outputs;
|
|
80
|
+
}
|
|
81
|
+
function read_vout(stream) {
|
|
82
|
+
const value = stream.read(8).reverse().big;
|
|
83
|
+
const script_pk = read_script(stream, true);
|
|
84
|
+
Assert.exists(script_pk, 'failed to decode script_pk');
|
|
85
|
+
return { value, script_pk };
|
|
86
|
+
}
|
|
87
|
+
function read_witness(stream) {
|
|
88
|
+
const stack = [];
|
|
89
|
+
const count = stream.varint();
|
|
90
|
+
for (let i = 0; i < count; i++) {
|
|
91
|
+
const element = read_script(stream, true);
|
|
92
|
+
if (element === null) {
|
|
93
|
+
throw new Error('failed to decode witness element: ' + i);
|
|
94
|
+
}
|
|
95
|
+
stack.push(element);
|
|
96
|
+
}
|
|
97
|
+
return stack;
|
|
98
|
+
}
|
|
99
|
+
export function read_script(stream, varint) {
|
|
100
|
+
const size = (varint === true)
|
|
101
|
+
? stream.varint('le')
|
|
102
|
+
: stream.size;
|
|
103
|
+
return size > 0
|
|
104
|
+
? stream.read(size).hex
|
|
105
|
+
: null;
|
|
106
|
+
}
|
|
107
|
+
function read_locktime(stream) {
|
|
108
|
+
return stream.read(4).reverse().to_num();
|
|
109
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Buff } from '@vbyte/buff';
|
|
2
|
+
import { TxInput, TxOutput, TxData } from '../../types/index.js';
|
|
3
|
+
export declare function encode_tx_data(txdata: TxData, segwit?: boolean): Buff;
|
|
4
|
+
export declare function encode_tx_version(num: number): Buff;
|
|
5
|
+
export declare function encode_txin_txid(txid: string): Buff;
|
|
6
|
+
export declare function encode_txin_vout(vout: number): Buff;
|
|
7
|
+
export declare function encode_txin_sequence(sequence: number): Buff;
|
|
8
|
+
export declare function encode_tx_inputs(vin: TxInput[]): Buff;
|
|
9
|
+
export declare function encode_vin(txin: TxInput): Buff;
|
|
10
|
+
export declare function encode_vout_value(value: bigint): Buff;
|
|
11
|
+
export declare function encode_tx_outputs(vout: TxOutput[]): Buff;
|
|
12
|
+
export declare function encode_tx_vout(txout: TxOutput): Buff;
|
|
13
|
+
export declare function encode_vin_witness(data: string[]): Buff;
|
|
14
|
+
export declare function encode_tx_locktime(locktime: number): Buff;
|
|
15
|
+
export declare function encode_script_data(script: string | null): Buff;
|
|
@@ -0,0 +1,94 @@
|
|
|
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
|
+
export function encode_tx_data(txdata, segwit = true) {
|
|
6
|
+
const tx = parse_tx_data(txdata);
|
|
7
|
+
const { version, vin, vout, locktime } = tx;
|
|
8
|
+
const buffer = [encode_tx_version(version)];
|
|
9
|
+
if (segwit) {
|
|
10
|
+
buffer.push(Buff.hex('0001'));
|
|
11
|
+
}
|
|
12
|
+
buffer.push(encode_tx_inputs(vin));
|
|
13
|
+
buffer.push(encode_tx_outputs(vout));
|
|
14
|
+
if (segwit) {
|
|
15
|
+
for (const input of vin) {
|
|
16
|
+
buffer.push(encode_vin_witness(input.witness));
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
buffer.push(encode_tx_locktime(locktime));
|
|
20
|
+
return Buff.join(buffer);
|
|
21
|
+
}
|
|
22
|
+
export function encode_tx_version(num) {
|
|
23
|
+
return Buff.num(num, 4).reverse();
|
|
24
|
+
}
|
|
25
|
+
export function encode_txin_txid(txid) {
|
|
26
|
+
return Buff.hex(txid, 32).reverse();
|
|
27
|
+
}
|
|
28
|
+
export function encode_txin_vout(vout) {
|
|
29
|
+
return Buff.num(vout, 4).reverse();
|
|
30
|
+
}
|
|
31
|
+
export function encode_txin_sequence(sequence) {
|
|
32
|
+
return Buff.num(sequence, 4).reverse();
|
|
33
|
+
}
|
|
34
|
+
export function encode_tx_inputs(vin) {
|
|
35
|
+
const raw = [Buff.varint(vin.length, 'le')];
|
|
36
|
+
for (const input of vin) {
|
|
37
|
+
raw.push(encode_vin(input));
|
|
38
|
+
}
|
|
39
|
+
return Buff.join(raw);
|
|
40
|
+
}
|
|
41
|
+
export function encode_vin(txin) {
|
|
42
|
+
if (txin.coinbase !== null) {
|
|
43
|
+
return Buff.join([
|
|
44
|
+
encode_txin_txid(COINBASE.TXID),
|
|
45
|
+
encode_txin_vout(COINBASE.VOUT),
|
|
46
|
+
encode_script_data(txin.coinbase),
|
|
47
|
+
encode_txin_sequence(txin.sequence)
|
|
48
|
+
]);
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
return Buff.join([
|
|
52
|
+
encode_txin_txid(txin.txid),
|
|
53
|
+
encode_txin_vout(txin.vout),
|
|
54
|
+
encode_script_data(txin.script_sig),
|
|
55
|
+
encode_txin_sequence(txin.sequence)
|
|
56
|
+
]);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
export function encode_vout_value(value) {
|
|
60
|
+
return Buff.big(value, 8).reverse();
|
|
61
|
+
}
|
|
62
|
+
export function encode_tx_outputs(vout) {
|
|
63
|
+
const buffer = [Buff.varint(vout.length, 'le')];
|
|
64
|
+
for (const output of vout) {
|
|
65
|
+
buffer.push(encode_tx_vout(output));
|
|
66
|
+
}
|
|
67
|
+
return Buff.join(buffer);
|
|
68
|
+
}
|
|
69
|
+
export function encode_tx_vout(txout) {
|
|
70
|
+
const { value, script_pk } = txout;
|
|
71
|
+
return Buff.join([
|
|
72
|
+
encode_vout_value(value),
|
|
73
|
+
encode_script_data(script_pk)
|
|
74
|
+
]);
|
|
75
|
+
}
|
|
76
|
+
export function encode_vin_witness(data) {
|
|
77
|
+
const buffer = [Buff.varint(data.length)];
|
|
78
|
+
for (const param of data) {
|
|
79
|
+
buffer.push(encode_script_data(param));
|
|
80
|
+
}
|
|
81
|
+
return Buff.join(buffer);
|
|
82
|
+
}
|
|
83
|
+
export function encode_tx_locktime(locktime) {
|
|
84
|
+
return Buff.num(locktime, 4).reverse();
|
|
85
|
+
}
|
|
86
|
+
export function encode_script_data(script) {
|
|
87
|
+
if (script !== null) {
|
|
88
|
+
Assert.is_hex(script);
|
|
89
|
+
return Buff.hex(script).prefix_varint('le');
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
return Buff.hex('00');
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -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,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,7 @@
|
|
|
1
|
+
import type { LocktimeInfo } from '../../types/index.js';
|
|
2
|
+
export declare namespace Locktime {
|
|
3
|
+
const encode: typeof encode_locktime;
|
|
4
|
+
const decode: typeof decode_locktime;
|
|
5
|
+
}
|
|
6
|
+
export declare function encode_locktime(locktime: LocktimeInfo): number;
|
|
7
|
+
export declare function decode_locktime(locktime: number): LocktimeInfo | null;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { Assert } from '@vbyte/micro-lib';
|
|
2
|
+
const LOCKTIME_THRESHOLD = 500000000;
|
|
3
|
+
export var Locktime;
|
|
4
|
+
(function (Locktime) {
|
|
5
|
+
Locktime.encode = encode_locktime;
|
|
6
|
+
Locktime.decode = decode_locktime;
|
|
7
|
+
})(Locktime || (Locktime = {}));
|
|
8
|
+
export function encode_locktime(locktime) {
|
|
9
|
+
switch (locktime.type) {
|
|
10
|
+
case 'timelock':
|
|
11
|
+
Assert.ok(locktime.stamp >= LOCKTIME_THRESHOLD, 'Invalid timestamp');
|
|
12
|
+
return locktime.stamp;
|
|
13
|
+
case 'heightlock':
|
|
14
|
+
Assert.ok(locktime.height > 0, 'height must be greater than 0');
|
|
15
|
+
Assert.ok(locktime.height < LOCKTIME_THRESHOLD, 'invalid block height');
|
|
16
|
+
return locktime.height;
|
|
17
|
+
default:
|
|
18
|
+
throw new Error('Invalid locktime type');
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export function decode_locktime(locktime) {
|
|
22
|
+
if (isNaN(locktime) || locktime <= 0) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
if (locktime < LOCKTIME_THRESHOLD) {
|
|
26
|
+
return {
|
|
27
|
+
type: 'heightlock',
|
|
28
|
+
height: locktime
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
return {
|
|
33
|
+
type: 'timelock',
|
|
34
|
+
stamp: locktime
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { TxData, TxOutput, TxOutputInfo, TxOutputType, TxValue, WitnessVersion } from '../../types/index.js';
|
|
2
|
+
export declare function is_return_script(script: string): boolean;
|
|
3
|
+
export declare function get_vout_info(txout: TxOutput): TxOutputInfo;
|
|
4
|
+
export declare function get_vout_type(script: string): TxOutputType;
|
|
5
|
+
export declare function get_vout_version(script: string): WitnessVersion;
|
|
6
|
+
export declare function get_txid(txdata: string | TxData): string;
|
|
7
|
+
export declare function get_txhash(txdata: string | TxData): string;
|
|
8
|
+
export declare function get_tx_value(txdata: string | TxData): TxValue;
|
|
@@ -0,0 +1,48 @@
|
|
|
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
|
+
export function is_return_script(script) {
|
|
6
|
+
return script.startsWith('6a');
|
|
7
|
+
}
|
|
8
|
+
export function get_vout_info(txout) {
|
|
9
|
+
return {
|
|
10
|
+
type: get_vout_type(txout.script_pk),
|
|
11
|
+
version: get_vout_version(txout.script_pk)
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
export function get_vout_type(script) {
|
|
15
|
+
for (const [type, regex] of Object.entries(LOCK_SCRIPT_REGEX)) {
|
|
16
|
+
if (regex.test(script))
|
|
17
|
+
return type;
|
|
18
|
+
}
|
|
19
|
+
return 'unknown';
|
|
20
|
+
}
|
|
21
|
+
export function get_vout_version(script) {
|
|
22
|
+
const wit_ver = script.slice(0, 4);
|
|
23
|
+
switch (wit_ver) {
|
|
24
|
+
case '0014':
|
|
25
|
+
return 0;
|
|
26
|
+
case '5120':
|
|
27
|
+
return 1;
|
|
28
|
+
default:
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
export function get_txid(txdata) {
|
|
33
|
+
const json = parse_tx_data(txdata);
|
|
34
|
+
const data = encode_tx_data(json, false);
|
|
35
|
+
return hash256(data).reverse().hex;
|
|
36
|
+
}
|
|
37
|
+
export function get_txhash(txdata) {
|
|
38
|
+
const json = parse_tx_data(txdata);
|
|
39
|
+
const data = encode_tx_data(json, true);
|
|
40
|
+
return hash256(data).reverse().hex;
|
|
41
|
+
}
|
|
42
|
+
export function get_tx_value(txdata) {
|
|
43
|
+
const tx = parse_tx_data(txdata);
|
|
44
|
+
const vin = tx.vin.reduce((acc, txin) => acc + (txin.prevout?.value ?? 0n), 0n);
|
|
45
|
+
const vout = tx.vout.reduce((acc, txout) => acc + txout.value, 0n);
|
|
46
|
+
const fees = (vin > vout) ? (vin - vout) : 0n;
|
|
47
|
+
return { fees, vin, vout };
|
|
48
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
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
|
+
export function parse_tx_data(txdata) {
|
|
5
|
+
if (typeof txdata === 'string') {
|
|
6
|
+
return decode_tx_data(txdata);
|
|
7
|
+
}
|
|
8
|
+
else {
|
|
9
|
+
assert_tx_template(txdata);
|
|
10
|
+
return create_tx_data(txdata);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { SequenceConfig, SequenceInfo } from '../../types/index.js';
|
|
2
|
+
export declare namespace Sequence {
|
|
3
|
+
const encode: typeof encode_sequence;
|
|
4
|
+
const decode: typeof decode_sequence;
|
|
5
|
+
}
|
|
6
|
+
export declare function encode_sequence(data: SequenceConfig): number;
|
|
7
|
+
export declare function decode_sequence(sequence: number | string): SequenceInfo | null;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
const TIMELOCK_DISABLE = 0x80000000;
|
|
2
|
+
const TIMELOCK_TYPE = 0x00400000;
|
|
3
|
+
const TIMELOCK_VALUE_MASK = 0x0000FFFF;
|
|
4
|
+
const TIMELOCK_VALUE_MAX = 0xFFFF;
|
|
5
|
+
const TIMELOCK_GRANULARITY = 512;
|
|
6
|
+
export var Sequence;
|
|
7
|
+
(function (Sequence) {
|
|
8
|
+
Sequence.encode = encode_sequence;
|
|
9
|
+
Sequence.decode = decode_sequence;
|
|
10
|
+
})(Sequence || (Sequence = {}));
|
|
11
|
+
export function encode_sequence(data) {
|
|
12
|
+
if (data.mode === 'height') {
|
|
13
|
+
const height = parse_height(data.height);
|
|
14
|
+
return (height & TIMELOCK_VALUE_MASK) >>> 0;
|
|
15
|
+
}
|
|
16
|
+
if (data.mode === 'stamp') {
|
|
17
|
+
const stamp = parse_stamp(data.stamp);
|
|
18
|
+
return (TIMELOCK_TYPE | (stamp & TIMELOCK_VALUE_MASK)) >>> 0;
|
|
19
|
+
}
|
|
20
|
+
throw new Error('invalid timelock mode: ' + data.mode);
|
|
21
|
+
}
|
|
22
|
+
export function decode_sequence(sequence) {
|
|
23
|
+
const seq = parse_sequence(sequence);
|
|
24
|
+
if (seq & TIMELOCK_DISABLE)
|
|
25
|
+
return null;
|
|
26
|
+
const value = seq & TIMELOCK_VALUE_MASK;
|
|
27
|
+
if (seq & TIMELOCK_TYPE) {
|
|
28
|
+
const stamp = value * TIMELOCK_GRANULARITY;
|
|
29
|
+
if (stamp > 0xFFFFFFFF) {
|
|
30
|
+
throw new Error('Decoded timestamp exceeds 32-bit limit');
|
|
31
|
+
}
|
|
32
|
+
return { mode: 'stamp', stamp };
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
if (value > TIMELOCK_VALUE_MAX) {
|
|
36
|
+
throw new Error('Decoded height exceeds maximum');
|
|
37
|
+
}
|
|
38
|
+
return { mode: 'height', height: value };
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function parse_sequence(sequence) {
|
|
42
|
+
const seq = (typeof sequence === 'string')
|
|
43
|
+
? parseInt(sequence, 16)
|
|
44
|
+
: sequence;
|
|
45
|
+
if (!Number.isInteger(seq) || seq < 0 || seq > 0xFFFFFFFF) {
|
|
46
|
+
throw new Error(`invalid sequence value: ${seq}`);
|
|
47
|
+
}
|
|
48
|
+
return seq;
|
|
49
|
+
}
|
|
50
|
+
function parse_stamp(stamp) {
|
|
51
|
+
if (stamp === undefined || !Number.isInteger(stamp)) {
|
|
52
|
+
throw new Error(`timestamp must be a number`);
|
|
53
|
+
}
|
|
54
|
+
const ts = Math.floor(stamp / TIMELOCK_GRANULARITY);
|
|
55
|
+
if (!Number.isInteger(ts) || ts < 0 || ts > TIMELOCK_VALUE_MAX) {
|
|
56
|
+
throw new Error(`timelock value must be an integer between 0 and ${TIMELOCK_VALUE_MAX} (in 512-second increments)`);
|
|
57
|
+
}
|
|
58
|
+
return ts;
|
|
59
|
+
}
|
|
60
|
+
function parse_height(height) {
|
|
61
|
+
if (height === undefined || !Number.isInteger(height) || height < 0 || height > TIMELOCK_VALUE_MAX) {
|
|
62
|
+
throw new Error(`Heightlock value must be an integer between 0 and ${TIMELOCK_VALUE_MAX}`);
|
|
63
|
+
}
|
|
64
|
+
return height;
|
|
65
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Bytes } from '@vbyte/buff';
|
|
2
|
+
import type { TxData, TxInput, TxOutput, TxSize, WitnessSize } from '../../types/index.js';
|
|
3
|
+
export declare function get_vsize(bytes: Bytes): number;
|
|
4
|
+
export declare function get_txsize(txdata: string | TxData): TxSize;
|
|
5
|
+
export declare function get_vin_size(vin: TxInput[]): number;
|
|
6
|
+
export declare function get_vout_size(vout: TxOutput[]): number;
|
|
7
|
+
export declare function get_segwit_size(txinputs: TxInput[]): number;
|
|
8
|
+
export declare function get_txin_size(txinput: TxInput): number;
|
|
9
|
+
export declare function get_txout_size(txoutput: TxOutput): number;
|
|
10
|
+
export declare function get_witness_size(witness: Bytes[]): WitnessSize;
|