@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.
Files changed (52) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/LICENSE +21 -121
  3. package/README.md +36 -227
  4. package/dist/error.d.ts +11 -0
  5. package/dist/error.js +20 -0
  6. package/dist/index.d.ts +1 -0
  7. package/dist/index.js +1 -0
  8. package/dist/lib/address/api.js +3 -2
  9. package/dist/lib/address/encode.js +6 -4
  10. package/dist/lib/address/p2pkh.js +4 -4
  11. package/dist/lib/address/p2sh.js +3 -3
  12. package/dist/lib/address/p2tr.js +3 -3
  13. package/dist/lib/address/p2wpkh.js +4 -4
  14. package/dist/lib/address/p2wsh.js +3 -3
  15. package/dist/lib/address/util.js +3 -1
  16. package/dist/lib/meta/locktime.js +3 -2
  17. package/dist/lib/meta/ref.js +4 -3
  18. package/dist/lib/meta/scribe.js +26 -6
  19. package/dist/lib/meta/sequence.js +8 -7
  20. package/dist/lib/script/decode.js +10 -9
  21. package/dist/lib/script/encode.js +4 -3
  22. package/dist/lib/script/words.js +4 -3
  23. package/dist/lib/sighash/segwit.js +9 -6
  24. package/dist/lib/sighash/taproot.js +7 -4
  25. package/dist/lib/sighash/util.js +4 -3
  26. package/dist/lib/signer/sign.js +7 -6
  27. package/dist/lib/signer/verify.js +8 -9
  28. package/dist/lib/taproot/cblock.js +3 -2
  29. package/dist/lib/taproot/encode.d.ts +1 -1
  30. package/dist/lib/taproot/encode.js +4 -3
  31. package/dist/lib/taproot/parse.js +9 -7
  32. package/dist/lib/taproot/tree.js +3 -2
  33. package/dist/lib/tx/create.js +1 -1
  34. package/dist/lib/tx/decode.js +13 -11
  35. package/dist/lib/tx/encode.js +1 -1
  36. package/dist/lib/tx/parse.js +3 -3
  37. package/dist/lib/tx/size.js +2 -4
  38. package/dist/lib/tx/util.js +2 -3
  39. package/dist/lib/tx/validate.js +36 -8
  40. package/dist/lib/witness/util.js +1 -1
  41. package/dist/main.cjs +1127 -1160
  42. package/dist/main.cjs.map +1 -1
  43. package/dist/module.mjs +1125 -1161
  44. package/dist/module.mjs.map +1 -1
  45. package/dist/package.json +13 -11
  46. package/dist/script.js +10 -12
  47. package/dist/script.js.map +1 -1
  48. package/docs/API.md +1145 -0
  49. package/docs/CONVENTIONS.md +316 -0
  50. package/docs/FAQ.md +396 -0
  51. package/docs/GUIDE.md +1102 -0
  52. package/package.json +13 -11
@@ -1,6 +1,6 @@
1
1
  import { Buff } from "@vbyte/buff";
2
- import { Assert } from "@vbyte/micro-lib";
3
- import { hash160 } from "@vbyte/micro-lib/hash";
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.size(bytes, 33, "invalid pubkey size");
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.size(pk_hash, config.size, `invalid payload size: ${pk_hash.length} !== ${config.size}`);
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/micro-lib";
3
- import { sha256 } from "@vbyte/micro-lib/hash";
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.size(script_hash, config.size, `invalid payload size: ${script_hash.length} !== ${config.size}`);
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",
@@ -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 Error("address configuration is invalid");
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/micro-lib";
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 Error("Invalid locktime type");
19
+ throw new ConfigError(`Invalid locktime type: expected 'timelock' or 'heightlock'`);
19
20
  }
20
21
  }
21
22
  export function decode_locktime(locktime) {
@@ -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 Error(`invalid inscription id: ${inscription_id}`);
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 Error(`invalid rune id: ${rune_id}`);
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 Error(`invalid outpoint: ${outpoint}`);
71
+ throw new ValidationError(`invalid outpoint: "${outpoint}". Expected format: <64-char-txid>:<vout> (e.g., "abc123...:0")`);
71
72
  }
72
73
  }
@@ -1,5 +1,6 @@
1
1
  import { Buff, Stream } from "@vbyte/buff";
