@btc-vision/bitcoin 6.4.1 → 6.4.3

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 (55) hide show
  1. package/browser/address.d.ts +1 -0
  2. package/browser/index.js +1 -1
  3. package/browser/payments/embed.d.ts +2 -2
  4. package/browser/payments/index.d.ts +61 -12
  5. package/browser/payments/lazy.d.ts +1 -1
  6. package/browser/payments/p2ms.d.ts +2 -2
  7. package/browser/payments/p2op.d.ts +25 -0
  8. package/browser/payments/p2pk.d.ts +2 -2
  9. package/browser/payments/p2pkh.d.ts +2 -2
  10. package/browser/payments/p2sh.d.ts +2 -2
  11. package/browser/payments/p2tr.d.ts +2 -2
  12. package/browser/payments/p2wpkh.d.ts +2 -2
  13. package/browser/payments/p2wsh.d.ts +2 -2
  14. package/browser/psbt/psbtutils.d.ts +1 -0
  15. package/build/address.js +4 -5
  16. package/build/payments/embed.d.ts +2 -2
  17. package/build/payments/embed.js +6 -2
  18. package/build/payments/index.d.ts +61 -12
  19. package/build/payments/index.js +1 -0
  20. package/build/payments/lazy.d.ts +1 -1
  21. package/build/payments/p2ms.d.ts +2 -2
  22. package/build/payments/p2ms.js +8 -2
  23. package/build/payments/p2op.d.ts +25 -0
  24. package/build/payments/p2op.js +112 -0
  25. package/build/payments/p2pk.d.ts +2 -2
  26. package/build/payments/p2pk.js +5 -1
  27. package/build/payments/p2pkh.d.ts +2 -2
  28. package/build/payments/p2pkh.js +5 -1
  29. package/build/payments/p2sh.d.ts +2 -2
  30. package/build/payments/p2sh.js +5 -1
  31. package/build/payments/p2tr.d.ts +2 -2
  32. package/build/payments/p2tr.js +5 -2
  33. package/build/payments/p2wpkh.d.ts +2 -2
  34. package/build/payments/p2wpkh.js +5 -1
  35. package/build/payments/p2wsh.d.ts +2 -2
  36. package/build/payments/p2wsh.js +5 -1
  37. package/build/psbt/psbtutils.d.ts +1 -0
  38. package/build/psbt/psbtutils.js +2 -0
  39. package/build/psbt.js +3 -1
  40. package/package.json +1 -1
  41. package/src/address.ts +283 -287
  42. package/src/payments/embed.ts +61 -55
  43. package/src/payments/index.ts +110 -13
  44. package/src/payments/lazy.ts +28 -28
  45. package/src/payments/p2ms.ts +11 -5
  46. package/src/payments/p2op.ts +170 -0
  47. package/src/payments/p2pk.ts +93 -85
  48. package/src/payments/p2pkh.ts +214 -210
  49. package/src/payments/p2sh.ts +211 -206
  50. package/src/payments/p2tr.ts +9 -4
  51. package/src/payments/p2wpkh.ts +7 -3
  52. package/src/payments/p2wsh.ts +7 -3
  53. package/src/psbt/psbtutils.ts +322 -320
  54. package/src/psbt.ts +5 -3
  55. package/test/payments.spec.ts +2 -2
