Skip to content

Commit 52a01b3

Browse files
committed
bitcoinjs#1552 testing stuff
1 parent cbd0ca4 commit 52a01b3

File tree

8 files changed

+252
-3
lines changed

8 files changed

+252
-3
lines changed
File renamed without changes.

play.js renamed to play/play.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
const bscript = require('./src/script');
1+
const bscript = require('../src/script');
22
const {
33
Transaction
4-
} = require('./src/transaction')
4+
} = require('../src/transaction')
55

66
const hex = '4da5fbaa000000002251206d1cb3f7811934f57a680ce8cabda34527cbcebf218a0fdc833b7a8e7df361ee';
77
const prevout = Buffer.from(hex, 'hex');

play/test-liftx.js

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
const ecpair = require('../src/ecpair')
2+
3+
const x = Buffer.from('79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798', 'hex');
4+
const point = ecpair.liftX(x)
5+
console.log('point', point.toString('hex'))

src/ecpair.js

+32
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,21 @@ Object.defineProperty(exports, '__esModule', { value: true });
33
const NETWORKS = require('./networks');
44
const types = require('./types');
55
const ecc = require('tiny-secp256k1');
6+
const BN = require('bn.js');
67
const randomBytes = require('randombytes');
78
const typeforce = require('typeforce');
89
const wif = require('wif');
10+
const EC_P = new BN(
11+
Buffer.from(
12+
'fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f',
13+
'hex',
14+
),
15+
);
16+
const EC_P_REDUCTION = BN.red(EC_P);
17+
const EC_P_QUADRATIC_RESIDUE = EC_P.addn(1).divn(4);
18+
const BN_2 = new BN(2);
19+
const BN_3 = new BN(3);
20+
const BN_7 = new BN(7);
921
const isOptions = typeforce.maybe(
1022
typeforce.compile({
1123
compressed: types.maybe(types.Boolean),
@@ -71,6 +83,26 @@ function fromPublicKey(buffer, options) {
7183
return new ECPair(undefined, buffer, options);
7284
}
7385
exports.fromPublicKey = fromPublicKey;
86+
function liftX(buffer) {
87+
typeforce(types.Buffer256bit, buffer);
88+
const x = new BN(buffer);
89+
if (x.gte(EC_P)) return null;
90+
const xRed = x.toRed(EC_P_REDUCTION);
91+
const ySq = xRed
92+
.redPow(BN_3)
93+
.add(BN_7)
94+
.mod(EC_P);
95+
const y = ySq.redPow(EC_P_QUADRATIC_RESIDUE);
96+
if (!ySq.eq(y.redPow(BN_2))) {
97+
return null;
98+
}
99+
const y1 = (y & 1) === 0 ? y : EC_P.sub(y);
100+
return Buffer.concat([
101+
Buffer.from(x.toBuffer('be')),
102+
Buffer.from(y1.toBuffer('be')),
103+
]);
104+
}
105+
exports.liftX = liftX;
74106
function fromWIF(wifString, network) {
75107
const decoded = wif.decode(wifString);
76108
const version = decoded.version;

src/payments/p2tr.js

+208
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
'use strict';
2+
Object.defineProperty(exports, '__esModule', { value: true });
3+
const bcrypto = require('../crypto');
4+
const networks_1 = require('../networks');
5+
const bscript = require('../script');
6+
const lazy = require('./lazy');
7+
const typef = require('typeforce');
8+
const OPS = bscript.OPS;
9+
const ecc = require('tiny-secp256k1');
10+
const { bech32, bech32m } = require('bech32');
11+
const EMPTY_BUFFER = Buffer.alloc(0);
12+
function stacksEqual(a, b) {
13+
if (a.length !== b.length) return false;
14+
return a.every((x, i) => {
15+
return x.equals(b[i]);
16+
});
17+
}
18+
function chunkHasUncompressedPubkey(chunk) {
19+
if (
20+
Buffer.isBuffer(chunk) &&
21+
chunk.length === 65 &&
22+
chunk[0] === 0x04 &&
23+
ecc.isPoint(chunk)
24+
) {
25+
return true;
26+
} else {
27+
return false;
28+
}
29+
}
30+
// input: <>
31+
// witness: [redeemScriptSig ...] {redeemScript}
32+
// output: OP_1 {publicKey}
33+
function p2tr(a, opts) {
34+
if (!a.address && !a.hash && !a.output && !a.redeem && !a.witness)
35+
throw new TypeError('Not enough data');
36+
opts = Object.assign({ validate: true }, opts || {});
37+
typef(
38+
{
39+
network: typef.maybe(typef.Object),
40+
address: typef.maybe(typef.String),
41+
hash: typef.maybe(typef.BufferN(32)),
42+
output: typef.maybe(typef.BufferN(34)),
43+
redeem: typef.maybe({
44+
input: typef.maybe(typef.Buffer),
45+
network: typef.maybe(typef.Object),
46+
output: typef.maybe(typef.Buffer),
47+
witness: typef.maybe(typef.arrayOf(typef.Buffer)),
48+
}),
49+
input: typef.maybe(typef.BufferN(0)),
50+
witness: typef.maybe(typef.arrayOf(typef.Buffer)),
51+
},
52+
a,
53+
);
54+
const _address = lazy.value(() => {
55+
const result = bech32m.decode(a.address);
56+
const version = result.words.shift();
57+
const data = bech32m.fromWords(result.words);
58+
return {
59+
version,
60+
prefix: result.prefix,
61+
data: Buffer.from(data),
62+
};
63+
});
64+
const _rchunks = lazy.value(() => {
65+
return bscript.decompile(a.redeem.input);
66+
});
67+
let network = a.network;
68+
if (!network) {
69+
network = (a.redeem && a.redeem.network) || networks_1.bitcoin;
70+
}
71+
const o = { network };
72+
lazy.prop(o, 'address', () => {
73+
if (!o.hash) return;
74+
const words = bech32.toWords(o.hash);
75+
words.unshift(0x00);
76+
return bech32.encode(network.bech32, words);
77+
});
78+
lazy.prop(o, 'hash', () => {
79+
if (a.output) return a.output.slice(2);
80+
if (a.address) return _address().data;
81+
if (o.redeem && o.redeem.output) return bcrypto.sha256(o.redeem.output);
82+
});
83+
lazy.prop(o, 'output', () => {
84+
if (!o.hash) return;
85+
return bscript.compile([OPS.OP_1, o.hash]);
86+
});
87+
lazy.prop(o, 'redeem', () => {
88+
if (!a.witness) return;
89+
return {
90+
output: a.witness[a.witness.length - 1],
91+
input: EMPTY_BUFFER,
92+
witness: a.witness.slice(0, -1),
93+
};
94+
});
95+
lazy.prop(o, 'input', () => {
96+
if (!o.witness) return;
97+
return EMPTY_BUFFER;
98+
});
99+
lazy.prop(o, 'witness', () => {
100+
// transform redeem input to witness stack?
101+
if (
102+
a.redeem &&
103+
a.redeem.input &&
104+
a.redeem.input.length > 0 &&
105+
a.redeem.output &&
106+
a.redeem.output.length > 0
107+
) {
108+
const stack = bscript.toStack(_rchunks());
109+
// assign, and blank the existing input
110+
o.redeem = Object.assign({ witness: stack }, a.redeem);
111+
o.redeem.input = EMPTY_BUFFER;
112+
return [].concat(stack, a.redeem.output);
113+
}
114+
if (!a.redeem) return;
115+
if (!a.redeem.output) return;
116+
if (!a.redeem.witness) return;
117+
return [].concat(a.redeem.witness, a.redeem.output);
118+
});
119+
lazy.prop(o, 'name', () => {
120+
const nameParts = ['p2wsh'];
121+
if (o.redeem !== undefined && o.redeem.name !== undefined)
122+
nameParts.push(o.redeem.name);
123+
return nameParts.join('-');
124+
});
125+
// extended validation
126+
if (opts.validate) {
127+
let hash = Buffer.from([]);
128+
if (a.address) {
129+
if (_address().prefix !== network.bech32)
130+
throw new TypeError('Invalid prefix or Network mismatch');
131+
if (_address().version !== 0x00)
132+
throw new TypeError('Invalid address version');
133+
if (_address().data.length !== 32)
134+
throw new TypeError('Invalid address data');
135+
hash = _address().data;
136+
}
137+
if (a.hash) {
138+
if (hash.length > 0 && !hash.equals(a.hash))
139+
throw new TypeError('Hash mismatch');
140+
else hash = a.hash;
141+
}
142+
if (a.output) {
143+
if (
144+
a.output.length !== 34 ||
145+
a.output[0] !== OPS.OP_0 ||
146+
a.output[1] !== 0x20
147+
)
148+
throw new TypeError('Output is invalid');
149+
const hash2 = a.output.slice(2);
150+
if (hash.length > 0 && !hash.equals(hash2))
151+
throw new TypeError('Hash mismatch');
152+
else hash = hash2;
153+
}
154+
if (a.redeem) {
155+
if (a.redeem.network && a.redeem.network !== network)
156+
throw new TypeError('Network mismatch');
157+
// is there two redeem sources?
158+
if (
159+
a.redeem.input &&
160+
a.redeem.input.length > 0 &&
161+
a.redeem.witness &&
162+
a.redeem.witness.length > 0
163+
)
164+
throw new TypeError('Ambiguous witness source');
165+
// is the redeem output non-empty?
166+
if (a.redeem.output) {
167+
if (bscript.decompile(a.redeem.output).length === 0)
168+
throw new TypeError('Redeem.output is invalid');
169+
// match hash against other sources
170+
const hash2 = bcrypto.sha256(a.redeem.output);
171+
if (hash.length > 0 && !hash.equals(hash2))
172+
throw new TypeError('Hash mismatch');
173+
else hash = hash2;
174+
}
175+
if (a.redeem.input && !bscript.isPushOnly(_rchunks()))
176+
throw new TypeError('Non push-only scriptSig');
177+
if (
178+
a.witness &&
179+
a.redeem.witness &&
180+
!stacksEqual(a.witness, a.redeem.witness)
181+
)
182+
throw new TypeError('Witness and redeem.witness mismatch');
183+
if (
184+
(a.redeem.input && _rchunks().some(chunkHasUncompressedPubkey)) ||
185+
(a.redeem.output &&
186+
(bscript.decompile(a.redeem.output) || []).some(
187+
chunkHasUncompressedPubkey,
188+
))
189+
) {
190+
throw new TypeError(
191+
'redeem.input or redeem.output contains uncompressed pubkey',
192+
);
193+
}
194+
}
195+
if (a.witness && a.witness.length > 0) {
196+
const wScript = a.witness[a.witness.length - 1];
197+
if (a.redeem && a.redeem.output && !a.redeem.output.equals(wScript))
198+
throw new TypeError('Witness and redeem.output mismatch');
199+
if (
200+
a.witness.some(chunkHasUncompressedPubkey) ||
201+
(bscript.decompile(wScript) || []).some(chunkHasUncompressedPubkey)
202+
)
203+
throw new TypeError('Witness contains uncompressed pubkey');
204+
}
205+
}
206+
return Object.assign(o, a);
207+
}
208+
exports.p2tr = p2tr;

types/ecpair.d.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ declare class ECPair implements ECPairInterface {
3939
}
4040
declare function fromPrivateKey(buffer: Buffer, options?: ECPairOptions): ECPair;
4141
declare function fromPublicKey(buffer: Buffer, options?: ECPairOptions): ECPair;
42+
declare function liftX(buffer: Buffer): Buffer | null;
4243
declare function fromWIF(wifString: string, network?: Network | Network[]): ECPair;
4344
declare function makeRandom(options?: ECPairOptions): ECPair;
44-
export { makeRandom, fromPrivateKey, fromPublicKey, fromWIF };
45+
export { makeRandom, fromPrivateKey, fromPublicKey, fromWIF, liftX };

types/payments/index.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export interface Payment {
2121
address?: string;
2222
hash?: Buffer;
2323
redeem?: Payment;
24+
taprootRedeems?: Payment[];
2425
witness?: Buffer[];
2526
}
2627
export declare type PaymentCreator = (a: Payment, opts?: PaymentOpts) => Payment;

types/payments/p2tr.d.ts

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
import { Payment, PaymentOpts } from './index';
2+
export declare function p2tr(a: Payment, opts?: PaymentOpts): Payment;

0 commit comments

Comments
 (0)