@vbyte/btc-dev 2.0.0 → 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 +33 -0
- package/LICENSE +21 -121
- package/README.md +36 -227
- package/dist/error.d.ts +11 -0
- package/dist/error.js +20 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/lib/address/api.js +3 -2
- package/dist/lib/address/encode.js +6 -4
- package/dist/lib/address/p2pkh.js +4 -4
- package/dist/lib/address/p2sh.js +3 -3
- package/dist/lib/address/p2tr.js +3 -3
- package/dist/lib/address/p2wpkh.js +4 -4
- package/dist/lib/address/p2wsh.js +3 -3
- package/dist/lib/address/util.js +3 -1
- package/dist/lib/meta/locktime.js +3 -2
- package/dist/lib/meta/ref.js +4 -3
- package/dist/lib/meta/scribe.js +26 -6
- package/dist/lib/meta/sequence.js +8 -7
- package/dist/lib/script/decode.js +10 -9
- package/dist/lib/script/encode.js +4 -3
- package/dist/lib/script/words.js +4 -3
- package/dist/lib/sighash/segwit.js +9 -6
- package/dist/lib/sighash/taproot.js +7 -4
- package/dist/lib/sighash/util.js +4 -3
- package/dist/lib/signer/sign.js +7 -6
- package/dist/lib/signer/verify.js +8 -9
- package/dist/lib/taproot/cblock.js +3 -2
- package/dist/lib/taproot/encode.d.ts +1 -1
- package/dist/lib/taproot/encode.js +4 -3
- package/dist/lib/taproot/parse.js +9 -7
- package/dist/lib/taproot/tree.js +3 -2
- package/dist/lib/tx/create.js +1 -1
- package/dist/lib/tx/decode.js +13 -11
- package/dist/lib/tx/encode.js +1 -1
- package/dist/lib/tx/parse.js +3 -3
- package/dist/lib/tx/size.js +2 -4
- package/dist/lib/tx/util.js +2 -3
- package/dist/lib/tx/validate.js +36 -8
- package/dist/lib/witness/util.js +1 -1
- package/dist/main.cjs +1127 -1160
- package/dist/main.cjs.map +1 -1
- package/dist/module.mjs +1125 -1161
- package/dist/module.mjs.map +1 -1
- package/dist/package.json +13 -11
- package/dist/script.js +10 -12
- package/dist/script.js.map +1 -1
- 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 +13 -11
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Buff } from "@vbyte/buff";
|
|
2
|
-
import { Assert } from "@vbyte/
|
|
3
|
-
import { hash160 } from "@vbyte/
|
|
2
|
+
import { Assert } from "@vbyte/util";
|
|
3
|
+
import { hash160 } from "@vbyte/crypto/hash";
|
|
4
4
|
import { LOCK_SCRIPT_TYPE } from "../../const.js";
|
|
5
5
|
import { is_p2wpkh_script } from "../../lib/script/lock.js";
|
|
6
6
|
import { encode_address } from "./encode.js";
|
|
@@ -21,7 +21,7 @@ function create_p2wpkh_address(pubkey, network = "main") {
|
|
|
21
21
|
}
|
|
22
22
|
function create_p2wpkh_script(pubkey) {
|
|
23
23
|
const bytes = Buff.bytes(pubkey);
|
|
24
|
-
Assert.
|
|
24
|
+
Assert.ok(bytes.length === 33, "invalid pubkey size");
|
|
25
25
|
const hash = hash160(bytes);
|
|
26
26
|
return encode_p2wpkh_script(hash);
|
|
27
27
|
}
|
|
@@ -32,7 +32,7 @@ function encode_p2wpkh_address(script_pk, network = "main") {
|
|
|
32
32
|
const pk_hash = decode_p2wpkh_script(script_pk);
|
|
33
33
|
const config = get_address_config(network, ADDRESS_TYPE);
|
|
34
34
|
Assert.exists(config, `unrecognized address config: ${ADDRESS_TYPE} on ${network}`);
|
|
35
|
-
Assert.
|
|
35
|
+
Assert.ok(pk_hash.length === config.size, `invalid payload size: ${pk_hash.length} !== ${config.size}`);
|
|
36
36
|
return encode_address({
|
|
37
37
|
data: pk_hash,
|
|
38
38
|
format: "bech32",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Buff } from "@vbyte/buff";
|
|
2
|
-
import { Assert } from "@vbyte/
|
|
3
|
-
import { sha256 } from "@vbyte/
|
|
2
|
+
import { Assert } from "@vbyte/util";
|
|
3
|
+
import { sha256 } from "@vbyte/crypto/hash";
|
|
4
4
|
import { LOCK_SCRIPT_TYPE } from "../../const.js";
|
|
5
5
|
import { is_p2wsh_script } from "../../lib/script/lock.js";
|
|
6
6
|
import { encode_address } from "./encode.js";
|
|
@@ -31,7 +31,7 @@ function encode_p2wsh_address(script_pk, network = "main") {
|
|
|
31
31
|
const script_hash = decode_p2wsh_script(script_pk);
|
|
32
32
|
const config = get_address_config(network, ADDRESS_TYPE);
|
|
33
33
|
Assert.exists(config, `unrecognized address config: ${ADDRESS_TYPE} on ${network}`);
|
|
34
|
-
Assert.
|
|
34
|
+
Assert.ok(script_hash.length === config.size, `invalid payload size: ${script_hash.length} !== ${config.size}`);
|
|
35
35
|
return encode_address({
|
|
36
36
|
data: script_hash,
|
|
37
37
|
format: "bech32",
|
package/dist/lib/address/util.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Buff } from "@vbyte/buff";
|
|
2
|
+
import { ConfigError } from "../../error.js";
|
|
2
3
|
import { decode_address } from "./encode.js";
|
|
3
4
|
import { get_address_script } from "./script.js";
|
|
4
5
|
const CONFIG_TABLE = [
|
|
@@ -49,5 +50,6 @@ export function get_address_info(address) {
|
|
|
49
50
|
const script = get_address_script(data, type);
|
|
50
51
|
return { data, script, type, prefix, network, size, format, version };
|
|
51
52
|
}
|
|
52
|
-
throw new
|
|
53
|
+
throw new ConfigError(`unrecognized address configuration: format=${dec.format}, size=${dec.data.length}, version=${dec.version}. ` +
|
|
54
|
+
`Supported formats: base58 (p2pkh, p2sh), bech32 (p2wpkh, p2wsh), bech32m (p2tr)`);
|
|
53
55
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { Assert } from "@vbyte/
|
|
1
|
+
import { Assert } from "@vbyte/util";
|
|
2
|
+
import { ConfigError } from "../../error.js";
|
|
2
3
|
const LOCKTIME_THRESHOLD = 500000000;
|
|
3
4
|
export var LocktimeField;
|
|
4
5
|
(function (LocktimeField) {
|
|
@@ -15,7 +16,7 @@ export function encode_locktime(locktime) {
|
|
|
15
16
|
Assert.ok(locktime.height < LOCKTIME_THRESHOLD, "invalid block height");
|
|
16
17
|
return locktime.height;
|
|
17
18
|
default:
|
|
18
|
-
throw new
|
|
19
|
+
throw new ConfigError(`Invalid locktime type: expected 'timelock' or 'heightlock'`);
|
|
19
20
|
}
|
|
20
21
|
}
|
|
21
22
|
export function decode_locktime(locktime) {
|
package/dist/lib/meta/ref.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ValidationError } from "../../error.js";
|
|
1
2
|
export var RefPointer;
|
|
2
3
|
(function (RefPointer) {
|
|
3
4
|
RefPointer.outpoint = {
|
|
@@ -32,7 +33,7 @@ function verify_inscription_id(inscription_id) {
|
|
|
32
33
|
}
|
|
33
34
|
function assert_inscription_id(inscription_id) {
|
|
34
35
|
if (!verify_inscription_id(inscription_id)) {
|
|
35
|
-
throw new
|
|
36
|
+
throw new ValidationError(`invalid inscription id: "${inscription_id}". Expected format: <64-char-txid>i<index> (e.g., "abc123...i0")`);
|
|
36
37
|
}
|
|
37
38
|
}
|
|
38
39
|
function encode_rune_id(block_height, block_index) {
|
|
@@ -51,7 +52,7 @@ function verify_rune_id(rune_id) {
|
|
|
51
52
|
}
|
|
52
53
|
function assert_rune_id(rune_id) {
|
|
53
54
|
if (!verify_rune_id(rune_id)) {
|
|
54
|
-
throw new
|
|
55
|
+
throw new ValidationError(`invalid rune id: "${rune_id}". Expected format: <block_height>:<block_index> (e.g., "840000:1")`);
|
|
55
56
|
}
|
|
56
57
|
}
|
|
57
58
|
function encode_outpoint(txid, vout) {
|
|
@@ -67,6 +68,6 @@ function verify_outpoint(outpoint) {
|
|
|
67
68
|
}
|
|
68
69
|
function assert_outpoint(outpoint) {
|
|
69
70
|
if (!verify_outpoint(outpoint)) {
|
|
70
|
-
throw new
|
|
71
|
+
throw new ValidationError(`invalid outpoint: "${outpoint}". Expected format: <64-char-txid>:<vout> (e.g., "abc123...:0")`);
|
|
71
72
|
}
|
|
72
73
|
}
|
package/dist/lib/meta/scribe.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Buff, Stream } from "@vbyte/buff";
|
|
2
|
-
import { Assert } from "@vbyte/
|
|
2
|
+
import { Assert } from "@vbyte/util";
|
|
3
|
+
import { ValidationError } from "../../error.js";
|
|
3
4
|
import { decode_script } from "../../lib/script/decode.js";
|
|
4
5
|
import { encode_script } from "../../lib/script/encode.js";
|
|
5
6
|
const _0n = BigInt(0);
|
|
@@ -59,13 +60,14 @@ function parse_envelopes(script) {
|
|
|
59
60
|
Assert.ok(start_idx !== -1, "inscription envelope not found");
|
|
60
61
|
const envelopes = [];
|
|
61
62
|
for (let idx = start_idx; idx < words.length; idx++) {
|
|
63
|
+
Assert.ok(idx + 2 < words.length, "incomplete envelope: missing OP_IF or magic bytes");
|
|
62
64
|
Assert.ok(words[idx + 1] === "OP_IF", "OP_IF missing from envelope");
|
|
63
65
|
Assert.ok(words[idx + 2] === "6f7264", "magic bytes missing from envelope");
|
|
64
|
-
const stop_idx = words.indexOf("OP_ENDIF");
|
|
65
|
-
Assert.ok(stop_idx !== -1, "inscription envelope missing
|
|
66
|
+
const stop_idx = words.indexOf("OP_ENDIF", idx);
|
|
67
|
+
Assert.ok(stop_idx !== -1, "inscription envelope missing OP_ENDIF statement");
|
|
66
68
|
const env = words.slice(idx + 3, stop_idx);
|
|
67
69
|
envelopes.push(env);
|
|
68
|
-
idx
|
|
70
|
+
idx = stop_idx;
|
|
69
71
|
}
|
|
70
72
|
return envelopes;
|
|
71
73
|
}
|
|
@@ -111,6 +113,19 @@ function parse_record(envelope) {
|
|
|
111
113
|
function decode_bytes(bytes) {
|
|
112
114
|
return Buff.bytes(bytes).hex;
|
|
113
115
|
}
|
|
116
|
+
function normalize_script_word(word) {
|
|
117
|
+
if (typeof word === "string" && word.startsWith("OP_")) {
|
|
118
|
+
if (word === "OP_0")
|
|
119
|
+
return Buff.num(0).hex;
|
|
120
|
+
const match = word.match(/^OP_(\d+)$/);
|
|
121
|
+
if (match) {
|
|
122
|
+
const num = parseInt(match[1], 10);
|
|
123
|
+
if (num >= 1 && num <= 16)
|
|
124
|
+
return Buff.num(num).hex;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return word;
|
|
128
|
+
}
|
|
114
129
|
function encode_id(identifier) {
|
|
115
130
|
Assert.ok(identifier.includes("i"), "identifier must include an index");
|
|
116
131
|
const parts = identifier.split("i");
|
|
@@ -121,6 +136,9 @@ function encode_id(identifier) {
|
|
|
121
136
|
}
|
|
122
137
|
function decode_id(identifier) {
|
|
123
138
|
const bytes = Buff.bytes(identifier);
|
|
139
|
+
if (bytes.length === 32) {
|
|
140
|
+
return `${bytes.reverse().hex}i0`;
|
|
141
|
+
}
|
|
124
142
|
const idx = bytes.at(-1) ?? 0;
|
|
125
143
|
const txid = bytes.slice(0, -1).reverse().hex;
|
|
126
144
|
return `${txid}i${String(idx)}`;
|
|
@@ -129,7 +147,7 @@ function encode_pointer(pointer) {
|
|
|
129
147
|
return Buff.num(pointer).reverse().hex;
|
|
130
148
|
}
|
|
131
149
|
function decode_pointer(bytes) {
|
|
132
|
-
return Buff.bytes(bytes).reverse().num;
|
|
150
|
+
return Buff.bytes(normalize_script_word(bytes)).reverse().num;
|
|
133
151
|
}
|
|
134
152
|
function encode_label(label) {
|
|
135
153
|
return Buff.str(label).hex;
|
|
@@ -165,13 +183,15 @@ function encode_rune_label(label) {
|
|
|
165
183
|
big = big * _26n + BigInt(char.charCodeAt(0) - ("A".charCodeAt(0) - 1));
|
|
166
184
|
}
|
|
167
185
|
else {
|
|
186
|
+
throw new ValidationError(`invalid character in rune label: '${char}' (only A-Z allowed)`, "label");
|
|
168
187
|
}
|
|
169
188
|
}
|
|
170
189
|
big = big - _1n;
|
|
171
190
|
return Buff.big(big).reverse().hex;
|
|
172
191
|
}
|
|
173
192
|
function decode_rune_label(label) {
|
|
174
|
-
|
|
193
|
+
const normalized = normalize_script_word(label);
|
|
194
|
+
let big = Buff.bytes(normalized).reverse().big;
|
|
175
195
|
big = big + _1n;
|
|
176
196
|
let result = "";
|
|
177
197
|
while (big > _0n) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ValidationError } from "../../error.js";
|
|
1
2
|
const TIMELOCK_DISABLE = 0x80000000;
|
|
2
3
|
const TIMELOCK_TYPE = 0x00400000;
|
|
3
4
|
const TIMELOCK_VALUE_MASK = 0x0000ffff;
|
|
@@ -17,7 +18,7 @@ export function encode_sequence(data) {
|
|
|
17
18
|
const stamp = parse_stamp(data.stamp);
|
|
18
19
|
return (TIMELOCK_TYPE | (stamp & TIMELOCK_VALUE_MASK)) >>> 0;
|
|
19
20
|
}
|
|
20
|
-
throw new
|
|
21
|
+
throw new ValidationError(`invalid timelock mode: "${data.mode}". Valid modes are "height" or "stamp"`);
|
|
21
22
|
}
|
|
22
23
|
export function decode_sequence(sequence) {
|
|
23
24
|
const seq = parse_sequence(sequence);
|
|
@@ -27,13 +28,13 @@ export function decode_sequence(sequence) {
|
|
|
27
28
|
if (seq & TIMELOCK_TYPE) {
|
|
28
29
|
const stamp = value * TIMELOCK_GRANULARITY;
|
|
29
30
|
if (stamp > 0xffffffff) {
|
|
30
|
-
throw new
|
|
31
|
+
throw new ValidationError(`decoded timestamp ${stamp} exceeds 32-bit limit (max: ${0xffffffff})`);
|
|
31
32
|
}
|
|
32
33
|
return { mode: "stamp", stamp };
|
|
33
34
|
}
|
|
34
35
|
else {
|
|
35
36
|
if (value > TIMELOCK_VALUE_MAX) {
|
|
36
|
-
throw new
|
|
37
|
+
throw new ValidationError(`decoded height ${value} exceeds maximum (${TIMELOCK_VALUE_MAX})`);
|
|
37
38
|
}
|
|
38
39
|
return { mode: "height", height: value };
|
|
39
40
|
}
|
|
@@ -41,17 +42,17 @@ export function decode_sequence(sequence) {
|
|
|
41
42
|
function parse_sequence(sequence) {
|
|
42
43
|
const seq = typeof sequence === "string" ? parseInt(sequence, 16) : sequence;
|
|
43
44
|
if (!Number.isInteger(seq) || seq < 0 || seq > 0xffffffff) {
|
|
44
|
-
throw new
|
|
45
|
+
throw new ValidationError(`invalid sequence value: ${seq}. Must be an integer between 0 and 0xffffffff`);
|
|
45
46
|
}
|
|
46
47
|
return seq;
|
|
47
48
|
}
|
|
48
49
|
function parse_stamp(stamp) {
|
|
49
50
|
if (stamp === undefined || !Number.isInteger(stamp)) {
|
|
50
|
-
throw new
|
|
51
|
+
throw new ValidationError(`timestamp must be an integer, got: ${stamp}`);
|
|
51
52
|
}
|
|
52
53
|
const ts = Math.floor(stamp / TIMELOCK_GRANULARITY);
|
|
53
54
|
if (!Number.isInteger(ts) || ts < 0 || ts > TIMELOCK_VALUE_MAX) {
|
|
54
|
-
throw new
|
|
55
|
+
throw new ValidationError(`timelock value must be an integer between 0 and ${TIMELOCK_VALUE_MAX} (in 512-second increments)`);
|
|
55
56
|
}
|
|
56
57
|
return ts;
|
|
57
58
|
}
|
|
@@ -60,7 +61,7 @@ function parse_height(height) {
|
|
|
60
61
|
!Number.isInteger(height) ||
|
|
61
62
|
height < 0 ||
|
|
62
63
|
height > TIMELOCK_VALUE_MAX) {
|
|
63
|
-
throw new
|
|
64
|
+
throw new ValidationError(`heightlock value must be an integer between 0 and ${TIMELOCK_VALUE_MAX}, got: ${height}`);
|
|
64
65
|
}
|
|
65
66
|
return height;
|
|
66
67
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Buff, Stream } from "@vbyte/buff";
|
|
2
|
+
import { DecodingError } from "../../error.js";
|
|
2
3
|
import { get_op_code, get_op_type, is_valid_op } from "./words.js";
|
|
3
4
|
export function parse_script(script) {
|
|
4
5
|
const bytes = Buff.bytes(script);
|
|
@@ -25,7 +26,7 @@ export function decode_script(script) {
|
|
|
25
26
|
stack.push(stream.read(word).hex);
|
|
26
27
|
}
|
|
27
28
|
catch {
|
|
28
|
-
throw new
|
|
29
|
+
throw new DecodingError(`Malformed script: varint push at position ${count - 1} requires ${word} bytes but stream exhausted`, count - 1);
|
|
29
30
|
}
|
|
30
31
|
count += word;
|
|
31
32
|
break;
|
|
@@ -34,13 +35,13 @@ export function decode_script(script) {
|
|
|
34
35
|
word_size = stream.read(1).reverse().num;
|
|
35
36
|
}
|
|
36
37
|
catch {
|
|
37
|
-
throw new
|
|
38
|
+
throw new DecodingError(`Malformed script: PUSHDATA1 at position ${count - 1} missing size byte`, count - 1);
|
|
38
39
|
}
|
|
39
40
|
try {
|
|
40
41
|
stack.push(stream.read(word_size).hex);
|
|
41
42
|
}
|
|
42
43
|
catch {
|
|
43
|
-
throw new
|
|
44
|
+
throw new DecodingError(`Malformed script: PUSHDATA1 at position ${count - 1} requires ${word_size} bytes but stream exhausted`, count - 1);
|
|
44
45
|
}
|
|
45
46
|
count += word_size + 1;
|
|
46
47
|
break;
|
|
@@ -49,13 +50,13 @@ export function decode_script(script) {
|
|
|
49
50
|
word_size = stream.read(2).reverse().num;
|
|
50
51
|
}
|
|
51
52
|
catch {
|
|
52
|
-
throw new
|
|
53
|
+
throw new DecodingError(`Malformed script: PUSHDATA2 at position ${count - 1} missing size bytes`, count - 1);
|
|
53
54
|
}
|
|
54
55
|
try {
|
|
55
56
|
stack.push(stream.read(word_size).hex);
|
|
56
57
|
}
|
|
57
58
|
catch {
|
|
58
|
-
throw new
|
|
59
|
+
throw new DecodingError(`Malformed script: PUSHDATA2 at position ${count - 1} requires ${word_size} bytes but stream exhausted`, count - 1);
|
|
59
60
|
}
|
|
60
61
|
count += word_size + 2;
|
|
61
62
|
break;
|
|
@@ -64,24 +65,24 @@ export function decode_script(script) {
|
|
|
64
65
|
word_size = stream.read(4).reverse().num;
|
|
65
66
|
}
|
|
66
67
|
catch {
|
|
67
|
-
throw new
|
|
68
|
+
throw new DecodingError(`Malformed script: PUSHDATA4 at position ${count - 1} missing size bytes`, count - 1);
|
|
68
69
|
}
|
|
69
70
|
try {
|
|
70
71
|
stack.push(stream.read(word_size).hex);
|
|
71
72
|
}
|
|
72
73
|
catch {
|
|
73
|
-
throw new
|
|
74
|
+
throw new DecodingError(`Malformed script: PUSHDATA4 at position ${count - 1} requires ${word_size} bytes but stream exhausted`, count - 1);
|
|
74
75
|
}
|
|
75
76
|
count += word_size + 4;
|
|
76
77
|
break;
|
|
77
78
|
case "opcode":
|
|
78
79
|
if (!is_valid_op(word)) {
|
|
79
|
-
throw new
|
|
80
|
+
throw new DecodingError(`Invalid OPCODE: ${word}`, count - 1);
|
|
80
81
|
}
|
|
81
82
|
stack.push(get_op_code(word));
|
|
82
83
|
break;
|
|
83
84
|
default:
|
|
84
|
-
throw new
|
|
85
|
+
throw new DecodingError(`Word type undefined: ${word}`, count - 1);
|
|
85
86
|
}
|
|
86
87
|
}
|
|
87
88
|
return stack;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Buff, Stream } from "@vbyte/buff";
|
|
2
2
|
import { MAX_SCRIPT_SIZE, OP_1_OFFSET } from "../../const.js";
|
|
3
|
+
import { ValidationError } from "../../error.js";
|
|
3
4
|
import { get_asm_code } from "./words.js";
|
|
4
5
|
const MAX_WORD_SIZE = 520;
|
|
5
6
|
export function encode_script(words, varint = false) {
|
|
@@ -11,7 +12,7 @@ export function encode_script(words, varint = false) {
|
|
|
11
12
|
}
|
|
12
13
|
const buffer = Buff.join(bytes);
|
|
13
14
|
if (buffer.length > MAX_SCRIPT_SIZE) {
|
|
14
|
-
throw new
|
|
15
|
+
throw new ValidationError(`script size ${buffer.length} exceeds consensus limit of ${MAX_SCRIPT_SIZE} bytes`);
|
|
15
16
|
}
|
|
16
17
|
return varint
|
|
17
18
|
? buffer.prepend(Buff.create_varint(buffer.length, "le"))
|
|
@@ -38,7 +39,7 @@ export function encode_script_word(word) {
|
|
|
38
39
|
buff = new Buff(word);
|
|
39
40
|
}
|
|
40
41
|
else {
|
|
41
|
-
throw new
|
|
42
|
+
throw new ValidationError(`invalid script word type: ${typeof word}. Expected string, number, or Uint8Array`);
|
|
42
43
|
}
|
|
43
44
|
if (buff.length === 1 && buff[0] <= 16) {
|
|
44
45
|
if (buff[0] !== 0)
|
|
@@ -79,6 +80,6 @@ export function get_size_varint(size) {
|
|
|
79
80
|
case size >= 0x100 && size <= MAX_WORD_SIZE:
|
|
80
81
|
return Buff.join([OP_PUSHDATA2, Buff.num(size, 2, "le")]);
|
|
81
82
|
default:
|
|
82
|
-
throw new
|
|
83
|
+
throw new ValidationError(`invalid script word size: ${size}. Maximum allowed is ${MAX_WORD_SIZE} bytes`);
|
|
83
84
|
}
|
|
84
85
|
}
|
package/dist/lib/script/words.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ValidationError } from "../../error.js";
|
|
1
2
|
export const OPCODE_MAP = {
|
|
2
3
|
OP_0: 0x00,
|
|
3
4
|
OP_PUSHDATA1: 0x4c,
|
|
@@ -118,14 +119,14 @@ export function get_op_code(num) {
|
|
|
118
119
|
if (v === num)
|
|
119
120
|
return k;
|
|
120
121
|
}
|
|
121
|
-
throw new
|
|
122
|
+
throw new ValidationError(`opcode not found for value: ${num} (0x${num.toString(16)}). Valid range is 0x00-0xba`);
|
|
122
123
|
}
|
|
123
124
|
export function get_asm_code(string) {
|
|
124
125
|
for (const [k, v] of Object.entries(OPCODE_MAP)) {
|
|
125
126
|
if (k === string)
|
|
126
127
|
return Number(v);
|
|
127
128
|
}
|
|
128
|
-
throw new
|
|
129
|
+
throw new ValidationError(`opcode not found: "${string}". Valid opcodes start with "OP_" (e.g., OP_DUP, OP_CHECKSIG)`);
|
|
129
130
|
}
|
|
130
131
|
export function get_op_type(word) {
|
|
131
132
|
switch (true) {
|
|
@@ -142,7 +143,7 @@ export function get_op_type(word) {
|
|
|
142
143
|
case word <= 254:
|
|
143
144
|
return "opcode";
|
|
144
145
|
default:
|
|
145
|
-
throw new
|
|
146
|
+
throw new ValidationError(`invalid word value: ${word}. Expected 0-254`);
|
|
146
147
|
}
|
|
147
148
|
}
|
|
148
149
|
export function is_valid_op(word) {
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { Buff } from "@vbyte/buff";
|
|
2
|
-
import { Assert } from "@vbyte/
|
|
3
|
-
import { hash160, hash256 } from "@vbyte/
|
|
2
|
+
import { Assert } from "@vbyte/util";
|
|
3
|
+
import { hash160, hash256 } from "@vbyte/crypto/hash";
|
|
4
4
|
import * as CONST from "../../const.js";
|
|
5
|
+
import { ValidationError } from "../../error.js";
|
|
5
6
|
import { decode_script, prefix_script_size } from "../../lib/script/index.js";
|
|
6
7
|
import { encode_tx_locktime, encode_tx_version, encode_txin_sequence, encode_txin_txid, encode_txin_vout, encode_vout_value, parse_tx, } from "../../lib/tx/index.js";
|
|
7
8
|
import { parse_txinput } from "./util.js";
|
|
@@ -11,14 +12,16 @@ export function hash_segwit_tx(txdata, options = {}) {
|
|
|
11
12
|
const is_anypay = (sigflag & 0x80) === 0x80;
|
|
12
13
|
const flag = sigflag % 0x80;
|
|
13
14
|
if (!CONST.SIGHASH_SEGWIT.includes(flag)) {
|
|
14
|
-
throw new
|
|
15
|
+
throw new ValidationError(`invalid sighash type: 0x${sigflag.toString(16)}. ` +
|
|
16
|
+
`Valid values: SIGHASH_ALL (0x01), SIGHASH_NONE (0x02), SIGHASH_SINGLE (0x03), ` +
|
|
17
|
+
`or combined with ANYONECANPAY (0x81, 0x82, 0x83)`);
|
|
15
18
|
}
|
|
16
19
|
const { version, vin, vout, locktime } = tx;
|
|
17
20
|
const txinput = parse_txinput(tx, options);
|
|
18
21
|
const { txid, vout: prevIdx, prevout, sequence } = txinput;
|
|
19
22
|
const { value } = prevout ?? {};
|
|
20
23
|
if (value === undefined) {
|
|
21
|
-
throw new
|
|
24
|
+
throw new ValidationError("Prevout value is required for segwit sighash calculation", "prevout.value");
|
|
22
25
|
}
|
|
23
26
|
let { pubkey, script } = options;
|
|
24
27
|
if (script === undefined && pubkey !== undefined) {
|
|
@@ -26,10 +29,10 @@ export function hash_segwit_tx(txdata, options = {}) {
|
|
|
26
29
|
script = `76a914${String(pkhash)}88ac`;
|
|
27
30
|
}
|
|
28
31
|
if (script === undefined) {
|
|
29
|
-
throw new
|
|
32
|
+
throw new ValidationError("Either pubkey or script must be provided for segwit sighash", "pubkey/script");
|
|
30
33
|
}
|
|
31
34
|
if (decode_script(script).includes("OP_CODESEPARATOR")) {
|
|
32
|
-
throw new
|
|
35
|
+
throw new ValidationError("OP_CODESEPARATOR is not supported in segwit scripts", "script");
|
|
33
36
|
}
|
|
34
37
|
const sighash = [
|
|
35
38
|
encode_tx_version(version),
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { Buff } from "@vbyte/buff";
|
|
2
|
-
import { Assert } from "@vbyte/
|
|
3
|
-
import { hash340, sha256 } from "@vbyte/
|
|
2
|
+
import { Assert } from "@vbyte/util";
|
|
3
|
+
import { hash340, sha256 } from "@vbyte/crypto/hash";
|
|
4
4
|
import * as CONST from "../../const.js";
|
|
5
|
+
import { ValidationError } from "../../error.js";
|
|
5
6
|
import { encode_tapscript } from "../../lib/taproot/encode.js";
|
|
6
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";
|
|
7
8
|
import { parse_tx } from "../../lib/tx/parse.js";
|
|
@@ -17,10 +18,12 @@ export function get_taproot_tx_preimage(template, config = {}) {
|
|
|
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) {
|
package/dist/lib/sighash/util.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Buff } from "@vbyte/buff";
|
|
2
|
-
import { Assert } from "@vbyte/
|
|
3
|
-
import { sha256 } from "@vbyte/
|
|
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
6
|
Assert.exists(vin.prevout, `Prevout data missing for input: ${String(vin.txid)}`);
|
|
6
7
|
return vin.prevout;
|
|
@@ -9,7 +10,7 @@ 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
|
}
|
package/dist/lib/signer/sign.js
CHANGED
|
@@ -1,37 +1,38 @@
|
|
|
1
1
|
import { Buff } from "@vbyte/buff";
|
|
2
|
-
import { ECC } from "@vbyte/
|
|
2
|
+
import { ECC } from "@vbyte/crypto";
|
|
3
3
|
import { SIGHASH_DEFAULT, SIGHASH_SEGWIT, SIGHASH_TAPROOT } from "../../const.js";
|
|
4
|
+
import { ConfigError, ValidationError } from "../../error.js";
|
|
4
5
|
import { hash_segwit_tx } from "../../lib/sighash/segwit.js";
|
|
5
6
|
import { hash_taproot_tx } from "../../lib/sighash/taproot.js";
|
|
6
7
|
import { parse_tx } from "../../lib/tx/parse.js";
|
|
7
8
|
const SECKEY_REGEX = /^[0-9a-fA-F]{64}$/;
|
|
8
9
|
function validate_seckey(seckey) {
|
|
9
10
|
if (typeof seckey !== "string") {
|
|
10
|
-
throw new
|
|
11
|
+
throw new ValidationError("Secret key must be a string", "seckey");
|
|
11
12
|
}
|
|
12
13
|
if (!SECKEY_REGEX.test(seckey)) {
|
|
13
|
-
throw new
|
|
14
|
+
throw new ValidationError("Invalid secret key format: expected 32-byte hex string (64 characters)", "seckey");
|
|
14
15
|
}
|
|
15
16
|
}
|
|
16
17
|
function validate_sighash_options(options, validFlags) {
|
|
17
18
|
const { sigflag, txindex } = options;
|
|
18
19
|
if (sigflag !== undefined) {
|
|
19
20
|
if (typeof sigflag !== "number" || !Number.isInteger(sigflag)) {
|
|
20
|
-
throw new
|
|
21
|
+
throw new ConfigError("sigflag must be an integer");
|
|
21
22
|
}
|
|
22
23
|
const normalizedFlag = sigflag & 0x7f;
|
|
23
24
|
const isAnypay = (sigflag & 0x80) === 0x80;
|
|
24
25
|
const baseFlag = isAnypay ? normalizedFlag | 0x80 : normalizedFlag;
|
|
25
26
|
if (!validFlags.includes(baseFlag) &&
|
|
26
27
|
!validFlags.includes(normalizedFlag)) {
|
|
27
|
-
throw new
|
|
28
|
+
throw new ConfigError(`Invalid sigflag: ${sigflag}`);
|
|
28
29
|
}
|
|
29
30
|
}
|
|
30
31
|
if (txindex !== undefined) {
|
|
31
32
|
if (typeof txindex !== "number" ||
|
|
32
33
|
!Number.isInteger(txindex) ||
|
|
33
34
|
txindex < 0) {
|
|
34
|
-
throw new
|
|
35
|
+
throw new ValidationError("txindex must be a non-negative integer", "txindex");
|
|
35
36
|
}
|
|
36
37
|
}
|
|
37
38
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Buff } from "@vbyte/buff";
|
|
2
|
-
import { ECC } from "@vbyte/
|
|
3
|
-
import { hash160 } from "@vbyte/
|
|
4
|
-
import {
|
|
2
|
+
import { ECC } from "@vbyte/crypto";
|
|
3
|
+
import { hash160 } from "@vbyte/crypto/hash";
|
|
4
|
+
import { ValidationError } from "../../error.js";
|
|
5
5
|
import { hash_segwit_tx } from "../../lib/sighash/segwit.js";
|
|
6
6
|
import { hash_taproot_tx } from "../../lib/sighash/taproot.js";
|
|
7
7
|
import { verify_taproot } from "../../lib/taproot/cblock.js";
|
|
@@ -24,7 +24,7 @@ export function verify_tx(txdata, options = {}) {
|
|
|
24
24
|
if (!result.valid) {
|
|
25
25
|
allValid = false;
|
|
26
26
|
if (throws) {
|
|
27
|
-
throw new
|
|
27
|
+
throw new ValidationError(`Input ${i} verification failed: ${result.error}`, `vin[${i}]`);
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
30
|
}
|
|
@@ -49,12 +49,11 @@ function verify_input(tx, vin, index, options) {
|
|
|
49
49
|
if (prevout === null || prevout === undefined) {
|
|
50
50
|
return { index, valid: false, type, error: "Missing prevout data" };
|
|
51
51
|
}
|
|
52
|
-
const scriptType = get_lock_script_type(prevout.script_pk);
|
|
53
52
|
if (version === 0) {
|
|
54
53
|
return verify_segwit_input(tx, vin, index, witnessData, options);
|
|
55
54
|
}
|
|
56
55
|
else if (version === 1) {
|
|
57
|
-
return verify_taproot_input(tx, vin, index, witnessData,
|
|
56
|
+
return verify_taproot_input(tx, vin, index, witnessData, options);
|
|
58
57
|
}
|
|
59
58
|
return {
|
|
60
59
|
index,
|
|
@@ -137,7 +136,7 @@ function verify_segwit_input(tx, vin, index, witnessData, options) {
|
|
|
137
136
|
error: isValid ? undefined : "Invalid ECDSA signature",
|
|
138
137
|
};
|
|
139
138
|
}
|
|
140
|
-
function verify_taproot_input(tx, vin, index, witnessData,
|
|
139
|
+
function verify_taproot_input(tx, vin, index, witnessData, options) {
|
|
141
140
|
const { type, params, script, cblock } = witnessData;
|
|
142
141
|
if (vin.prevout == null) {
|
|
143
142
|
return {
|
|
@@ -225,10 +224,10 @@ function parse_schnorr_signature(sigHex) {
|
|
|
225
224
|
else if (sigBytes.length === 65) {
|
|
226
225
|
const sigflag = sigBytes.at(-1) ?? 0x00;
|
|
227
226
|
if (sigflag === 0x00) {
|
|
228
|
-
throw new
|
|
227
|
+
throw new ValidationError("0x00 is not a valid appended sigflag (use 64-byte signature for SIGHASH_DEFAULT)", "sigflag");
|
|
229
228
|
}
|
|
230
229
|
const signature = sigBytes.slice(0, 64).hex;
|
|
231
230
|
return { signature, sigflag };
|
|
232
231
|
}
|
|
233
|
-
throw new
|
|
232
|
+
throw new ValidationError(`Invalid Schnorr signature length: ${sigBytes.length} (expected 64 or 65 bytes)`, "signature");
|
|
234
233
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Buff } from "@vbyte/buff";
|
|
2
|
-
import { Assert
|
|
2
|
+
import { Assert } from "@vbyte/util";
|
|
3
|
+
import { ECC } from "@vbyte/crypto";
|
|
3
4
|
import { TAPLEAF_DEFAULT_VERSION } from "../../const.js";
|
|
4
5
|
import * as Schema from "../../schema/index.js";
|
|
5
6
|
import { encode_tapbranch, encode_taptweak } from "./encode.js";
|
|
@@ -41,7 +42,7 @@ export function create_taproot(config) {
|
|
|
41
42
|
};
|
|
42
43
|
}
|
|
43
44
|
export function verify_taproot(tapkey, target, cblock) {
|
|
44
|
-
Assert.
|
|
45
|
+
Assert.ok(Buff.bytes(tapkey).length === 32, "tapkey must be 32 bytes");
|
|
45
46
|
const { parity, path, int_key } = parse_cblock(cblock);
|
|
46
47
|
const ext_key = Buff.join([parity, tapkey]);
|
|
47
48
|
let branch = Buff.bytes(target).hex;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
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,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { Buff } from "@vbyte/buff";
|
|
2
|
+
import { Assert } from "@vbyte/util";
|
|
3
|
+
import { hash340 } from "@vbyte/crypto/hash";
|
|
3
4
|
import { TAPLEAF_DEFAULT_VERSION } from "../../const.js";
|
|
4
5
|
import { prefix_script_size } from "../../lib/script/index.js";
|
|
5
6
|
const DEFAULT_VERSION = TAPLEAF_DEFAULT_VERSION;
|
|
@@ -21,6 +22,6 @@ 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
|
+
Assert.ok(Buff.bytes(pubkey).length === 32, "pubkey must be 32 bytes");
|
|
25
26
|
return hash340("TapTweak", pubkey, data);
|
|
26
27
|
}
|