2
- import { Assert } from "@vbyte/micro-lib";
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 END_IF statement");
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 += stop_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
- let big = Buff.bytes(label).reverse().big;
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 Error(`invalid timelock mode: ${data.mode}`);
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 Error("Decoded timestamp exceeds 32-bit limit");
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 Error("Decoded height exceeds maximum");
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 Error(`invalid sequence value: ${seq}`);
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 Error(`timestamp must be a number`);
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 Error(`timelock value must be an integer between 0 and ${TIMELOCK_VALUE_MAX} (in 512-second increments)`);
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 Error(`Heightlock value must be an integer between 0 and ${TIMELOCK_VALUE_MAX}`);
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 Error(`Malformed script: varint push at position ${count - 1} requires ${word} bytes but stream exhausted`);
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 Error(`Malformed script: PUSHDATA1 at position ${count - 1} missing size byte`);
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 Error(`Malformed script: PUSHDATA1 at position ${count - 1} requires ${word_size} bytes but stream exhausted`);
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 Error(`Malformed script: PUSHDATA2 at position ${count - 1} missing size bytes`);
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 Error(`Malformed script: PUSHDATA2 at position ${count - 1} requires ${word_size} bytes but stream exhausted`);
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 Error(`Malformed script: PUSHDATA4 at position ${count - 1} missing size bytes`);
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 Error(`Malformed script: PUSHDATA4 at position ${count - 1} requires ${word_size} bytes but stream exhausted`);
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 Error(`Invalid OPCODE: ${word}`);
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 Error(`Word type undefined: ${word}`);
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 Error(`script size ${buffer.length} exceeds consensus limit of ${MAX_SCRIPT_SIZE} bytes`);
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 Error(`invalid word type:${typeof word}`);
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 Error(`Invalid word size:${size.toString()}`);
83
+ throw new ValidationError(`invalid script word size: ${size}. Maximum allowed is ${MAX_WORD_SIZE} bytes`);
83
84
  }
84
85
  }
@@ -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 Error(`OPCODE not found:${String(num)}`);
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 Error(`OPCODE not found:${string}`);
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 Error(`Invalid word range: ${word}`);
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/micro-lib";
3
- import { hash160, hash256 } from "@vbyte/micro-lib/hash";
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 Error(`Invalid hash type: ${String(sigflag)}`);
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 Error("Prevout value is empty!");
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 Error("No pubkey / script has been set!");
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 Error("This library does not currently support the use of OP_CODESEPARATOR in segwit scripts.");
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/micro-lib";
3
- import { hash340, sha256 } from "@vbyte/micro-lib/hash";
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 Error(`Invalid hash type: ${String(sigflag)}`);
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 Error(`Extention flag out of range: ${String(extflag)}`);
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) {
@@ -1,6 +1,7 @@
1
1
  import { Buff } from "@vbyte/buff";
2
- import { Assert } from "@vbyte/micro-lib";
3
- import { sha256 } from "@vbyte/micro-lib/hash";
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 Error(`Input index out of bounds: ${String(txindex)}`);
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
  }
@@ -1,37 +1,38 @@
1
1
  import { Buff } from "@vbyte/buff";
2
- import { ECC } from "@vbyte/micro-lib";
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 Error("Secret key must be a string");
11
+ throw new ValidationError("Secret key must be a string", "seckey");
11
12
  }
12
13
  if (!SECKEY_REGEX.test(seckey)) {
13
- throw new Error("Invalid secret key format: expected 32-byte hex string (64 characters)");
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 Error("sigflag must be an integer");
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 Error(`Invalid sigflag: ${sigflag}`);
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 Error("txindex must be a non-negative integer");
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/micro-lib";
3
- import { hash160 } from "@vbyte/micro-lib/hash";
4
- import { get_lock_script_type } from "../../lib/script/lock.js";
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 Error(`Input ${i} verification failed: ${result.error}`);
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, scriptType, options);
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, _scriptType, options) {
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 Error("0x00 is not a valid appended sigflag");
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 Error(`Invalid Schnorr signature length: ${sigBytes.length}`);
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, ECC } from "@vbyte/micro-lib";
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.size(tapkey, 32);
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 type { Buff } from "@vbyte/buff";
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 { Assert } from "@vbyte/micro-lib";
2
- import { hash340 } from "@vbyte/micro-lib/hash";
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.size(pubkey, 32);
25
+ Assert.ok(Buff.bytes(pubkey).length === 32, "pubkey must be 32 bytes");
25
26
  return hash340("TapTweak", pubkey, data);
26
27
  }