@@ -1,55 +1,61 @@
1
- import { bitcoin as BITCOIN_NETWORK } from '../networks.js';
2
- import * as bscript from '../script.js';
3
- import { stacksEqual, typeforce as typef } from '../types.js';
4
- import { Payment, PaymentOpts, Stack } from './index.js';
5
- import * as lazy from './lazy.js';
6
-
7
- const OPS = bscript.OPS;
8
-
9
- // output: OP_RETURN ...
10
- /**
11
- * Embeds data in a Bitcoin payment.
12
- * @param a - The payment object.
13
- * @param opts - Optional payment options.
14
- * @returns The modified payment object.
15
- * @throws {TypeError} If there is not enough data or if the output is invalid.
16
- */
17
- export function p2data(a: Payment, opts?: PaymentOpts): Payment {
18
- if (!a.data && !a.output) throw new TypeError('Not enough data');
19
- opts = Object.assign({ validate: true }, opts || {});
20
-
21
- typef(
22
- {
23
- network: typef.maybe(typef.Object),
24
- output: typef.maybe(typef.Buffer),
25
- data: typef.maybe(typef.arrayOf(typef.Buffer)),
26
- },
27
- a,
28
- );
29
-
30
- const network = a.network || BITCOIN_NETWORK;
31
- const o = { name: 'embed', network } as Payment;
32
-
33
- lazy.prop(o, 'output', () => {
34
- if (!a.data) return;
35
- return bscript.compile(([OPS.OP_RETURN] as Stack).concat(a.data));
36
- });
37
- lazy.prop(o, 'data', () => {
38
- if (!a.output) return;
39
- return bscript.decompile(a.output)!.slice(1);
40
- });
41
-
42
- // extended validation
43
- if (opts.validate) {
44
- if (a.output) {
45
- const chunks = bscript.decompile(a.output);
46
- if (chunks![0] !== OPS.OP_RETURN) throw new TypeError('Output is invalid');
47
- if (!chunks!.slice(1).every(typef.Buffer)) throw new TypeError('Output is invalid');
48
-
49
- if (a.data && !stacksEqual(a.data, o.data as Buffer[]))
50
- throw new TypeError('Data mismatch');
51
- }
52
- }
53
-
54
- return Object.assign(o, a);
55
- }
1
+ import { bitcoin as BITCOIN_NETWORK } from '../networks.js';
2
+ import * as bscript from '../script.js';
3
+ import { stacksEqual, typeforce as typef } from '../types.js';
4
+ import { EmbedPayment, PaymentOpts, Stack } from './index.js';
5
+ import * as lazy from './lazy.js';
6
+
7
+ const OPS = bscript.OPS;
8
+
9
+ // output: OP_RETURN ...
10
+ /**
11
+ * Embeds data in a Bitcoin payment.
12
+ * @param a - The payment object.
13
+ * @param opts - Optional payment options.
14
+ * @returns The modified payment object.
15
+ * @throws {TypeError} If there is not enough data or if the output is invalid.
16
+ */
17
+ export function p2data(a: Omit<EmbedPayment, 'name'>, opts?: PaymentOpts): EmbedPayment {
18
+ if (!a.data && !a.output) throw new TypeError('Not enough data');
19
+ opts = Object.assign({ validate: true }, opts || {});
20
+
21
+ typef(
22
+ {
23
+ network: typef.maybe(typef.Object),
24
+ output: typef.maybe(typef.Buffer),
25
+ data: typef.maybe(typef.arrayOf(typef.Buffer)),
26
+ },
27
+ a,
28
+ );
29
+
30
+ const network = a.network || BITCOIN_NETWORK;
31
+ const o: EmbedPayment = { name: 'embed', network, data: [] };
32
+
33
+ lazy.prop(o, 'output', () => {
34
+ if (!a.data) return;
35
+ return bscript.compile(([OPS.OP_RETURN] as Stack).concat(a.data));
36
+ });
37
+
38
+ lazy.prop(o, 'data', () => {
39
+ if (!a.output) return;
40
+ const script = bscript.decompile(a.output);
41
+ if (script === null || script === undefined) {
42
+ return;
43
+ }
44
+
45
+ return script.slice(1) as Buffer[];
46
+ });
47
+
48
+ // extended validation
49
+ if (opts.validate) {
50
+ if (a.output) {
51
+ const chunks = bscript.decompile(a.output);
52
+ if (chunks![0] !== OPS.OP_RETURN) throw new TypeError('Output is invalid');
53
+ if (!chunks!.slice(1).every(typef.Buffer)) throw new TypeError('Output is invalid');
54
+
55
+ if (a.data && !stacksEqual(a.data, o.data as Buffer[]))
56
+ throw new TypeError('Data mismatch');
57
+ }
58
+ }
59
+
60
+ return Object.assign(o, a);
61
+ }
@@ -19,35 +19,132 @@ export * from './p2sh.js';
19
19
  export * from './p2tr.js';
