@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/address.d.ts +9 -0
- package/browser/index.js +1 -1
- package/browser/psbt.d.ts +2 -2
- package/build/address.d.ts +9 -0
- package/build/address.js +25 -8
- package/build/psbt.d.ts +2 -2
- package/build/psbt.js +27 -9
- package/package.json +1 -1
- package/src/address.ts +28 -8
- package/src/payments/p2op.ts +195 -195
- package/src/psbt.ts +36 -7
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
|
};
|
package/build/address.d.ts
CHANGED
|
@@ -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
|
|
845
|
+
finalScriptSig = p2sh?.input;
|
|
843
846
|
}
|
|
844
847
|
}
|
|
845
848
|
else {
|
|
846
849
|
if (p2sh) {
|
|
847
|
-
finalScriptSig = p2sh
|
|
850
|
+
finalScriptSig = p2sh?.input;
|
|
848
851
|
}
|
|
849
852
|
else {
|
|
850
|
-
|
|
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(
|
|
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
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');
|
package/src/payments/p2op.ts
CHANGED
|
@@ -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
|
+
}
|