@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,5 +1,7 @@
1
1
  import { Buff, Stream } from "@vbyte/buff";
2
- import { Assert, ECC } from "@vbyte/micro-lib";
2
+ import { Assert } from "@vbyte/util";
3
+ import { ECC } from "@vbyte/crypto";
4
+ import { DecodingError, ValidationError } from "../../error.js";
3
5
  import { parse_witness } from "../../lib/witness/parse.js";
4
6
  import { encode_tapbranch, encode_tapscript, encode_taptweak, } from "./encode.js";
5
7
  export function parse_taproot_witness(witness) {
@@ -14,8 +16,8 @@ export function parse_taproot_witness(witness) {
14
16
  }
15
17
  const tweak = encode_taptweak(cblk.int_key, branch);
16
18
  const tapkey = ECC.tweak_pubkey(cblk.int_key, tweak, "bip340");
17
- params.map((e) => Buff.bytes(e).hex);
18
- return { cblock: cblk, params, script, tapkey: tapkey.hex, tweak: tweak.hex };
19
+ const hexParams = params.map((e) => Buff.bytes(e).hex);
20
+ return { cblock: cblk, params: hexParams, script, tapkey: tapkey.hex, tweak: tweak.hex };
19
21
  }
20
22
  export function parse_cblock(cblock) {
21
23
  const buffer = new Stream(cblock);
@@ -27,19 +29,19 @@ export function parse_cblock(cblock) {
27
29
  path.push(buffer.read(32).hex);
28
30
  }
29
31
  if (buffer.size !== 0) {
30
- throw new Error(`Non-empty buffer on control block: ${String(buffer)}`);
32
+ throw new DecodingError(`control block has ${buffer.size} extra bytes. Expected: 33 + (32 * path_length) bytes`);
31
33
  }
32
34
  return { int_key, path, parity, version };
33
35
  }
34
36
  export function parse_cblock_parity(cbits) {
35
- return cbits % 2 === 0 ? [cbits - 0, 0x02] : [cbits - 1, 0x03];
37
+ return cbits % 2 === 0 ? [cbits, 0x02] : [cbits - 1, 0x03];
36
38
  }
37
39
  export function parse_pubkey_parity(pubkey) {
38
- Assert.size(pubkey, 33, "invalid pubkey size");
40
+ Assert.ok(Buff.bytes(pubkey).length === 33, "invalid pubkey size");
39
41
  const [parity] = Buff.bytes(pubkey);
40
42
  if (parity === 0x02)
41
43
  return 0;
42
44
  if (parity === 0x03)
43
45
  return 1;
44
- throw new Error(`Invalid parity bit: ${String(parity)}`);
46
+ throw new ValidationError(`invalid pubkey parity prefix: 0x${parity.toString(16)}. Expected 0x02 (even) or 0x03 (odd)`);
45
47
  }
@@ -1,4 +1,5 @@
1
1
  import { Buff } from "@vbyte/buff";
2
+ import { ValidationError } from "../../error.js";
2
3
  import { encode_tapbranch } from "./encode.js";
3
4
  const MAX_TAPROOT_DEPTH = 128;