20
20
  export * from './p2wpkh.js';
21
21
  export * from './p2wsh.js';
22
+ export * from './p2op.js';
22
23
 
23
- export interface Payment {
24
+ export interface BasePayment {
25
+ /** Convenience label, also the discriminant for the union. */
24
26
  name?: string;
27
+ /** Network parameters (mainnet if omitted). */
25
28
  network?: Network;
29
+ /** Fully-assembled scriptPubKey (if already known). */
26
30
  output?: Buffer;
27
- data?: Buffer[];
31
+ /** Raw scriptSig (legacy script types only). */
32
+ input?: Buffer;
33
+ /** Human-readable address (if already known). */
34
+ address?: string;
35
+ /** Segwit stack (empty for legacy). */
36
+ witness?: Buffer[];
37
+
38
+ /** Script template for P2SH, P2WSH, P2TR, etc. */
39
+ redeem?: ScriptRedeem;
40
+
41
+ /** Non-standard options used by some wallets. */
42
+ useHybrid?: boolean;
43
+ useUncompressed?: boolean;
44
+ }
45
+
46
+ /** Helper used by redeeming script-template outputs (P2SH, P2WSH). */
47
+ export interface ScriptRedeem extends BasePayment {
48
+ output?: Buffer; // script template
49
+ redeemVersion?: number; // tapscript leaves etc.
50
+ network?: Network; // network parameters (mainnet if omitted)
51
+ }
52
+
53
+ export interface P2PKPayment extends BasePayment {
54
+ name: 'p2pk';
55
+ pubkey?: Buffer;
56
+ /** DER-encoded sig – empty until signed. */
57
+ signature?: Buffer;
58
+ }
59
+
60
+ export interface P2PKHPayment extends BasePayment {
61
+ name: 'p2pkh';
62
+ /** RIPEMD-160(SHA-256(pubkey)) – 20 bytes. */
63
+ hash?: Buffer;
64
+ pubkey?: Buffer;
65
+ signature?: Buffer;
66
+ }
67
+
68
+ export interface P2SHPayment extends BasePayment {
69
+ name: 'p2sh';
70
+ /** Hash160 of a redeem script. */
71
+ hash?: Buffer;
72
+
73
+ /** The entire signature stack when spending a P2SH (non-segwit). */
74
+ signatures?: Buffer[];
75
+ }
76
+
77
+ export interface P2MSPayment extends BasePayment {
78
+ name: 'p2ms';
79
+ /** M-of-N parameters. */
28
80
  m?: number;
29
81
  n?: number;
30
82
  pubkeys?: Buffer[];
31
- input?: Buffer;
32
83
  signatures?: Buffer[];
33
- internalPubkey?: Buffer;
84
+ }
85
+
86
+ export interface P2WPKHPayment extends BasePayment {
87
+ name: 'p2wpkh';
88
+ /** 20-byte witness program. */
89
+ hash?: Buffer;
34
90
  pubkey?: Buffer;
35
91
  signature?: Buffer;
36
- address?: string;
92
+ }
93
+
94
+ export interface P2WSHPayment extends BasePayment {
95
+ name: 'p2wsh';
96
+ /** 32-byte witness program. */
37
97
  hash?: Buffer;
38
- redeem?: Payment;
39
- redeemVersion?: number;
98
+ redeem?: ScriptRedeem;
99
+ }
100
+
101
+ export interface P2TRPayment extends BasePayment {
102
+ name: 'p2tr';
103
+ /** x-only pubkey that commits to the tree. */
104
+ pubkey?: Buffer;
105
+ /** Internal (untweaked) x-only pubkey. */
106
+ internalPubkey?: Buffer;
107
+ /** Merkle-root tweak, present when a script path exists. */
108
+ hash?: Buffer;
109
+ /** Full taptree description (optional, dev-side). */
40
110
  scriptTree?: Taptree;
41
- witness?: Buffer[];
111
+ /** Key-path sig or leading stack elem. */
112
+ signature?: Buffer;
42
113
 
43
- // NEW FIELDS
44
- useHybrid?: boolean;
45
- useUncompressed?: boolean;
114
+ redeemVersion?: number; // tapscript leaves etc.
115
+ redeem?: ScriptRedeem;
116
+ }
117
+
118
+ export interface P2OPPayment extends BasePayment {
119
+ name: 'p2op';
120
+ /** <deploymentVersion || HASH160(payload)> (2–40 bytes). */
121
+ program?: Buffer;
122
+ deploymentVersion: number | undefined;
123
+ /** Convenience slice of `program` (20 bytes for current spec). */
124
+ hash160?: Buffer;
125
+ }
126
+
127
+ /** OP_RETURN data-carrying output */
128
+ export interface EmbedPayment extends BasePayment {
129
+ name: 'embed';
130
+ /** Raw pushed chunks after OP_RETURN. */
131
+ data: Buffer[];
132
+ // `output` is automatically derived from `data` (or vice-versa)
46
133
  }
47
134
 
48
- export type PaymentCreator = (a: Payment, opts?: PaymentOpts) => Payment;
135
+ export type Payment =
136
+ | P2PKPayment
137
+ | P2PKHPayment
138
+ | P2SHPayment
139
+ | P2MSPayment
140
+ | P2WPKHPayment
141
+ | P2WSHPayment
142
+ | P2TRPayment
143
+ | P2OPPayment
144
+ | EmbedPayment
145
+ | ScriptRedeem;
49
146
 
50
- export type PaymentFunction = () => Payment;
147
+ export type PaymentCreator = <T extends BasePayment>(a: T, opts?: PaymentOpts) => T;
51
148
 
52
149
  export interface PaymentOpts {
53
150
  validate?: boolean;
@@ -1,28 +1,28 @@
1
- export function prop(object: {}, name: string, f: () => any): void {
2
- Object.defineProperty(object, name, {
3
- configurable: true,
4
- enumerable: true,
5
- get(): any {
6
- const _value = f.call(this);
7
- this[name] = _value;
8
- return _value;
9
- },
10
- set(_value: any): void {
11
- Object.defineProperty(this, name, {
12
- configurable: true,
13
- enumerable: true,
14
- value: _value,
15
- writable: true,
16
- });
17
- },
18
- });
19
- }
20
-
21
- export function value<T>(f: () => T): () => T {
22
- let _value: T;
23
- return (): T => {
24
- if (_value !== undefined) return _value;
25
- _value = f();
26
- return _value;
27
- };
28
- }
1
+ export function prop<T extends {}>(object: T, name: string, f: () => T[keyof T]): void {
2
+ Object.defineProperty(object, name, {
3
+ configurable: true,
4
+ enumerable: true,
5
+ get(): unknown {
6
+ const _value = f.call(this);
7
+ this[name] = _value;
8
+ return _value;
9
+ },
10
+ set(_value: unknown): void {
11
+ Object.defineProperty(this, name, {
12
+ configurable: true,
13
+ enumerable: true,
14
+ value: _value,
15
+ writable: true,
16
+ });
17
+ },
18
+ });
19
+ }
20
+
21
+ export function value<T>(f: () => T): () => T {
22
+ let _value: T;
23
+ return (): T => {
24
+ if (_value !== undefined) return _value;
25
+ _value = f();
26
+ return _value;
27
+ };
28
+ }
@@ -1,7 +1,7 @@
1
1
  import { bitcoin as BITCOIN_NETWORK } from '../networks.js';
2
2
  import * as bscript from '../script.js';
3
3
  import { isPoint, stacksEqual, typeforce as typef } from '../types.js';
4
- import { Payment, PaymentOpts, Stack } from './index.js';
4
+ import { P2MSPayment, PaymentOpts, Stack } from './index.js';
5
5
  import * as lazy from './lazy.js';
6
6
 
7
7
  const OPS = bscript.OPS;
@@ -17,7 +17,7 @@ const OP_INT_BASE = OPS.OP_RESERVED; // OP_1 - 1
17
17
  * @returns The created payment object.
18
18
  * @throws {TypeError} If the provided data is not valid.
19
19
  */
20
- export function p2ms(a: Payment, opts?: PaymentOpts): Payment {
20
+ export function p2ms(a: Omit<P2MSPayment, 'name'>, opts?: PaymentOpts): P2MSPayment {
21
21
  if (!a.input && !a.output && !(a.pubkeys && a.m !== undefined) && !a.signatures)
22
22
  throw new TypeError('Not enough data');
23
23
  opts = Object.assign({ validate: true }, opts || {});
@@ -44,7 +44,10 @@ export function p2ms(a: Payment, opts?: PaymentOpts): Payment {
44
44
  );
45
45
 
46
46
  const network = a.network || BITCOIN_NETWORK;
47
- const o: Payment = { network };
47
+ const o: P2MSPayment = {
48
+ network,
49
+ name: 'p2ms',
50
+ };
48
51
 
49
52
  let chunks: Stack = [];
50
53
  let decoded = false;
@@ -85,9 +88,12 @@ export function p2ms(a: Payment, opts?: PaymentOpts): Payment {
85
88
  decode(a.output);
86
89
  return o.pubkeys;
87
90
  });
88
- lazy.prop(o, 'signatures', () => {
91
+ lazy.prop<P2MSPayment>(o, 'signatures', () => {
89
92
  if (!a.input) return;
90
- return bscript.decompile(a.input)!.slice(1);
93
+ const decompiled = bscript.decompile(a.input);
94
+ if (decompiled === null || decompiled === undefined) return;
95
+
96
+ return decompiled.slice(1) as Buffer[];
91
97
  });
92
98
  lazy.prop(o, 'input', () => {
93
99
  if (!a.signatures) return;
@@ -0,0 +1,170 @@
1
+ import { bech32m } from 'bech32';
2
+ import { Buffer as NBuffer } from 'buffer';
3
+ import { fromBech32 } from '../address.js';
4
+ import { bitcoin as BITCOIN_NETWORK, Network } from '../networks.js';
5
+ import * as bscript from '../script.js';
6
+ import { typeforce as typef } from '../types.js';
7
+ import * as lazy from './lazy.js';
8
+ import { BasePayment, P2OPPayment, PaymentOpts } from './index.js';
9
+
10
+ const OPS = bscript.OPS;
11
+ const P2OP_WITNESS_VERSION = 0x10;
12
+ const MIN_SIZE = 2;
13
+ const MAX_SIZE = 40;
14
+
15
+ interface P2OPBase extends BasePayment {
16
+ name: 'p2op';
17
+ }
18
+
19
+ interface P2OP_fromOutput extends P2OPBase {
20
+ output: Buffer;
21
+
22
+ program?: undefined;
23
+ deploymentVersion?: undefined;
24
+ hash160?: undefined;
25
+ }
26
+
27
+ interface P2OP_fromProgram extends P2OPBase {
28
+ program: Buffer;
29
+
30
+ output?: undefined;
31
+ deploymentVersion?: never;
32
+ hash160?: never;
33
+ }
34
+
35
+ interface P2OP_fromParts extends P2OPBase {
36
+ deploymentVersion: number;
37
+ hash160: Buffer;
38
+
39
+ output?: undefined;
40
+ program?: undefined;
41
+ }
42
+
43
+ export type P2OPPaymentParams = P2OP_fromOutput | P2OP_fromProgram | P2OP_fromParts;
44
+
45
+ /**
46
+ * Pay-to-OPNet (P2OP) decoder / encoder.
47
+ *
48
+ * ▪ witness program = <deploymentVersion:uint8><hash160:20-bytes|...>
49
+ * ▪ scriptPubKey = OP_16 <program>
50
+ * ▪ address HRP = network.bech32Opnet, encoded with Bech32m
51
+ *
52
+ * Accepts any combination of { address, output, program } and returns a
53
+ * fully lazy payment object, mirroring the style of `p2tr`.
54
+ */
55
+ export function p2op(a: Omit<P2OPPaymentParams, 'name'>, opts?: PaymentOpts): P2OPPayment {
56
+ if (!a.address && !a.output && !a.program) throw new TypeError('Not enough data for p2op');
57
+
58
+ opts = Object.assign({ validate: true }, opts || {});
59
+
60
+ typef(
61
+ {
62
+ address: typef.maybe(typef.String),
63
+ output: typef.maybe(typef.Buffer),
64
+ program: typef.maybe(typef.Buffer),
65
+ network: typef.maybe(typef.Object),
66
+ deploymentVersion: typef.maybe(typef.Number),
67
+ hash160: typef.maybe(typef.BufferN(20)),
68
+ },
69
+ a,
70
+ );
71
+
72
+ const _address = lazy.value(() => fromBech32(a.address!));
73
+
74
+ const network: Network = a.network || BITCOIN_NETWORK;
75
+ const o: P2OPPayment = {
76
+ name: 'p2op',
77
+ network,
78
+ deploymentVersion: 0,
79
+ };
80
+
81
+ lazy.prop(o, 'program', () => {
82
+ if (a.program) return a.program;
83
+
84
+ if (a.output) {
85
+ if (a.output[0] !== OPS.OP_16) throw new TypeError('Invalid P2OP script');
86
+ let pushPos = 1,
87
+ progLen: number;
88
+ if (a.output[1] < 0x4c) {
89
+ progLen = a.output[1];
90
+ pushPos = 2;
91
+ } else if (a.output[1] === 0x4c) {
92
+ progLen = a.output[2];
93
+ pushPos = 3;
94
+ } else {
95
+ throw new TypeError('Unsupported push opcode in P2OP script');
96
+ }
97
+ return a.output.slice(pushPos, pushPos + progLen);
98
+ }
99
+
100
+ if (a.address) {
101
+ const dec = _address();
102
+ return dec.data;
103
+ }
104
+ });
105
+
106
+ lazy.prop(o, 'deploymentVersion', () => {
107
+ if (!o.program) return;
108
+ return o.program[0];
109
+ });
110
+
111
+ lazy.prop(o, 'hash160', () => {
112
+ if (!o.program) return;
113
+ return o.program.slice(1);
114
+ });
115
+
116
+ lazy.prop(o, 'output', () => {
117
+ if (!o.program) return;
118
+ return bscript.compile([OPS.OP_16, o.program]);
119
+ });
120
+
121
+ lazy.prop(o, 'address', () => {
122
+ if (!o.program) return;
123
+ if (!network.bech32Opnet) {
124
+ throw new TypeError('Network does not support opnet');
125
+ }
126
+
127
+ const words = bech32m.toWords(o.program);
128
+ words.unshift(P2OP_WITNESS_VERSION);
129
+
130
+ return bech32m.encode(network.bech32Opnet, words);
131
+ });
132
+
133
+ // extended validation
134
+ if (opts.validate) {
135
+ let prog: Buffer = NBuffer.alloc(0);
136
+
137
+ if (a.address) {
138
+ const dec = _address();
139
+ if (network.bech32Opnet !== dec.prefix)
140
+ throw new TypeError('Invalid prefix or network mismatch');
141
+ if (dec.version !== P2OP_WITNESS_VERSION)
142
+ throw new TypeError('Invalid witness version for p2op');
143
+ if (dec.data.length < MIN_SIZE || dec.data.length > MAX_SIZE)
144
+ throw new TypeError('Invalid witness program length');
145
+ prog = dec.data;
146
+ }
147
+
148
+ if (a.program) {
149
+ if (prog.length && !prog.equals(a.program)) throw new TypeError('Program mismatch');
150
+ prog = a.program;
151
+ }
152
+
153
+ if (a.output) {
154
+ const outProg = o.program!;
155
+ if (prog.length && !prog.equals(outProg))
156
+ throw new TypeError('Program mismatch (output vs other source)');
157
+ prog = outProg;
158
+ }
159
+
160
+ if (prog.length < MIN_SIZE || prog.length > MAX_SIZE)
161
+ throw new TypeError('Witness program must be 2–40 bytes');
162
+
163
+ if (a.deploymentVersion !== undefined && a.deploymentVersion !== prog[0])
164
+ throw new TypeError('deploymentVersion mismatch');
165
+
166
+ if (a.hash160 && !a.hash160.equals(prog.slice(1))) throw new TypeError('hash160 mismatch');
167
+ }
168
+
169
+ return Object.assign(o, a);
170
+ }