@btc-vision/bitcoin 6.4.5 → 6.4.6

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/browser/psbt.d.ts CHANGED
@@ -155,11 +155,11 @@ type FinalScriptsFunc = (inputIndex: number, input: PsbtInput, script: Buffer, i
155
155
  type FinalTaprootScriptsFunc = (inputIndex: number, input: PsbtInput, tapLeafHashToFinalize?: Buffer) => {
156
156
  finalScriptWitness: Buffer | undefined;
157
157
  };
158
- export declare function getFinalScripts(inputIndex: number, input: PsbtInput, script: Buffer, isSegwit: boolean, isP2SH: boolean, isP2WSH: boolean, canRunChecks?: boolean): {
158
+ export declare function getFinalScripts(inputIndex: number, input: PsbtInput, script: Buffer, isSegwit: boolean, isP2SH: boolean, isP2WSH: boolean, canRunChecks?: boolean, solution?: Buffer[]): {
159
159
  finalScriptSig: Buffer | undefined;
160
160
  finalScriptWitness: Buffer | undefined;
161
161
  };
162
- export declare function prepareFinalScripts(script: Buffer, scriptType: string, partialSig: PartialSig[], isSegwit: boolean, isP2SH: boolean, isP2WSH: boolean): {
162
+ export declare function prepareFinalScripts(script: Buffer, scriptType: string, partialSig: PartialSig[], isSegwit: boolean, isP2SH: boolean, isP2WSH: boolean, solution?: Buffer[]): {
163
163
  finalScriptSig: Buffer | undefined;
164
164
  finalScriptWitness: Buffer | undefined;
165
165
  };
@@ -8,7 +8,16 @@ export interface Bech32Result {
8
8
  prefix: string;
9
9
  data: Buffer;
10
10
  }
11
+ export declare const FUTURE_SEGWIT_MAX_SIZE: number;
12
+ export declare const FUTURE_SEGWIT_MIN_SIZE: number;
13
+ export declare const FUTURE_SEGWIT_MAX_VERSION: number;
14
+ export declare const FUTURE_MAX_VERSION: number;
15
+ export declare const FUTURE_OPNET_VERSION: number;
16
+ export declare const FUTURE_SEGWIT_MIN_VERSION: number;
17
+ export declare const FUTURE_SEGWIT_VERSION_DIFF: number;
18
+ export declare const isUnknownSegwitVersion: (output: Buffer) => boolean;
11
19
  export declare function toFutureOPNetAddress(output: Buffer, network: Network): string;
20
+ export declare function _toFutureSegwitAddress(output: Buffer, network: Network): string;
12
21
  export declare function fromBase58Check(address: string): Base58CheckResult;
13
22
  export declare function fromBech32(address: string): Bech32Result;
14
23
  export declare function toBase58Check(hash: Buffer, version: number): string;
package/build/address.js CHANGED
@@ -4,17 +4,34 @@ import { opcodes, payments } from './index.js';
4
4
  import * as networks from './networks.js';
5
5
  import * as bscript from './script.js';
6
6
  import { Hash160bit, tuple, typeforce, UInt8 } from './types.js';
7
- const FUTURE_SEGWIT_MAX_SIZE = 40;
8
- const FUTURE_SEGWIT_MIN_SIZE = 2;
9
- const FUTURE_SEGWIT_MAX_VERSION = 15;
10
- const FUTURE_MAX_VERSION = 16;
11
- const FUTURE_OPNET_VERSION = 16;
12
- const FUTURE_SEGWIT_MIN_VERSION = 2;
13
- const FUTURE_SEGWIT_VERSION_DIFF = 0x50;
7
+ export const FUTURE_SEGWIT_MAX_SIZE = 40;
8
+ export const FUTURE_SEGWIT_MIN_SIZE = 2;
9
+ export const FUTURE_SEGWIT_MAX_VERSION = 15;
10
+ export const FUTURE_MAX_VERSION = 16;
11
+ export const FUTURE_OPNET_VERSION = 16;
12
+ export const FUTURE_SEGWIT_MIN_VERSION = 2;
13
+ export const FUTURE_SEGWIT_VERSION_DIFF = 0x50;
14
14
  const FUTURE_SEGWIT_VERSION_WARNING = 'WARNING: Sending to a future segwit version address can lead to loss of funds. ' +
15
15
  'End users MUST be warned carefully in the GUI and asked if they wish to proceed ' +
16
16
  'with caution. Wallets should verify the segwit version from the output of fromBech32, ' +
17
17
  'then decide when it is safe to use which version of segwit.';
18
+ export const isUnknownSegwitVersion = (output) => {
19
+ try {
20
+ const data = output.subarray(2);
21
+ if (data.length < FUTURE_SEGWIT_MIN_SIZE || data.length > FUTURE_SEGWIT_MAX_SIZE) {
22
+ throw new TypeError('Invalid program length for segwit address');
23
+ }
24
+ const version = output[0] - FUTURE_SEGWIT_VERSION_DIFF;
25
+ if (version < FUTURE_SEGWIT_MIN_VERSION || version > FUTURE_SEGWIT_MAX_VERSION + 1) {
26
+ throw new TypeError('Invalid version for segwit address');
27
+ }
28
+ if (version === 1)
29
+ throw new TypeError('taproot');
30
+ return true;
31
+ }
32
+ catch (e) { }
33
+ return false;
34
+ };
18
35
  export function toFutureOPNetAddress(output, network) {
19
36
  if (!Buffer.isBuffer(output))
20
37
  throw new TypeError('output must be a Buffer');
@@ -46,7 +63,7 @@ export function toFutureOPNetAddress(output, network) {
46
63
  const words = [version, ...bech32m.toWords(program)];
47
64
  return bech32m.encode(network.bech32Opnet, words);
48
65
  }
49
- function _toFutureSegwitAddress(output, network) {
66
+ export function _toFutureSegwitAddress(output, network) {
50
67
  const data = output.subarray(2);
51
68
  if (data.length < FUTURE_SEGWIT_MIN_SIZE || data.length > FUTURE_SEGWIT_MAX_SIZE) {
52
69
  throw new TypeError('Invalid program length for segwit address');
package/build/psbt.d.ts CHANGED
@@ -155,11 +155,11 @@ type FinalScriptsFunc = (inputIndex: number, input: PsbtInput, script: Buffer, i
155
155
  type FinalTaprootScriptsFunc = (inputIndex: number, input: PsbtInput, tapLeafHashToFinalize?: Buffer) => {
156
156
  finalScriptWitness: Buffer | undefined;
157
157
  };
158
- export declare function getFinalScripts(inputIndex: number, input: PsbtInput, script: Buffer, isSegwit: boolean, isP2SH: boolean, isP2WSH: boolean, canRunChecks?: boolean): {
158
+ export declare function getFinalScripts(inputIndex: number, input: PsbtInput, script: Buffer, isSegwit: boolean, isP2SH: boolean, isP2WSH: boolean, canRunChecks?: boolean, solution?: Buffer[]): {
159
159
  finalScriptSig: Buffer | undefined;
160
160
  finalScriptWitness: Buffer | undefined;
161
161
  };
162
- export declare function prepareFinalScripts(script: Buffer, scriptType: string, partialSig: PartialSig[], isSegwit: boolean, isP2SH: boolean, isP2WSH: boolean): {
162
+ export declare function prepareFinalScripts(script: Buffer, scriptType: string, partialSig: PartialSig[], isSegwit: boolean, isP2SH: boolean, isP2WSH: boolean, solution?: Buffer[]): {
163
163
  finalScriptSig: Buffer | undefined;
164
164
  finalScriptWitness: Buffer | undefined;
165
165
  };
package/build/psbt.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { Psbt as PsbtBase } from 'bip174';
2
2
  import * as varuint from 'bip174/src/lib/converter/varint.js';
3
3
  import { checkForInput, checkForOutput } from 'bip174/src/lib/utils.js';
4
- import { fromOutputScript, toOutputScript } from './address.js';
4
+ import { fromOutputScript, isUnknownSegwitVersion, toOutputScript } from './address.js';
5
5
  import { cloneBuffer, reverseBuffer } from './bufferutils.js';
6
6
  import { payments } from './index.js';
7
7
  import { bitcoin as btcNetwork } from './networks.js';
@@ -818,14 +818,14 @@ function getTxCacheValue(key, name, inputs, c, disableOutputChecks = false) {
818
818
  else if (key === '__FEE')
819
819
  return c.__FEE;
820
820
  }
821
- export function getFinalScripts(inputIndex, input, script, isSegwit, isP2SH, isP2WSH, canRunChecks = true) {
821
+ export function getFinalScripts(inputIndex, input, script, isSegwit, isP2SH, isP2WSH, canRunChecks = true, solution) {
822
822
  const scriptType = classifyScript(script);
823
823
  if (!canFinalize(input, script, scriptType) && canRunChecks) {
824
824
  throw new Error(`Can not finalize input #${inputIndex}`);
825
825
  }
826
- return prepareFinalScripts(script, scriptType, input.partialSig, isSegwit, isP2SH, isP2WSH);
826
+ return prepareFinalScripts(script, scriptType, input.partialSig, isSegwit, isP2SH, isP2WSH, solution);
827
827
  }
828
- export function prepareFinalScripts(script, scriptType, partialSig, isSegwit, isP2SH, isP2WSH) {
828
+ export function prepareFinalScripts(script, scriptType, partialSig, isSegwit, isP2SH, isP2WSH, solution) {
829
829
  let finalScriptSig;
830
830
  let finalScriptWitness;
831
831
  const payment = getPayment(script, scriptType, partialSig);
@@ -835,19 +835,28 @@ export function prepareFinalScripts(script, scriptType, partialSig, isSegwit, is
835
835
  if (p2wsh) {
836
836
  finalScriptWitness = witnessStackToScriptWitness(p2wsh.witness);
837
837
  }
838
- else {
838
+ else if (payment) {
839
839
  finalScriptWitness = witnessStackToScriptWitness(payment.witness);
840
840
  }
841
+ else {
842
+ finalScriptWitness = witnessStackToScriptWitness(solution ?? [Buffer.from([0x00])]);
843
+ }
841
844
  if (p2sh) {
842
- finalScriptSig = p2sh.input;
845
+ finalScriptSig = p2sh?.input;
843
846
  }
844
847
  }
845
848
  else {
846
849
  if (p2sh) {
847
- finalScriptSig = p2sh.input;
850
+ finalScriptSig = p2sh?.input;
848
851
  }
849
852
  else {
850
- finalScriptSig = payment.input;
853
+ if (!payment) {
854
+ finalScriptSig =
855
+ Array.isArray(solution) && solution[0] ? solution[0] : Buffer.from([0x01]);
856
+ }
857
+ else {
858
+ finalScriptSig = payment.input;
859
+ }
851
860
  }
852
861
  }
853
862
  return {
@@ -1043,6 +1052,15 @@ function getScriptFromInput(inputIndex, input, cache) {
1043
1052
  if (input.witnessScript || isP2WPKH(res.script)) {
1044
1053
  res.isSegwit = true;
1045
1054
  }
1055
+ else {
1056
+ try {
1057
+ const output = res.script;
1058
+ if (!output)
1059
+ throw new TypeError('Invalid script for segwit address');
1060
+ res.isSegwit = isUnknownSegwitVersion(output);
1061
+ }
1062
+ catch (e) { }
1063
+ }
1046
1064
  return res;
1047
1065
  }
1048
1066
  function getSignersFromHD(inputIndex, inputs, hdKeyPair) {
@@ -1167,7 +1185,7 @@ function inputFinalizeGetAmts(inputs, tx, cache, mustFinalize, disableOutputChec
1167
1185
  const fee = inputAmount - outputAmount;
1168
1186
  if (!disableOutputChecks) {
1169
1187
  if (fee < 0) {
1170
- throw new Error('Outputs are spending more than Inputs');
1188
+ throw new Error(`Outputs are spending more than Inputs ${inputAmount} < ${outputAmount}`);
1171
1189
  }
1172
1190
  }
1173
1191
  const bytes = tx.virtualSize();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@btc-vision/bitcoin",
3
3
  "type": "module",
4
- "version": "6.4.5",
4
+ "version": "6.4.6",
5
5
  "description": "Client-side Bitcoin JavaScript library",
6
6
  "engines": {
7
7
  "node": ">=16.0.0"
package/src/address.ts CHANGED
@@ -33,19 +33,39 @@ export interface Bech32Result {
33
33
  data: Buffer;
34
34
  }
35
35
 
36
- const FUTURE_SEGWIT_MAX_SIZE: number = 40;
37
- const FUTURE_SEGWIT_MIN_SIZE: number = 2;
38
- const FUTURE_SEGWIT_MAX_VERSION: number = 15;
39
- const FUTURE_MAX_VERSION: number = 16;
40
- const FUTURE_OPNET_VERSION: number = 16;
41
- const FUTURE_SEGWIT_MIN_VERSION: number = 2;
42
- const FUTURE_SEGWIT_VERSION_DIFF: number = 0x50;
36
+ export const FUTURE_SEGWIT_MAX_SIZE: number = 40;
37
+ export const FUTURE_SEGWIT_MIN_SIZE: number = 2;
38
+ export const FUTURE_SEGWIT_MAX_VERSION: number = 15;
39
+ export const FUTURE_MAX_VERSION: number = 16;
40
+ export const FUTURE_OPNET_VERSION: number = 16;
41
+ export const FUTURE_SEGWIT_MIN_VERSION: number = 2;
42
+ export const FUTURE_SEGWIT_VERSION_DIFF: number = 0x50;
43
43
  const FUTURE_SEGWIT_VERSION_WARNING: string =
44
44
  'WARNING: Sending to a future segwit version address can lead to loss of funds. ' +
45
45
  'End users MUST be warned carefully in the GUI and asked if they wish to proceed ' +
46
46
  'with caution. Wallets should verify the segwit version from the output of fromBech32, ' +
47
47
  'then decide when it is safe to use which version of segwit.';
48
48
 
49
+ export const isUnknownSegwitVersion = (output: Buffer): boolean => {
50
+ try {
51
+ const data = output.subarray(2);
52
+ if (data.length < FUTURE_SEGWIT_MIN_SIZE || data.length > FUTURE_SEGWIT_MAX_SIZE) {
53
+ throw new TypeError('Invalid program length for segwit address');
54
+ }
55
+
56
+ const version = output[0] - FUTURE_SEGWIT_VERSION_DIFF;
57
+ if (version < FUTURE_SEGWIT_MIN_VERSION || version > FUTURE_SEGWIT_MAX_VERSION + 1) {
58
+ throw new TypeError('Invalid version for segwit address');
59
+ }
60
+
61
+ if (version === 1) throw new TypeError('taproot');
62
+
63
+ return true;
64
+ } catch (e) {}
65
+
66
+ return false;
67
+ };
68
+
49
69
  /**
50
70
  * Encode a future Taproot-style segwit address (SegWit v2 - v16) using bech32m.
51
71
  * Only for versions not yet assigned specific meanings (future use).
@@ -92,7 +112,7 @@ export function toFutureOPNetAddress(output: Buffer, network: Network): string {
92
112
  return bech32m.encode(network.bech32Opnet, words);
93
113
  }
94
114
 
95
- function _toFutureSegwitAddress(output: Buffer, network: Network): string {
115
+ export function _toFutureSegwitAddress(output: Buffer, network: Network): string {
96
116
  const data = output.subarray(2);
97
117
  if (data.length < FUTURE_SEGWIT_MIN_SIZE || data.length > FUTURE_SEGWIT_MAX_SIZE) {
98
118
  throw new TypeError('Invalid program length for segwit address');
@@ -1,195 +1,195 @@
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, PaymentType } 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: PaymentType.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 (
57
- !a.address &&
58
- !a.output &&
59
- !a.program &&
60
- (typeof a.deploymentVersion === 'undefined' || !a.hash160)
61
- ) {
62
- throw new TypeError('At least one of address, output or program must be provided');
63
- }
64
-
65
- opts = Object.assign({ validate: true }, opts || {});
66
-
67
- typef(
68
- {
69
- address: typef.maybe(typef.String),
70
- output: typef.maybe(typef.Buffer),
71
- program: typef.maybe(typef.Buffer),
72
- network: typef.maybe(typef.Object),
73
- deploymentVersion: typef.maybe(typef.Number),
74
- hash160: typef.maybe(typef.BufferN(20)),
75
- },
76
- a,
77
- );
78
-
79
- const makeProgramFromParts = (): Buffer | undefined => {
80
- if (typeof a.deploymentVersion !== 'undefined' && typeof a.hash160 !== 'undefined') {
81
- if (a.hash160.length !== 20) throw new TypeError('hash160 must be exactly 20 bytes');
82
- if (a.deploymentVersion < 0 || a.deploymentVersion > 0xff)
83
- throw new TypeError('deploymentVersion must fit in one byte');
84
- return Buffer.concat([Buffer.of(a.deploymentVersion), a.hash160]);
85
- }
86
- return undefined;
87
- };
88
-
89
- const _address = lazy.value(() => fromBech32(a.address!));
90
-
91
- const network: Network = a.network || BITCOIN_NETWORK;
92
- const o: P2OPPayment = {
93
- name: PaymentType.P2OP,
94
- network,
95
- deploymentVersion: 0,
96
- };
97
-
98
- lazy.prop(o, 'program', () => {
99
- if (a.program) return a.program;
100
-
101
- // NEW: build from deploymentVersion+hash160
102
- const fromParts = makeProgramFromParts();
103
- if (fromParts) return fromParts;
104
-
105
- if (a.output) {
106
- if (a.output[0] !== OPS.OP_16) throw new TypeError('Invalid P2OP script');
107
- let pushPos = 1,
108
- progLen: number;
109
- if (a.output[1] < 0x4c) {
110
- progLen = a.output[1];
111
- pushPos = 2;
112
- } else if (a.output[1] === 0x4c) {
113
- progLen = a.output[2];
114
- pushPos = 3;
115
- } else {
116
- throw new TypeError('Unsupported push opcode in P2OP script');
117
- }
118
- return a.output.slice(pushPos, pushPos + progLen);
119
- }
120
-
121
- if (a.address) {
122
- const dec = _address();
123
- return dec.data;
124
- }
125
- });
126
-
127
- lazy.prop(o, 'deploymentVersion', () => {
128
- if (!o.program) return;
129
- return o.program[0];
130
- });
131
-
132
- lazy.prop(o, 'hash160', () => {
133
- if (!o.program) return;
134
- return o.program.slice(1);
135
- });
136
-
137
- lazy.prop(o, 'output', () => {
138
- if (!o.program) return;
139
- return bscript.compile([OPS.OP_16, o.program]);
140
- });
141
-
142
- lazy.prop(o, 'address', () => {
143
- if (!o.program) return;
144
- if (!network.bech32Opnet) {
145
- throw new TypeError('Network does not support opnet');
146
- }
147
-
148
- const words = bech32m.toWords(o.program);
149
- words.unshift(P2OP_WITNESS_VERSION);
150
-
151
- return bech32m.encode(network.bech32Opnet, words);
152
- });
153
-
154
- // extended validation
155
- if (opts.validate) {
156
- let prog: Buffer = NBuffer.alloc(0);
157
-
158
- if (a.address) {
159
- const dec = _address();
160
- if (network.bech32Opnet !== dec.prefix)
161
- throw new TypeError('Invalid prefix or network mismatch');
162
- if (dec.version !== P2OP_WITNESS_VERSION)
163
- throw new TypeError('Invalid witness version for p2op');
164
- if (dec.data.length < MIN_SIZE || dec.data.length > MAX_SIZE)
165
- throw new TypeError('Invalid witness program length');
166
- prog = dec.data;
167
- }
168
-
169
- if (a.program) {
170
- if (prog.length && !prog.equals(a.program)) throw new TypeError('Program mismatch');
171
- prog = a.program;
172
- }
173
-
174
- if (!prog.length && a.deploymentVersion !== undefined && a.hash160) {
175
- prog = makeProgramFromParts()!;
176
- }
177
-
178
- if (a.output) {
179
- const outProg = o.program!;
180
- if (prog.length && !prog.equals(outProg))
181
- throw new TypeError('Program mismatch (output vs other source)');
182
- prog = outProg;
183
- }
184
-
185
- if (prog.length < MIN_SIZE || prog.length > MAX_SIZE)
186
- throw new TypeError(`Witness program must be 2–40 bytes. Was ${prog.length} bytes`);
187
-
188
- if (a.deploymentVersion !== undefined && a.deploymentVersion !== prog[0])
189
- throw new TypeError('deploymentVersion mismatch');
190
-
191
- if (a.hash160 && !a.hash160.equals(prog.slice(1))) throw new TypeError('hash160 mismatch');
192
- }
193
-
194
- return Object.assign(o, a);
195
- }
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, PaymentType } 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: PaymentType.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 (
57
+ !a.address &&
58
+ !a.output &&
59
+ !a.program &&
60
+ (typeof a.deploymentVersion === 'undefined' || !a.hash160)
61
+ ) {
62
+ throw new TypeError('At least one of address, output or program must be provided');
63
+ }
64
+
65
+ opts = Object.assign({ validate: true }, opts || {});
66
+
67
+ typef(
68
+ {
69
+ address: typef.maybe(typef.String),
70
+ output: typef.maybe(typef.Buffer),
71
+ program: typef.maybe(typef.Buffer),
72
+ network: typef.maybe(typef.Object),
73
+ deploymentVersion: typef.maybe(typef.Number),
74
+ hash160: typef.maybe(typef.BufferN(20)),
75
+ },
76
+ a,
77
+ );
78
+
79
+ const makeProgramFromParts = (): Buffer | undefined => {
80
+ if (typeof a.deploymentVersion !== 'undefined' && typeof a.hash160 !== 'undefined') {
81
+ if (a.hash160.length !== 20) throw new TypeError('hash160 must be exactly 20 bytes');
82
+ if (a.deploymentVersion < 0 || a.deploymentVersion > 0xff)
83
+ throw new TypeError('deploymentVersion must fit in one byte');
84
+ return Buffer.concat([Buffer.of(a.deploymentVersion), a.hash160]);
85
+ }
86
+ return undefined;
87
+ };
88
+
89
+ const _address = lazy.value(() => fromBech32(a.address!));
90
+
91
+ const network: Network = a.network || BITCOIN_NETWORK;
92
+ const o: P2OPPayment = {
93
+ name: PaymentType.P2OP,
94
+ network,
95
+ deploymentVersion: 0,
96
+ };
97
+
98
+ lazy.prop(o, 'program', () => {
99
+ if (a.program) return a.program;
100
+
101
+ // NEW: build from deploymentVersion+hash160
102
+ const fromParts = makeProgramFromParts();
103
+ if (fromParts) return fromParts;
104
+
105
+ if (a.output) {
106
+ if (a.output[0] !== OPS.OP_16) throw new TypeError('Invalid P2OP script');
107
+ let pushPos = 1,
108
+ progLen: number;
109
+ if (a.output[1] < 0x4c) {
110
+ progLen = a.output[1];
111
+ pushPos = 2;
112
+ } else if (a.output[1] === 0x4c) {
113
+ progLen = a.output[2];
114
+ pushPos = 3;
115
+ } else {
116
+ throw new TypeError('Unsupported push opcode in P2OP script');
117
+ }
118
+ return a.output.slice(pushPos, pushPos + progLen);
119
+ }
120
+
121
+ if (a.address) {
122
+ const dec = _address();
123
+ return dec.data;
124
+ }
125
+ });
126
+
127
+ lazy.prop(o, 'deploymentVersion', () => {
128
+ if (!o.program) return;
129
+ return o.program[0];
130
+ });
131
+
132
+ lazy.prop(o, 'hash160', () => {
133
+ if (!o.program) return;
134
+ return o.program.slice(1);
135
+ });
136
+
137
+ lazy.prop(o, 'output', () => {
138
+ if (!o.program) return;
139
+ return bscript.compile([OPS.OP_16, o.program]);
140
+ });
141
+
142
+ lazy.prop(o, 'address', () => {
143
+ if (!o.program) return;
144
+ if (!network.bech32Opnet) {
145
+ throw new TypeError('Network does not support opnet');
146
+ }
147
+
148
+ const words = bech32m.toWords(o.program);
149
+ words.unshift(P2OP_WITNESS_VERSION);
150
+
151
+ return bech32m.encode(network.bech32Opnet, words);
152
+ });
153
+
154
+ // extended validation
155
+ if (opts.validate) {
156
+ let prog: Buffer = NBuffer.alloc(0);
157
+
158
+ if (a.address) {
159
+ const dec = _address();
160
+ if (network.bech32Opnet !== dec.prefix)
161
+ throw new TypeError('Invalid prefix or network mismatch');
162
+ if (dec.version !== P2OP_WITNESS_VERSION)
163
+ throw new TypeError('Invalid witness version for p2op');
164
+ if (dec.data.length < MIN_SIZE || dec.data.length > MAX_SIZE)
165
+ throw new TypeError('Invalid witness program length');
166
+ prog = dec.data;
167
+ }
168
+
169
+ if (a.program) {
170
+ if (prog.length && !prog.equals(a.program)) throw new TypeError('Program mismatch');
171
+ prog = a.program;
172
+ }
173
+
174
+ if (!prog.length && a.deploymentVersion !== undefined && a.hash160) {
175
+ prog = makeProgramFromParts()!;
176
+ }
177
+
178
+ if (a.output) {
179
+ const outProg = o.program!;
180
+ if (prog.length && !prog.equals(outProg))
181
+ throw new TypeError('Program mismatch (output vs other source)');
182
+ prog = outProg;
183
+ }
184
+
185
+ if (prog.length < MIN_SIZE || prog.length > MAX_SIZE)
186
+ throw new TypeError(`Witness program must be 2–40 bytes. Was ${prog.length} bytes`);
187
+
188
+ if (a.deploymentVersion !== undefined && a.deploymentVersion !== prog[0])
189
+ throw new TypeError('deploymentVersion mismatch');
190
+
191
+ if (a.hash160 && !a.hash160.equals(prog.slice(1))) throw new TypeError('hash160 mismatch');
192
+ }
193
+
194
+ return Object.assign(o, a);
195
+ }