4
5
  export function get_merkle_root(leaves) {
@@ -6,12 +7,12 @@ export function get_merkle_root(leaves) {
6
7
  }
7
8
  export function merkleize(taptree, target, path = [], depth = 0) {
8
9
  if (depth > MAX_TAPROOT_DEPTH) {
9
- throw new Error(`Taproot tree depth ${depth} exceeds maximum ${MAX_TAPROOT_DEPTH}`);
10
+ throw new ValidationError(`Taproot tree depth ${depth} exceeds maximum ${MAX_TAPROOT_DEPTH}`, "depth");
10
11
  }
11
12
  const leaves = [];
12
13
  const tree = [];
13
14
  if (taptree.length < 1) {
14
- throw new Error("Tree is empty!");
15
+ throw new ValidationError("Taproot tree cannot be empty", "taptree");
15
16
  }
16
17
  for (let i = 0; i < taptree.length; i++) {
17
18
  const bytes = taptree[i];
@@ -1,4 +1,4 @@
1
- import { Assert } from "@vbyte/micro-lib";
1
+ import { Assert } from "@vbyte/util";
2
2
  import { COINBASE, DEFAULT } from "../../const.js";
3
3
  import { normalize_prevout, normalize_sequence, normalize_value, } from "./util.js";
4
4
  import { assert_tx_template, assert_vin_template, assert_vout_template, } from "./validate.js";
@@ -1,13 +1,14 @@
1
1
  import { Stream } from "@vbyte/buff";
2
- import { Assert } from "@vbyte/micro-lib/assert";
2
+ import { Assert } from "@vbyte/util";
3
3
  import { COINBASE, MAX_VARINT_SIZE } from "../../const.js";
4
+ import { DecodingError } from "../../error.js";
4
5
  const MAX_TX_SIZE = 4_000_000;
5
6
  const MAX_TX_ELEMENTS = 100_000;
6
7
  export function decode_tx(txdata, use_segwit = true) {
7
- Assert.is_bytes(txdata, "txdata must be hex or bytes");
8
+ Assert.ok(typeof txdata === "string" || txdata instanceof Uint8Array, "txdata must be hex or bytes");
8
9
  const txSize = typeof txdata === "string" ? txdata.length / 2 : txdata.length;
9
10
  if (txSize > MAX_TX_SIZE) {
10
- throw new Error(`Transaction size ${txSize} exceeds maximum ${MAX_TX_SIZE} bytes`);
11
+ throw new DecodingError(`Transaction size ${txSize} exceeds maximum ${MAX_TX_SIZE} bytes`);
11
12
  }
12
13
  const stream = new Stream(txdata);
13
14
  const version = read_version(stream);
@@ -34,7 +35,7 @@ function check_witness_flag(stream) {
34
35
  return true;
35
36
  }
36
37
  else {
37
- throw new Error(`Invalid witness flag: ${flag}`);
38
+ throw new DecodingError(`Invalid witness flag: ${flag}`, 1);
38
39
  }
39
40
  }
40
41
  return false;
@@ -43,7 +44,7 @@ function read_inputs(stream) {
43
44
  const inputs = [];
44
45
  const vinCount = stream.read_varint();
45
46
  if (vinCount > MAX_TX_ELEMENTS) {
46
- throw new Error(`Input count ${vinCount} exceeds maximum ${MAX_TX_ELEMENTS}`);
47
+ throw new DecodingError(`Input count ${vinCount} exceeds maximum ${MAX_TX_ELEMENTS}`);
47
48
  }
48
49
  for (let i = 0; i < vinCount; i++) {
49
50
  const txinput = read_vin(stream);
@@ -84,14 +85,15 @@ function read_outputs(stream) {
84
85
  const outputs = [];
85
86
  const vcount = stream.read_varint();
86
87
  if (vcount > MAX_TX_ELEMENTS) {
87
- throw new Error(`Output count ${vcount} exceeds maximum ${MAX_TX_ELEMENTS}`);
88
+ throw new DecodingError(`Output count ${vcount} exceeds maximum ${MAX_TX_ELEMENTS}`);
88
89
  }
89
90
  for (let i = 0; i < vcount; i++) {
90
91
  try {
91
92
  outputs.push(read_vout(stream));
92
93
  }
93
- catch (_error) {
94
- throw new Error(`failed to decode output at index ${i}`);
94
+ catch (error) {
95
+ const message = error instanceof Error ? error.message : String(error);
96
+ throw new DecodingError(`Failed to decode output at index ${i}: ${message}`);
95
97
  }
96
98
  }
97
99
  return outputs;
@@ -106,12 +108,12 @@ function read_witness(stream) {
106
108
  const stack = [];
107
109
  const count = stream.read_varint();
108
110
  if (count > MAX_TX_ELEMENTS) {
109
- throw new Error(`Witness element count ${count} exceeds maximum ${MAX_TX_ELEMENTS}`);
111
+ throw new DecodingError(`Witness element count ${count} exceeds maximum ${MAX_TX_ELEMENTS}`);
110
112
  }
111
113
  for (let i = 0; i < count; i++) {
112
114
  const element = read_payload(stream);
113
115
  if (element === null) {
114
- throw new Error(`failed to decode witness element at index ${i}`);
116
+ throw new DecodingError(`Failed to decode witness element at index ${i}`);
115
117
  }
116
118
  stack.push(element);
117
119
  }
@@ -120,7 +122,7 @@ function read_witness(stream) {
120
122
  export function read_payload(stream) {
121
123
  const size = stream.read_varint("le");
122
124
  if (size > MAX_VARINT_SIZE) {
123
- throw new Error(`Payload size ${size} exceeds maximum ${MAX_VARINT_SIZE}`);
125
+ throw new DecodingError(`Payload size ${size} exceeds maximum ${MAX_VARINT_SIZE}`);
124
126
  }
125
127
  return size > 0 ? stream.read(size).hex : null;
126
128
  }
@@ -1,5 +1,5 @@
1
1
  import { Buff } from "@vbyte/buff";
2
- import { Assert } from "@vbyte/micro-lib";
2
+ import { Assert } from "@vbyte/util";
3
3
  import { COINBASE } from "../../const.js";
4
4
  import { assert_tx_data } from "./validate.js";
5
5
  export function encode_tx(txdata, use_segwit = true) {
@@ -1,4 +1,4 @@
1
- import { Assert } from "@vbyte/micro-lib/assert";
1
+ import { Assert } from "@vbyte/util";
2
2
  import { create_tx, create_tx_output } from "./create.js";
3
3
  import { decode_tx } from "./decode.js";
4
4
  import { assert_tx_template } from "./validate.js";
@@ -31,14 +31,14 @@ export function serialize_tx(txdata) {
31
31
  if (e.prevout !== null) {
32
32
  vin.push({
33
33
  script_pk: e.prevout.script_pk,
34
- value: Number(e.prevout.value),
34
+ value: String(e.prevout.value),
35
35
  });
36
36
  }
37
37
  }
38
38
  for (const e of tx.vout) {
39
39
  vout.push({
40
40
  script_pk: e.script_pk,
41
- value: Number(e.value),
41
+ value: String(e.value),
42
42
  });
43
43
  }
44
44
  return { version, locktime, vin, vout };
@@ -4,16 +4,14 @@ import { parse_tx } from "./parse.js";
4
4
  const WIT_FLAG_BYTES = 2;
5
5
  export function get_vsize(bytes) {
6
6
  const weight = Buff.bytes(bytes).length;
7
- const remain = weight % 4 > 0 ? 1 : 0;
8
- return Math.ceil(weight / 4) + remain;
7
+ return Math.ceil(weight / 4);
9
8
  }
10
9
  export function get_txsize(txdata) {
11
10
  const json = parse_tx(txdata);
12
11
  const base = encode_tx(json, false).length;
13
12
  const total = encode_tx(json, true).length;
14
13
  const weight = base * 3 + total;
15
- const remain = weight % 4 > 0 ? 1 : 0;
16
- const vsize = Math.ceil(weight / 4) + remain;
14
+ const vsize = Math.ceil(weight / 4);
17
15
  return { base, total, vsize, weight };
18
16
  }
19
17
  export function get_vin_size(vin) {
@@ -1,7 +1,6 @@
1
1
  import { Buff } from "@vbyte/buff";
2
- import { Test } from "@vbyte/micro-lib";
3
- import { Assert } from "@vbyte/micro-lib/assert";
4
- import { hash256 } from "@vbyte/micro-lib/hash";
2
+ import { Assert, Test } from "@vbyte/util";
3
+ import { hash256 } from "@vbyte/crypto/hash";
5
4
  import { DEFAULT } from "../../const.js";
6
5
  import { decode_tx } from "./decode.js";
7
6
  import { encode_tx } from "./encode.js";
@@ -1,28 +1,56 @@
1
1
  import * as Schema from "../../schema/index.js";
2
+ import { ValidationError } from "../../error.js";
3
+ function format_zod_error(error) {
4
+ const issues = error.issues.map((issue) => {
5
+ const path = issue.path.length > 0 ? `${issue.path.join(".")}: ` : "";
6
+ return `${path}${issue.message}`;
7
+ });
8
+ return issues.join("; ");
9
+ }
2
10
  export function assert_tx_template(txdata) {
3
- Schema.tx.tx_template.parse(txdata);
11
+ const result = Schema.tx.tx_template.safeParse(txdata);
12
+ if (!result.success) {
13
+ throw new ValidationError(`invalid transaction template: ${format_zod_error(result.error)}`);
14
+ }
4
15
  }
5
16
  export function assert_has_prevouts(vin) {
6
- if (vin.some((txin) => txin.prevout === null)) {
7
- throw new Error("transaction missing prevouts");
17
+ const missingIdx = vin.findIndex((txin) => txin.prevout === null || txin.prevout === undefined);
18
+ if (missingIdx !== -1) {
19
+ throw new ValidationError(`transaction input at index ${missingIdx} is missing prevout data. ` +
20
+ `Prevout (previous output) is required for signing`);
8
21
  }
9
22
  }
10
23
  export function assert_tx_data(txdata) {
11
- Schema.tx.tx_data.parse(txdata);
24
+ const result = Schema.tx.tx_data.safeParse(txdata);
25
+ if (!result.success) {
26
+ throw new ValidationError(`invalid transaction data: ${format_zod_error(result.error)}`);
27
+ }
12
28
  }
13
29
  export function assert_tx_spend_data(txdata) {
14
30
  assert_tx_data(txdata);
15
31
  assert_has_prevouts(txdata.vin);
16
32
  }
17
33
  export function assert_tx_input(tx_input) {
18
- Schema.tx.tx_input.parse(tx_input);
34
+ const result = Schema.tx.tx_input.safeParse(tx_input);
35
+ if (!result.success) {
36
+ throw new ValidationError(`invalid transaction input: ${format_zod_error(result.error)}`);
37
+ }
19
38
  }
20
39
  export function assert_tx_output(tx_output) {
21
- Schema.tx.tx_output.parse(tx_output);
40
+ const result = Schema.tx.tx_output.safeParse(tx_output);
41
+ if (!result.success) {
42
+ throw new ValidationError(`invalid transaction output: ${format_zod_error(result.error)}`);
43
+ }
22
44
  }
23
45
  export function assert_vin_template(vin) {
24
- Schema.tx.vin_template.parse(vin);
46
+ const result = Schema.tx.vin_template.safeParse(vin);
47
+ if (!result.success) {
48
+ throw new ValidationError(`invalid input template: ${format_zod_error(result.error)}`);
49
+ }
25
50
  }
26
51
  export function assert_vout_template(vout) {
27
- Schema.tx.vout_template.parse(vout);
52
+ const result = Schema.tx.vout_template.safeParse(vout);
53
+ if (!result.success) {
54
+ throw new ValidationError(`invalid output template: ${format_zod_error(result.error)}`);
55
+ }
28
56
  }
@@ -1,5 +1,5 @@
1
1
  import { Buff } from "@vbyte/buff";
2
- import { Assert } from "@vbyte/micro-lib";
2
+ import { Assert } from "@vbyte/util";
3
3
  const WIT_LENGTH_BYTE = 1;
4
4
  export function get_witness_size(witness) {
5
5
  const stack = witness.map((e) => Buff.bytes(e));