@vbyte/btc-dev 1.1.8 → 2.1.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/CHANGELOG.md +127 -0
- package/LICENSE +21 -121
- package/README.md +69 -3
- package/dist/const.d.ts +3 -0
- package/dist/const.js +23 -22
- package/dist/error.d.ts +11 -0
- package/dist/error.js +20 -0
- package/dist/index.d.ts +12 -11
- package/dist/index.js +11 -10
- package/dist/lib/address/api.d.ts +2 -2
- package/dist/lib/address/api.js +13 -12
- package/dist/lib/address/encode.d.ts +1 -1
- package/dist/lib/address/encode.js +26 -24
- package/dist/lib/address/index.d.ts +6 -6
- package/dist/lib/address/index.js +6 -6
- package/dist/lib/address/p2pkh.d.ts +2 -2
- package/dist/lib/address/p2pkh.js +15 -15
- package/dist/lib/address/p2sh.d.ts +2 -2
- package/dist/lib/address/p2sh.js +14 -14
- package/dist/lib/address/p2tr.d.ts +2 -2
- package/dist/lib/address/p2tr.js +14 -14
- package/dist/lib/address/p2wpkh.d.ts +2 -2
- package/dist/lib/address/p2wpkh.js +15 -15
- package/dist/lib/address/p2wsh.d.ts +2 -2
- package/dist/lib/address/p2wsh.js +14 -14
- package/dist/lib/address/script.d.ts +1 -1
- package/dist/lib/address/script.js +16 -16
- package/dist/lib/address/util.d.ts +1 -1
- package/dist/lib/address/util.js +24 -22
- package/dist/lib/meta/index.d.ts +4 -4
- package/dist/lib/meta/index.js +4 -4
- package/dist/lib/meta/locktime.d.ts +1 -1
- package/dist/lib/meta/locktime.js +13 -12
- package/dist/lib/meta/ref.js +13 -9
- package/dist/lib/meta/scribe.d.ts +2 -2
- package/dist/lib/meta/scribe.js +71 -56
- package/dist/lib/meta/sequence.d.ts +1 -1
- package/dist/lib/meta/sequence.js +21 -19
- package/dist/lib/script/decode.d.ts +2 -2
- package/dist/lib/script/decode.js +53 -17
- package/dist/lib/script/encode.d.ts +1 -1
- package/dist/lib/script/encode.js +21 -16
- package/dist/lib/script/index.d.ts +5 -13
- package/dist/lib/script/index.js +5 -14
- package/dist/lib/script/lock.d.ts +2 -2
- package/dist/lib/script/lock.js +15 -12
- package/dist/lib/script/util.js +4 -4
- package/dist/lib/script/words.js +131 -130
- package/dist/lib/sighash/index.d.ts +3 -3
- package/dist/lib/sighash/index.js +3 -3
- package/dist/lib/sighash/segwit.d.ts +2 -2
- package/dist/lib/sighash/segwit.js +18 -14
- package/dist/lib/sighash/taproot.d.ts +2 -2
- package/dist/lib/sighash/taproot.js +27 -23
- package/dist/lib/sighash/util.d.ts +2 -2
- package/dist/lib/sighash/util.js +8 -7
- package/dist/lib/signer/index.d.ts +2 -2
- package/dist/lib/signer/index.js +2 -2
- package/dist/lib/signer/sign.d.ts +1 -1
- package/dist/lib/signer/sign.js +43 -7
- package/dist/lib/signer/verify.d.ts +17 -3
- package/dist/lib/signer/verify.js +232 -3
- package/dist/lib/taproot/cblock.d.ts +1 -1
- package/dist/lib/taproot/cblock.js +16 -17
- package/dist/lib/taproot/encode.d.ts +1 -1
- package/dist/lib/taproot/encode.js +9 -8
- package/dist/lib/taproot/index.d.ts +4 -4
- package/dist/lib/taproot/index.js +4 -4
- package/dist/lib/taproot/parse.d.ts +1 -1
- package/dist/lib/taproot/parse.js +15 -15
- package/dist/lib/taproot/tree.d.ts +2 -2
- package/dist/lib/taproot/tree.js +12 -7
- package/dist/lib/tx/create.d.ts +1 -1
- package/dist/lib/tx/create.js +28 -12
- package/dist/lib/tx/decode.d.ts +2 -2
- package/dist/lib/tx/decode.js +52 -17
- package/dist/lib/tx/encode.d.ts +2 -2
- package/dist/lib/tx/encode.js +13 -16
- package/dist/lib/tx/index.d.ts +7 -7
- package/dist/lib/tx/index.js +7 -7
- package/dist/lib/tx/parse.d.ts +1 -1
- package/dist/lib/tx/parse.js +9 -9
- package/dist/lib/tx/size.d.ts +2 -2
- package/dist/lib/tx/size.js +9 -11
- package/dist/lib/tx/util.d.ts +2 -2
- package/dist/lib/tx/util.js +22 -20
- package/dist/lib/tx/validate.d.ts +1 -1
- package/dist/lib/tx/validate.js +37 -9
- package/dist/lib/witness/index.d.ts +2 -2
- package/dist/lib/witness/index.js +2 -2
- package/dist/lib/witness/parse.d.ts +2 -2
- package/dist/lib/witness/parse.js +24 -23
- package/dist/lib/witness/util.d.ts +2 -2
- package/dist/lib/witness/util.js +5 -5
- package/dist/main.cjs +3305 -2035
- package/dist/main.cjs.map +1 -1
- package/dist/module.mjs +3303 -2036
- package/dist/module.mjs.map +1 -1
- package/dist/package.json +24 -19
- package/dist/schema/base.d.ts +1 -1
- package/dist/schema/base.js +17 -13
- package/dist/schema/index.d.ts +2 -2
- package/dist/schema/index.js +2 -2
- package/dist/schema/taproot.d.ts +1 -1
- package/dist/schema/taproot.js +2 -2
- package/dist/schema/tx.d.ts +1 -1
- package/dist/schema/tx.js +4 -4
- package/dist/script.js +10 -12
- package/dist/script.js.map +1 -1
- package/dist/types/address.d.ts +4 -4
- package/dist/types/index.d.ts +8 -8
- package/dist/types/index.js +8 -8
- package/dist/types/meta.d.ts +4 -4
- package/dist/types/psbt.d.ts +2 -2
- package/dist/types/script.d.ts +2 -2
- package/dist/types/sighash.d.ts +2 -2
- package/dist/types/witness.d.ts +5 -5
- package/docs/API.md +1145 -0
- package/docs/CONVENTIONS.md +316 -0
- package/docs/FAQ.md +396 -0
- package/docs/GUIDE.md +1102 -0
- package/package.json +24 -19
- package/src/const.ts +0 -61
- package/src/index.ts +0 -13
- package/src/lib/address/api.ts +0 -50
- package/src/lib/address/encode.ts +0 -183
- package/src/lib/address/index.ts +0 -7
- package/src/lib/address/p2pkh.ts +0 -94
- package/src/lib/address/p2sh.ts +0 -96
- package/src/lib/address/p2tr.ts +0 -91
- package/src/lib/address/p2wpkh.ts +0 -94
- package/src/lib/address/p2wsh.ts +0 -92
- package/src/lib/address/script.ts +0 -63
- package/src/lib/address/util.ts +0 -87
- package/src/lib/meta/index.ts +0 -4
- package/src/lib/meta/locktime.ts +0 -57
- package/src/lib/meta/ref.ts +0 -107
- package/src/lib/meta/scribe.ts +0 -256
- package/src/lib/meta/sequence.ts +0 -146
- package/src/lib/script/decode.ts +0 -85
- package/src/lib/script/encode.ts +0 -129
- package/src/lib/script/index.ts +0 -20
- package/src/lib/script/lock.ts +0 -73
- package/src/lib/script/util.ts +0 -78
- package/src/lib/script/words.ts +0 -182
- package/src/lib/sighash/index.ts +0 -3
- package/src/lib/sighash/segwit.ts +0 -152
- package/src/lib/sighash/taproot.ts +0 -206
- package/src/lib/sighash/util.ts +0 -51
- package/src/lib/signer/index.ts +0 -2
- package/src/lib/signer/sign.ts +0 -39
- package/src/lib/signer/verify.ts +0 -88
- package/src/lib/taproot/cblock.ts +0 -96
- package/src/lib/taproot/encode.ts +0 -49
- package/src/lib/taproot/index.ts +0 -4
- package/src/lib/taproot/parse.ts +0 -65
- package/src/lib/taproot/tree.ts +0 -94
- package/src/lib/tx/create.ts +0 -90
- package/src/lib/tx/decode.ts +0 -123
- package/src/lib/tx/encode.ts +0 -155
- package/src/lib/tx/index.ts +0 -7
- package/src/lib/tx/parse.ts +0 -69
- package/src/lib/tx/size.ts +0 -68
- package/src/lib/tx/util.ts +0 -111
- package/src/lib/tx/validate.ts +0 -49
- package/src/lib/witness/index.ts +0 -2
- package/src/lib/witness/parse.ts +0 -127
- package/src/lib/witness/util.ts +0 -18
- package/src/schema/base.ts +0 -57
- package/src/schema/index.ts +0 -2
- package/src/schema/taproot.ts +0 -12
- package/src/schema/tx.ts +0 -48
- package/src/types/address.ts +0 -35
- package/src/types/index.ts +0 -8
- package/src/types/meta.ts +0 -48
- package/src/types/psbt.ts +0 -15
- package/src/types/script.ts +0 -18
- package/src/types/sighash.ts +0 -16
- package/src/types/taproot.ts +0 -41
- package/src/types/txdata.ts +0 -85
- package/src/types/witness.ts +0 -42
|
@@ -1,26 +1,29 @@
|
|
|
1
|
-
import { Buff } from
|
|
2
|
-
import { Assert } from
|
|
3
|
-
import { hash340, sha256 } from
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
6
|
-
import
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
1
|
+
import { Buff } from "@vbyte/buff";
|
|
2
|
+
import { Assert } from "@vbyte/util";
|
|
3
|
+
import { hash340, sha256 } from "@vbyte/crypto/hash";
|
|
4
|
+
import * as CONST from "../../const.js";
|
|
5
|
+
import { ValidationError } from "../../error.js";
|
|
6
|
+
import { encode_tapscript } from "../../lib/taproot/encode.js";
|
|
7
|
+
import { encode_script_data, encode_tx_locktime, encode_tx_version, encode_txin_sequence, encode_txin_txid, encode_txin_vout, encode_vout_value, } from "../../lib/tx/encode.js";
|
|
8
|
+
import { parse_tx } from "../../lib/tx/parse.js";
|
|
9
|
+
import { get_annex_data, get_prevout, parse_txinput } from "./util.js";
|
|
9
10
|
export function hash_taproot_tx(template, config = {}) {
|
|
10
11
|
const preimage = get_taproot_tx_preimage(template, config);
|
|
11
|
-
return hash340(
|
|
12
|
+
return hash340("TapSighash", preimage);
|
|
12
13
|
}
|
|
13
14
|
export function get_taproot_tx_preimage(template, config = {}) {
|
|
14
|
-
const { script, txindex, sigflag = 0x00, extflag = 0x00, key_version = 0x00, separator_pos =
|
|
15
|
+
const { script, txindex, sigflag = 0x00, extflag = 0x00, key_version = 0x00, separator_pos = 0xffffffff, } = config;
|
|
15
16
|
const tx = parse_tx(template);
|
|
16
17
|
const { version, vin: input, vout: output, locktime } = tx;
|
|
17
18
|
const txinput = parse_txinput(tx, config);
|
|
18
19
|
const { txid, vout, sequence, witness = [] } = txinput;
|
|
19
20
|
if (!CONST.SIGHASH_TAPROOT.includes(sigflag)) {
|
|
20
|
-
throw new
|
|
21
|
+
throw new ValidationError(`invalid taproot sighash type: 0x${sigflag.toString(16)}. ` +
|
|
22
|
+
`Valid values: SIGHASH_DEFAULT (0x00), SIGHASH_ALL (0x01), SIGHASH_NONE (0x02), SIGHASH_SINGLE (0x03), ` +
|
|
23
|
+
`or combined with ANYONECANPAY (0x81, 0x82, 0x83)`);
|
|
21
24
|
}
|
|
22
25
|
if (extflag < 0 || extflag > 127) {
|
|
23
|
-
throw new
|
|
26
|
+
throw new ValidationError(`extension flag out of range: ${extflag}. Valid range is 0-127`);
|
|
24
27
|
}
|
|
25
28
|
let { extension } = config;
|
|
26
29
|
if (script !== undefined) {
|
|
@@ -28,17 +31,17 @@ export function get_taproot_tx_preimage(template, config = {}) {
|
|
|
28
31
|
}
|
|
29
32
|
const is_anypay = (sigflag & 0x80) === 0x80;
|
|
30
33
|
const annex = get_annex_data(witness);
|
|
31
|
-
const annexBit =
|
|
32
|
-
const extendBit =
|
|
33
|
-
const spendType = (
|
|
34
|
+
const annexBit = annex !== undefined ? 1 : 0;
|
|
35
|
+
const extendBit = extension !== undefined ? 1 : 0;
|
|
36
|
+
const spendType = (extflag + extendBit) * 2 + annexBit;
|
|
34
37
|
const preimage = [
|
|
35
38
|
Buff.num(0x00, 1),
|
|
36
39
|
Buff.num(sigflag, 1),
|
|
37
40
|
encode_tx_version(version),
|
|
38
|
-
encode_tx_locktime(locktime)
|
|
41
|
+
encode_tx_locktime(locktime),
|
|
39
42
|
];
|
|
40
43
|
if (!is_anypay) {
|
|
41
|
-
const prevouts = input.map(e => get_prevout(e));
|
|
44
|
+
const prevouts = input.map((e) => get_prevout(e));
|
|
42
45
|
preimage.push(bip341_hash_outpoints(input), bip341_hash_amounts(prevouts), bip341_hash_scripts(prevouts), bip341_hash_sequence(input));
|
|
43
46
|
}
|
|
44
47
|
if ((sigflag & 0x03) < 2 || (sigflag & 0x03) > 3) {
|
|
@@ -50,18 +53,19 @@ export function get_taproot_tx_preimage(template, config = {}) {
|
|
|
50
53
|
preimage.push(encode_txin_txid(txid), encode_txin_vout(vout), encode_vout_value(value), encode_script_data(script_pk), encode_txin_sequence(sequence));
|
|
51
54
|
}
|
|
52
55
|
else {
|
|
53
|
-
Assert.ok(typeof txindex ===
|
|
56
|
+
Assert.ok(typeof txindex === "number");
|
|
54
57
|
preimage.push(Buff.num(txindex, 4).reverse());
|
|
55
58
|
}
|
|
56
59
|
if (annex !== undefined) {
|
|
57
60
|
preimage.push(annex);
|
|
58
61
|
}
|
|
59
62
|
if ((sigflag & 0x03) === 0x03) {
|
|
60
|
-
Assert.ok(typeof txindex ===
|
|
63
|
+
Assert.ok(typeof txindex === "number", "txindex required for SIGHASH_SINGLE");
|
|
64
|
+
Assert.ok(txindex >= 0 && txindex < output.length, `txindex ${txindex} out of bounds for ${output.length} outputs`);
|
|
61
65
|
preimage.push(bip341_hash_output(output[txindex]));
|
|
62
66
|
}
|
|
63
67
|
if (extension !== undefined) {
|
|
64
|
-
preimage.push(Buff.bytes(extension), Buff.num(key_version), Buff.num(separator_pos, 4,
|
|
68
|
+
preimage.push(Buff.bytes(extension), Buff.num(key_version), Buff.num(separator_pos, 4, "le"));
|
|
65
69
|
}
|
|
66
70
|
return Buff.join(preimage);
|
|
67
71
|
}
|
|
@@ -74,13 +78,13 @@ export function bip341_hash_outpoints(vin) {
|
|
|
74
78
|
return sha256(Buff.join(stack));
|
|
75
79
|
}
|
|
76
80
|
export function bip341_hash_sequence(vin) {
|
|
77
|
-
return sha256(...vin.map(vin => encode_txin_sequence(vin.sequence)));
|
|
81
|
+
return sha256(...vin.map((vin) => encode_txin_sequence(vin.sequence)));
|
|
78
82
|
}
|
|
79
83
|
export function bip341_hash_amounts(prevouts) {
|
|
80
|
-
return sha256(...prevouts.map(prevout => encode_vout_value(prevout.value)));
|
|
84
|
+
return sha256(...prevouts.map((prevout) => encode_vout_value(prevout.value)));
|
|
81
85
|
}
|
|
82
86
|
export function bip341_hash_scripts(prevouts) {
|
|
83
|
-
return sha256(...prevouts.map(prevout => encode_script_data(prevout.script_pk)));
|
|
87
|
+
return sha256(...prevouts.map((prevout) => encode_script_data(prevout.script_pk)));
|
|
84
88
|
}
|
|
85
89
|
export function bip341_hash_outputs(vout) {
|
|
86
90
|
const stack = [];
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Buff } from
|
|
2
|
-
import type { SigHashOptions,
|
|
1
|
+
import { Buff } from "@vbyte/buff";
|
|
2
|
+
import type { SigHashOptions, TxData, TxInput, TxOutput } from "../../types/index.js";
|
|
3
3
|
export declare function get_prevout(vin: TxInput): TxOutput;
|
|
4
4
|
export declare function parse_txinput(txdata: TxData, config?: SigHashOptions): TxInput;
|
|
5
5
|
export declare function get_annex_data(witness?: string[]): Buff | undefined;
|
package/dist/lib/sighash/util.js
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
|
-
import { Buff } from
|
|
2
|
-
import { Assert } from
|
|
3
|
-
import { sha256 } from
|
|
1
|
+
import { Buff } from "@vbyte/buff";
|
|
2
|
+
import { Assert } from "@vbyte/util";
|
|
3
|
+
import { sha256 } from "@vbyte/crypto/hash";
|
|
4
|
+
import { ValidationError } from "../../error.js";
|
|
4
5
|
export function get_prevout(vin) {
|
|
5
|
-
Assert.exists(vin.prevout,
|
|
6
|
+
Assert.exists(vin.prevout, `Prevout data missing for input: ${String(vin.txid)}`);
|
|
6
7
|
return vin.prevout;
|
|
7
8
|
}
|
|
8
9
|
export function parse_txinput(txdata, config) {
|
|
9
10
|
let { txindex, txinput } = config ?? {};
|
|
10
11
|
if (txindex !== undefined) {
|
|
11
12
|
if (txindex >= txdata.vin.length) {
|
|
12
|
-
throw new
|
|
13
|
+
throw new ValidationError(`input index ${txindex} out of bounds. Transaction has ${txdata.vin.length} inputs (indices 0-${txdata.vin.length - 1})`);
|
|
13
14
|
}
|
|
14
15
|
txinput = txdata.vin.at(txindex);
|
|
15
16
|
}
|
|
@@ -22,8 +23,8 @@ export function get_annex_data(witness) {
|
|
|
22
23
|
if (witness.length < 2)
|
|
23
24
|
return;
|
|
24
25
|
const annex = witness.at(-1);
|
|
25
|
-
if (typeof annex ===
|
|
26
|
-
const bytes = Buff.hex(annex).prefix_varint(
|
|
26
|
+
if (typeof annex === "string" && annex.startsWith("50")) {
|
|
27
|
+
const bytes = Buff.hex(annex).prefix_varint("be");
|
|
27
28
|
return sha256(bytes);
|
|
28
29
|
}
|
|
29
30
|
return undefined;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export * from
|
|
2
|
-
export * from
|
|
1
|
+
export * from "./sign.js";
|
|
2
|
+
export * from "./verify.js";
|
package/dist/lib/signer/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export * from
|
|
2
|
-
export * from
|
|
1
|
+
export * from "./sign.js";
|
|
2
|
+
export * from "./verify.js";
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import type { SigHashOptions, TxData } from
|
|
1
|
+
import type { SigHashOptions, TxData } from "../../types/index.js";
|
|
2
2
|
export declare function sign_segwit_tx(seckey: string, txdata: TxData, options: SigHashOptions): string;
|
|
3
3
|
export declare function sign_taproot_tx(seckey: string, txdata: TxData, options: SigHashOptions): string;
|
package/dist/lib/signer/sign.js
CHANGED
|
@@ -1,10 +1,44 @@
|
|
|
1
|
-
import { Buff } from
|
|
2
|
-
import { ECC } from
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { hash_segwit_tx } from
|
|
6
|
-
import { hash_taproot_tx } from
|
|
1
|
+
import { Buff } from "@vbyte/buff";
|
|
2
|
+
import { ECC } from "@vbyte/crypto";
|
|
3
|
+
import { SIGHASH_DEFAULT, SIGHASH_SEGWIT, SIGHASH_TAPROOT } from "../../const.js";
|
|
4
|
+
import { ConfigError, ValidationError } from "../../error.js";
|
|
5
|
+
import { hash_segwit_tx } from "../../lib/sighash/segwit.js";
|
|
6
|
+
import { hash_taproot_tx } from "../../lib/sighash/taproot.js";
|
|
7
|
+
import { parse_tx } from "../../lib/tx/parse.js";
|
|
8
|
+
const SECKEY_REGEX = /^[0-9a-fA-F]{64}$/;
|
|
9
|
+
function validate_seckey(seckey) {
|
|
10
|
+
if (typeof seckey !== "string") {
|
|
11
|
+
throw new ValidationError("Secret key must be a string", "seckey");
|
|
12
|
+
}
|
|
13
|
+
if (!SECKEY_REGEX.test(seckey)) {
|
|
14
|
+
throw new ValidationError("Invalid secret key format: expected 32-byte hex string (64 characters)", "seckey");
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function validate_sighash_options(options, validFlags) {
|
|
18
|
+
const { sigflag, txindex } = options;
|
|
19
|
+
if (sigflag !== undefined) {
|
|
20
|
+
if (typeof sigflag !== "number" || !Number.isInteger(sigflag)) {
|
|
21
|
+
throw new ConfigError("sigflag must be an integer");
|
|
22
|
+
}
|
|
23
|
+
const normalizedFlag = sigflag & 0x7f;
|
|
24
|
+
const isAnypay = (sigflag & 0x80) === 0x80;
|
|
25
|
+
const baseFlag = isAnypay ? normalizedFlag | 0x80 : normalizedFlag;
|
|
26
|
+
if (!validFlags.includes(baseFlag) &&
|
|
27
|
+
!validFlags.includes(normalizedFlag)) {
|
|
28
|
+
throw new ConfigError(`Invalid sigflag: ${sigflag}`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
if (txindex !== undefined) {
|
|
32
|
+
if (typeof txindex !== "number" ||
|
|
33
|
+
!Number.isInteger(txindex) ||
|
|
34
|
+
txindex < 0) {
|
|
35
|
+
throw new ValidationError("txindex must be a non-negative integer", "txindex");
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
7
39
|
export function sign_segwit_tx(seckey, txdata, options) {
|
|
40
|
+
validate_seckey(seckey);
|
|
41
|
+
validate_sighash_options(options, SIGHASH_SEGWIT);
|
|
8
42
|
const tx = parse_tx(txdata);
|
|
9
43
|
const msg = hash_segwit_tx(tx, options);
|
|
10
44
|
const sig = ECC.sign_ecdsa(seckey, msg).hex;
|
|
@@ -12,6 +46,8 @@ export function sign_segwit_tx(seckey, txdata, options) {
|
|
|
12
46
|
return sig + flag;
|
|
13
47
|
}
|
|
14
48
|
export function sign_taproot_tx(seckey, txdata, options) {
|
|
49
|
+
validate_seckey(seckey);
|
|
50
|
+
validate_sighash_options(options, SIGHASH_TAPROOT);
|
|
15
51
|
const tx = parse_tx(txdata);
|
|
16
52
|
const msg = hash_taproot_tx(tx, options);
|
|
17
53
|
const sig = ECC.sign_bip340(seckey, msg).hex;
|
|
@@ -19,5 +55,5 @@ export function sign_taproot_tx(seckey, txdata, options) {
|
|
|
19
55
|
return sig + flag;
|
|
20
56
|
}
|
|
21
57
|
function format_sigflag(flag) {
|
|
22
|
-
return
|
|
58
|
+
return flag !== 0 ? Buff.num(flag, 1).hex : "";
|
|
23
59
|
}
|
|
@@ -1,3 +1,17 @@
|
|
|
1
|
-
import { Bytes } from
|
|
2
|
-
import type { SigHashOptions, TxData } from
|
|
3
|
-
export
|
|
1
|
+
import { type Bytes } from "@vbyte/buff";
|
|
2
|
+
import type { SigHashOptions, TxData } from "../../types/index.js";
|
|
3
|
+
export interface VerifyOptions extends SigHashOptions {
|
|
4
|
+
throws?: boolean;
|
|
5
|
+
}
|
|
6
|
+
export interface VerifyResult {
|
|
7
|
+
valid: boolean;
|
|
8
|
+
inputs: InputVerifyResult[];
|
|
9
|
+
error?: string;
|
|
10
|
+
}
|
|
11
|
+
export interface InputVerifyResult {
|
|
12
|
+
index: number;
|
|
13
|
+
valid: boolean;
|
|
14
|
+
type?: string | null;
|
|
15
|
+
error?: string;
|
|
16
|
+
}
|
|
17
|
+
export declare function verify_tx(txdata: TxData | Bytes, options?: VerifyOptions): VerifyResult;
|
|
@@ -1,4 +1,233 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import { Buff } from "@vbyte/buff";
|
|
2
|
+
import { ECC } from "@vbyte/crypto";
|
|
3
|
+
import { hash160 } from "@vbyte/crypto/hash";
|
|
4
|
+
import { ValidationError } from "../../error.js";
|
|
5
|
+
import { hash_segwit_tx } from "../../lib/sighash/segwit.js";
|
|
6
|
+
import { hash_taproot_tx } from "../../lib/sighash/taproot.js";
|
|
7
|
+
import { verify_taproot } from "../../lib/taproot/cblock.js";
|
|
8
|
+
import { encode_tapscript } from "../../lib/taproot/encode.js";
|
|
9
|
+
import { parse_tx } from "../../lib/tx/index.js";
|
|
10
|
+
import { parse_witness } from "../../lib/witness/parse.js";
|
|
11
|
+
export function verify_tx(txdata, options = {}) {
|
|
12
|
+
const { throws = false } = options;
|
|
13
|
+
const tx = parse_tx(txdata);
|
|
14
|
+
const inputs = [];
|
|
15
|
+
let allValid = true;
|
|
16
|
+
for (let i = 0; i < tx.vin.length; i++) {
|
|
17
|
+
const vin = tx.vin[i];
|
|
18
|
+
if (vin.coinbase !== null) {
|
|
19
|
+
inputs.push({ index: i, valid: true, type: "coinbase" });
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
const result = verify_input(tx, vin, i, options);
|
|
23
|
+
inputs.push(result);
|
|
24
|
+
if (!result.valid) {
|
|
25
|
+
allValid = false;
|
|
26
|
+
if (throws) {
|
|
27
|
+
throw new ValidationError(`Input ${i} verification failed: ${result.error}`, `vin[${i}]`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
valid: allValid,
|
|
33
|
+
inputs,
|
|
34
|
+
error: allValid ? undefined : "One or more inputs failed verification",
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function verify_input(tx, vin, index, options) {
|
|
38
|
+
try {
|
|
39
|
+
const { witness = [] } = vin;
|
|
40
|
+
if (witness.length === 0) {
|
|
41
|
+
return { index, valid: true, type: null };
|
|
42
|
+
}
|
|
43
|
+
const witnessData = parse_witness(witness.map((e) => Buff.hex(e)));
|
|
44
|
+
const { type, version } = witnessData;
|
|
45
|
+
if (type === null || version === null) {
|
|
46
|
+
return { index, valid: false, type, error: "Unknown witness type" };
|
|
47
|
+
}
|
|
48
|
+
const prevout = vin.prevout;
|
|
49
|
+
if (prevout === null || prevout === undefined) {
|
|
50
|
+
return { index, valid: false, type, error: "Missing prevout data" };
|
|
51
|
+
}
|
|
52
|
+
if (version === 0) {
|
|
53
|
+
return verify_segwit_input(tx, vin, index, witnessData, options);
|
|
54
|
+
}
|
|
55
|
+
else if (version === 1) {
|
|
56
|
+
return verify_taproot_input(tx, vin, index, witnessData, options);
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
index,
|
|
60
|
+
valid: false,
|
|
61
|
+
type,
|
|
62
|
+
error: `Unsupported witness version: ${version}`,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
catch (err) {
|
|
66
|
+
const error = err instanceof Error ? err.message : String(err);
|
|
67
|
+
return { index, valid: false, error };
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
function verify_segwit_input(tx, vin, index, witnessData, options) {
|
|
71
|
+
const { type, params, script } = witnessData;
|
|
72
|
+
if (params.length < 1) {
|
|
73
|
+
return { index, valid: false, type, error: "Missing signature in witness" };
|
|
74
|
+
}
|
|
75
|
+
const sigHex = params[0];
|
|
76
|
+
const { signature, sigflag } = parse_ecdsa_signature(sigHex);
|
|
77
|
+
let pubkey;
|
|
78
|
+
let hashScript;
|
|
79
|
+
if (type === "p2wpkh") {
|
|
80
|
+
if (params.length < 2) {
|
|
81
|
+
return {
|
|
82
|
+
index,
|
|
83
|
+
valid: false,
|
|
84
|
+
type,
|
|
85
|
+
error: "Missing pubkey in P2WPKH witness",
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
pubkey = params[1];
|
|
89
|
+
const pkh = hash160(pubkey).hex;
|
|
90
|
+
hashScript = `76a914${pkh}88ac`;
|
|
91
|
+
}
|
|
92
|
+
else if (type === "p2wsh") {
|
|
93
|
+
if (script === null) {
|
|
94
|
+
return {
|
|
95
|
+
index,
|
|
96
|
+
valid: false,
|
|
97
|
+
type,
|
|
98
|
+
error: "Missing script in P2WSH witness",
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
hashScript = script;
|
|
102
|
+
if (params.length >= 2) {
|
|
103
|
+
pubkey = params[1];
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
return {
|
|
107
|
+
index,
|
|
108
|
+
valid: false,
|
|
109
|
+
type,
|
|
110
|
+
error: "Missing pubkey in P2WSH witness",
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
return {
|
|
116
|
+
index,
|
|
117
|
+
valid: false,
|
|
118
|
+
type,
|
|
119
|
+
error: `Unexpected segwit type: ${type}`,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
const sighashOptions = {
|
|
123
|
+
...options,
|
|
124
|
+
txindex: index,
|
|
125
|
+
txinput: vin,
|
|
126
|
+
pubkey: type === "p2wpkh" ? pubkey : undefined,
|
|
127
|
+
script: type === "p2wsh" ? hashScript : undefined,
|
|
128
|
+
sigflag,
|
|
129
|
+
};
|
|
130
|
+
const hash = hash_segwit_tx(tx, sighashOptions);
|
|
131
|
+
const isValid = ECC.verify_ecdsa(signature, hash, pubkey);
|
|
132
|
+
return {
|
|
133
|
+
index,
|
|
134
|
+
valid: isValid,
|
|
135
|
+
type,
|
|
136
|
+
error: isValid ? undefined : "Invalid ECDSA signature",
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
function verify_taproot_input(tx, vin, index, witnessData, options) {
|
|
140
|
+
const { type, params, script, cblock } = witnessData;
|
|
141
|
+
if (vin.prevout == null) {
|
|
142
|
+
return {
|
|
143
|
+
index,
|
|
144
|
+
valid: false,
|
|
145
|
+
type,
|
|
146
|
+
error: "Missing prevout for taproot verification",
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
const prevout = vin.prevout;
|
|
150
|
+
if (params.length < 1) {
|
|
151
|
+
return { index, valid: false, type, error: "Missing signature in witness" };
|
|
152
|
+
}
|
|
153
|
+
const sigHex = params[0];
|
|
154
|
+
const { signature, sigflag } = parse_schnorr_signature(sigHex);
|
|
155
|
+
const tapkey = prevout.script_pk.slice(4);
|
|
156
|
+
let pubkey;
|
|
157
|
+
let extension;
|
|
158
|
+
if (type === "p2tr") {
|
|
159
|
+
pubkey = tapkey;
|
|
160
|
+
}
|
|
161
|
+
else if (type === "p2ts") {
|
|
162
|
+
if (cblock === null || script === null) {
|
|
163
|
+
return {
|
|
164
|
+
index,
|
|
165
|
+
valid: false,
|
|
166
|
+
type,
|
|
167
|
+
error: "Missing cblock or script in script-path spend",
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
const target = encode_tapscript(script).hex;
|
|
171
|
+
const pathValid = verify_taproot(tapkey, target, cblock);
|
|
172
|
+
if (!pathValid) {
|
|
173
|
+
return {
|
|
174
|
+
index,
|
|
175
|
+
valid: false,
|
|
176
|
+
type,
|
|
177
|
+
error: "Control block verification failed",
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
if (params.length >= 2 && params[1].length === 64) {
|
|
181
|
+
pubkey = params[1];
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
pubkey = tapkey;
|
|
185
|
+
}
|
|
186
|
+
extension = target;
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
return {
|
|
190
|
+
index,
|
|
191
|
+
valid: false,
|
|
192
|
+
type,
|
|
193
|
+
error: `Unexpected taproot type: ${type}`,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
const sighashOptions = {
|
|
197
|
+
...options,
|
|
198
|
+
txindex: index,
|
|
199
|
+
txinput: vin,
|
|
200
|
+
sigflag,
|
|
201
|
+
extension: extension,
|
|
202
|
+
script: type === "p2ts" ? (script ?? undefined) : undefined,
|
|
203
|
+
};
|
|
204
|
+
const hash = hash_taproot_tx(tx, sighashOptions);
|
|
205
|
+
const isValid = ECC.verify_bip340(signature, hash, pubkey);
|
|
206
|
+
return {
|
|
207
|
+
index,
|
|
208
|
+
valid: isValid,
|
|
209
|
+
type,
|
|
210
|
+
error: isValid ? undefined : "Invalid Schnorr signature",
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
function parse_ecdsa_signature(sigHex) {
|
|
214
|
+
const sigBytes = Buff.hex(sigHex);
|
|
215
|
+
const sigflag = sigBytes.at(-1) ?? 0x01;
|
|
216
|
+
const signature = sigBytes.slice(0, -1).hex;
|
|
217
|
+
return { signature, sigflag };
|
|
218
|
+
}
|
|
219
|
+
function parse_schnorr_signature(sigHex) {
|
|
220
|
+
const sigBytes = Buff.hex(sigHex);
|
|
221
|
+
if (sigBytes.length === 64) {
|
|
222
|
+
return { signature: sigHex, sigflag: 0x00 };
|
|
223
|
+
}
|
|
224
|
+
else if (sigBytes.length === 65) {
|
|
225
|
+
const sigflag = sigBytes.at(-1) ?? 0x00;
|
|
226
|
+
if (sigflag === 0x00) {
|
|
227
|
+
throw new ValidationError("0x00 is not a valid appended sigflag (use 64-byte signature for SIGHASH_DEFAULT)", "sigflag");
|
|
228
|
+
}
|
|
229
|
+
const signature = sigBytes.slice(0, 64).hex;
|
|
230
|
+
return { signature, sigflag };
|
|
231
|
+
}
|
|
232
|
+
throw new ValidationError(`Invalid Schnorr signature length: ${sigBytes.length} (expected 64 or 65 bytes)`, "signature");
|
|
4
233
|
}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { TaprootConfig, TaprootContext } from
|
|
1
|
+
import type { TaprootConfig, TaprootContext } from "../../types/index.js";
|
|
2
2
|
export declare function create_taproot(config: TaprootConfig): TaprootContext;
|
|
3
3
|
export declare function verify_taproot(tapkey: string, target: string, cblock: string): boolean;
|
|
@@ -1,18 +1,17 @@
|
|
|
1
|
-
import { Buff } from
|
|
2
|
-
import { Assert
|
|
3
|
-
import {
|
|
4
|
-
import { TAPLEAF_DEFAULT_VERSION } from
|
|
5
|
-
import * as Schema from
|
|
6
|
-
import { encode_tapbranch, encode_taptweak } from
|
|
7
|
-
import {
|
|
1
|
+
import { Buff } from "@vbyte/buff";
|
|
2
|
+
import { Assert } from "@vbyte/util";
|
|
3
|
+
import { ECC } from "@vbyte/crypto";
|
|
4
|
+
import { TAPLEAF_DEFAULT_VERSION } from "../../const.js";
|
|
5
|
+
import * as Schema from "../../schema/index.js";
|
|
6
|
+
import { encode_tapbranch, encode_taptweak } from "./encode.js";
|
|
7
|
+
import { parse_cblock, parse_pubkey_parity } from "./parse.js";
|
|
8
|
+
import { merkleize } from "./tree.js";
|
|
8
9
|
const DEFAULT_VERSION = TAPLEAF_DEFAULT_VERSION;
|
|
9
10
|
export function create_taproot(config) {
|
|
10
11
|
Schema.taproot.config.parse(config);
|
|
11
12
|
const { pubkey, version = DEFAULT_VERSION } = config;
|
|
12
13
|
const leaves = config.leaves ?? [];
|
|
13
|
-
const target =
|
|
14
|
-
? Buff.bytes(config.target).hex
|
|
15
|
-
: undefined;
|
|
14
|
+
const target = config.target !== undefined ? Buff.bytes(config.target).hex : undefined;
|
|
16
15
|
let path = [], taproot;
|
|
17
16
|
if (leaves.length > 0) {
|
|
18
17
|
const [root, _, proofs] = merkleize(leaves, target);
|
|
@@ -23,13 +22,13 @@ export function create_taproot(config) {
|
|
|
23
22
|
taproot = target;
|
|
24
23
|
}
|
|
25
24
|
const taptweak = encode_taptweak(pubkey, taproot);
|
|
26
|
-
const twk_key = ECC.tweak_pubkey(pubkey, taptweak,
|
|
25
|
+
const twk_key = ECC.tweak_pubkey(pubkey, taptweak, "ecdsa");
|
|
27
26
|
const parity = parse_pubkey_parity(twk_key);
|
|
28
|
-
const tapkey = ECC.serialize_pubkey(twk_key,
|
|
27
|
+
const tapkey = ECC.serialize_pubkey(twk_key, "bip340");
|
|
29
28
|
const cbit = Buff.num(version + parity);
|
|
30
29
|
const block = [cbit, Buff.bytes(pubkey)];
|
|
31
30
|
if (path.length > 0) {
|
|
32
|
-
|
|
31
|
+
block.push(...path);
|
|
33
32
|
}
|
|
34
33
|
const cblock = Buff.join(block);
|
|
35
34
|
return {
|
|
@@ -39,11 +38,11 @@ export function create_taproot(config) {
|
|
|
39
38
|
taproot: taproot ?? null,
|
|
40
39
|
cblock: cblock.hex,
|
|
41
40
|
tapkey: tapkey.hex,
|
|
42
|
-
taptweak: taptweak.hex
|
|
41
|
+
taptweak: taptweak.hex,
|
|
43
42
|
};
|
|
44
43
|
}
|
|
45
44
|
export function verify_taproot(tapkey, target, cblock) {
|
|
46
|
-
Assert.
|
|
45
|
+
Assert.ok(Buff.bytes(tapkey).length === 32, "tapkey must be 32 bytes");
|
|
47
46
|
const { parity, path, int_key } = parse_cblock(cblock);
|
|
48
47
|
const ext_key = Buff.join([parity, tapkey]);
|
|
49
48
|
let branch = Buff.bytes(target).hex;
|
|
@@ -51,6 +50,6 @@ export function verify_taproot(tapkey, target, cblock) {
|
|
|
51
50
|
branch = encode_tapbranch(branch, leaf).hex;
|
|
52
51
|
}
|
|
53
52
|
const tap_tweak = encode_taptweak(int_key, branch);
|
|
54
|
-
const tweaked_key = ECC.tweak_pubkey(int_key, tap_tweak,
|
|
55
|
-
return
|
|
53
|
+
const tweaked_key = ECC.tweak_pubkey(int_key, tap_tweak, "ecdsa");
|
|
54
|
+
return ext_key.hex === tweaked_key.hex;
|
|
56
55
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Buff } from
|
|
1
|
+
import { Buff } from "@vbyte/buff";
|
|
2
2
|
export declare function encode_tapscript(script: string | Uint8Array, version?: number): Buff;
|
|
3
3
|
export declare function encode_tapleaf(data: string | Uint8Array, version?: number): Buff;
|
|
4
4
|
export declare function encode_tapbranch(leaf_a: string, leaf_b: string): Buff;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { Assert } from
|
|
3
|
-
import {
|
|
4
|
-
import { TAPLEAF_DEFAULT_VERSION } from
|
|
1
|
+
import { Buff } from "@vbyte/buff";
|
|
2
|
+
import { Assert } from "@vbyte/util";
|
|
3
|
+
import { hash340 } from "@vbyte/crypto/hash";
|
|
4
|
+
import { TAPLEAF_DEFAULT_VERSION } from "../../const.js";
|
|
5
|
+
import { prefix_script_size } from "../../lib/script/index.js";
|
|
5
6
|
const DEFAULT_VERSION = TAPLEAF_DEFAULT_VERSION;
|
|
6
7
|
export function encode_tapscript(script, version = DEFAULT_VERSION) {
|
|
7
8
|
const preimg = prefix_script_size(script);
|
|
@@ -9,18 +10,18 @@ export function encode_tapscript(script, version = DEFAULT_VERSION) {
|
|
|
9
10
|
}
|
|
10
11
|
export function encode_tapleaf(data, version = DEFAULT_VERSION) {
|
|
11
12
|
const vbyte = encode_leaf_version(version);
|
|
12
|
-
return hash340(
|
|
13
|
+
return hash340("TapLeaf", vbyte, data);
|
|
13
14
|
}
|
|
14
15
|
export function encode_tapbranch(leaf_a, leaf_b) {
|
|
15
16
|
if (leaf_b < leaf_a) {
|
|
16
17
|
[leaf_a, leaf_b] = [leaf_b, leaf_a];
|
|
17
18
|
}
|
|
18
|
-
return hash340(
|
|
19
|
+
return hash340("TapBranch", leaf_a, leaf_b);
|
|
19
20
|
}
|
|
20
21
|
export function encode_leaf_version(version = 0xc0) {
|
|
21
22
|
return version & 0xfe;
|
|
22
23
|
}
|
|
23
24
|
export function encode_taptweak(pubkey, data = new Uint8Array()) {
|
|
24
|
-
Assert.
|
|
25
|
-
return hash340(
|
|
25
|
+
Assert.ok(Buff.bytes(pubkey).length === 32, "pubkey must be 32 bytes");
|
|
26
|
+
return hash340("TapTweak", pubkey, data);
|
|
26
27
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export * from
|
|
2
|
-
export * from
|
|
3
|
-
export * from
|
|
4
|
-
export * from
|
|
1
|
+
export * from "./cblock.js";
|
|
2
|
+
export * from "./encode.js";
|
|
3
|
+
export * from "./parse.js";
|
|
4
|
+
export * from "./tree.js";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export * from
|
|
2
|
-
export * from
|
|
3
|
-
export * from
|
|
4
|
-
export * from
|
|
1
|
+
export * from "./cblock.js";
|
|
2
|
+
export * from "./encode.js";
|
|
3
|
+
export * from "./parse.js";
|
|
4
|
+
export * from "./tree.js";
|