Skip to content

Commit 25b5806

Browse files
committed
Throw errors when p2wsh or p2wpkh contain uncompressed pubkeys.
This will enforce BIP143 compressed pubkey rules on an address generation level.
1 parent 4eb698d commit 25b5806

File tree

7 files changed

+105
-15
lines changed

7 files changed

+105
-15
lines changed

src/payments/p2wpkh.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,12 +107,14 @@ function p2wpkh(a, opts) {
107107
if (hash.length > 0 && !hash.equals(pkh))
108108
throw new TypeError('Hash mismatch');
109109
else hash = pkh;
110+
if (!ecc.isPoint(a.pubkey) || a.pubkey.length !== 33)
111+
throw new TypeError('Invalid pubkey for p2wpkh');
110112
}
111113
if (a.witness) {
112114
if (a.witness.length !== 2) throw new TypeError('Witness is invalid');
113115
if (!bscript.isCanonicalScriptSignature(a.witness[0]))
114116
throw new TypeError('Witness has invalid signature');
115-
if (!ecc.isPoint(a.witness[1]))
117+
if (!ecc.isPoint(a.witness[1]) || a.witness[1].length !== 33)
116118
throw new TypeError('Witness has invalid pubkey');
117119
if (a.signature && !a.signature.equals(a.witness[0]))
118120
throw new TypeError('Signature mismatch');

src/payments/p2wsh.js

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ const bscript = require('../script');
66
const lazy = require('./lazy');
77
const typef = require('typeforce');
88
const OPS = bscript.OPS;
9+
const ecc = require('tiny-secp256k1');
910
const bech32 = require('bech32');
1011
const EMPTY_BUFFER = Buffer.alloc(0);
1112
function stacksEqual(a, b) {
@@ -14,6 +15,14 @@ function stacksEqual(a, b) {
1415
return x.equals(b[i]);
1516
});
1617
}
18+
function chunkHasUncompressedPubkey(chunk) {
19+
if (Buffer.isBuffer(chunk) && chunk.length === 65) {
20+
if (ecc.isPoint(chunk)) return true;
21+
else return false;
22+
} else {
23+
return false;
24+
}
25+
}
1726
// input: <>
1827
// witness: [redeemScriptSig ...] {redeemScript}
1928
// output: OP_0 {sha256(redeemScript)}
@@ -51,6 +60,9 @@ function p2wsh(a, opts) {
5160
const _rchunks = lazy.value(() => {
5261
return bscript.decompile(a.redeem.input);
5362
});
63+
const _rochunks = lazy.value(() => {
64+
return bscript.decompile(a.redeem.output);
65+
});
5466
let network = a.network;
5567
if (!network) {
5668
network = (a.redeem && a.redeem.network) || networks_1.bitcoin;
@@ -166,14 +178,24 @@ function p2wsh(a, opts) {
166178
!stacksEqual(a.witness, a.redeem.witness)
167179
)
168180
throw new TypeError('Witness and redeem.witness mismatch');
181+
if (
182+
(a.redeem.input && _rchunks().some(chunkHasUncompressedPubkey)) ||
183+
(a.redeem.output && _rochunks().some(chunkHasUncompressedPubkey))
184+
) {
185+
throw new TypeError(
186+
'redeem.input or redeem.output contains uncompressed pubkey',
187+
);
188+
}
169189
}
170-
if (a.witness) {
190+
if (a.witness && a.witness.length > 0) {
191+
const wScript = a.witness[a.witness.length - 1];
192+
if (a.redeem && a.redeem.output && !a.redeem.output.equals(wScript))
193+
throw new TypeError('Witness and redeem.output mismatch');
171194
if (
172-
a.redeem &&
173-
a.redeem.output &&
174-
!a.redeem.output.equals(a.witness[a.witness.length - 1])
195+
a.witness.some(chunkHasUncompressedPubkey) ||
196+
(bscript.decompile(wScript) || []).some(chunkHasUncompressedPubkey)
175197
)
176-
throw new TypeError('Witness and redeem.output mismatch');
198+
throw new TypeError('Witness contains uncompressed pubkey');
177199
}
178200
}
179201
return Object.assign(o, a);

test/fixtures/p2wpkh.json

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,23 @@
113113
"output": "OP_RESERVED ea6d525c0c955d90d3dbd29a81ef8bfb79003727"
114114
}
115115
},
116+
{
117+
"exception": "Invalid pubkey for p2wpkh",
118+
"description": "Uncompressed pubkey in pubkey",
119+
"arguments": {
120+
"pubkey": "049fa211b0fca342589ca381cc96520c3d0e3924832158d9f29891936cac091e80687acdca51868ee1f89a3bb36bb16f186262938e1f94c1e7692924935b9b1457"
121+
}
122+
},
123+
{
124+
"exception": "Witness has invalid pubkey",
125+
"description": "Uncompressed pubkey in witness",
126+
"arguments": {
127+
"witness": [
128+
"300602010002010001",
129+
"049fa211b0fca342589ca381cc96520c3d0e3924832158d9f29891936cac091e80687acdca51868ee1f89a3bb36bb16f186262938e1f94c1e7692924935b9b1457"
130+
]
131+
}
132+
},
116133
{
117134
"exception": "Pubkey mismatch",
118135
"options": {},

test/fixtures/p2wsh.json

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,30 @@
344344
}
345345
}
346346
},
347+
{
348+
"exception": "redeem.input or redeem.output contains uncompressed pubkey",
349+
"arguments": {
350+
"redeem": {
351+
"output": "049fa211b0fca342589ca381cc96520c3d0e3924832158d9f29891936cac091e80687acdca51868ee1f89a3bb36bb16f186262938e1f94c1e7692924935b9b1457 OP_CHECKSIG"
352+
}
353+
}
354+
},
355+
{
356+
"exception": "redeem.input or redeem.output contains uncompressed pubkey",
357+
"arguments": {
358+
"redeem": {
359+
"input": "049fa211b0fca342589ca381cc96520c3d0e3924832158d9f29891936cac091e80687acdca51868ee1f89a3bb36bb16f186262938e1f94c1e7692924935b9b1457"
360+
}
361+
}
362+
},
363+
{
364+
"exception": "Witness contains uncompressed pubkey",
365+
"arguments": {
366+
"witness": [
367+
"049fa211b0fca342589ca381cc96520c3d0e3924832158d9f29891936cac091e80687acdca51868ee1f89a3bb36bb16f186262938e1f94c1e7692924935b9b1457"
368+
]
369+
}
370+
},
347371
{
348372
"exception": "Invalid prefix or Network mismatch",
349373
"arguments": {

test/fixtures/transaction_builder.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2122,7 +2122,7 @@
21222122
},
21232123
{
21242124
"description": "Transaction w/ P2WSH(P2PK), signing with uncompressed public key",
2125-
"exception": "BIP143 rejects uncompressed public keys in P2WPKH or P2WSH",
2125+
"exception": "redeem.input or redeem.output contains uncompressed pubkey",
21262126
"inputs": [
21272127
{
21282128
"txId": "2fddebc1a7e67e04fc6b77645ae9ae10eeaa35e168606587d79b031ebca33345",
@@ -2148,7 +2148,7 @@
21482148
},
21492149
{
21502150
"description": "Transaction w/ P2SH(P2WSH(P2PK)), signing with uncompressed public key",
2151-
"exception": "BIP143 rejects uncompressed public keys in P2WPKH or P2WSH",
2151+
"exception": "redeem.input or redeem.output contains uncompressed pubkey",
21522152
"inputs": [
21532153
{
21542154
"txId": "2fddebc1a7e67e04fc6b77645ae9ae10eeaa35e168606587d79b031ebca33345",

ts_src/payments/p2wpkh.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,13 +118,15 @@ export function p2wpkh(a: Payment, opts?: PaymentOpts): Payment {
118118
if (hash.length > 0 && !hash.equals(pkh))
119119
throw new TypeError('Hash mismatch');
120120
else hash = pkh;
121+
if (!ecc.isPoint(a.pubkey) || a.pubkey.length !== 33)
122+
throw new TypeError('Invalid pubkey for p2wpkh');
121123
}
122124

123125
if (a.witness) {
124126
if (a.witness.length !== 2) throw new TypeError('Witness is invalid');
125127
if (!bscript.isCanonicalScriptSignature(a.witness[0]))
126128
throw new TypeError('Witness has invalid signature');
127-
if (!ecc.isPoint(a.witness[1]))
129+
if (!ecc.isPoint(a.witness[1]) || a.witness[1].length !== 33)
128130
throw new TypeError('Witness has invalid pubkey');
129131

130132
if (a.signature && !a.signature.equals(a.witness[0]))

ts_src/payments/p2wsh.ts

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import * as bcrypto from '../crypto';
22
import { bitcoin as BITCOIN_NETWORK } from '../networks';
33
import * as bscript from '../script';
4-
import { Payment, PaymentOpts, StackFunction } from './index';
4+
import { Payment, PaymentOpts, StackElement, StackFunction } from './index';
55
import * as lazy from './lazy';
66
const typef = require('typeforce');
77
const OPS = bscript.OPS;
8+
const ecc = require('tiny-secp256k1');
89

910
const bech32 = require('bech32');
1011

@@ -18,6 +19,15 @@ function stacksEqual(a: Buffer[], b: Buffer[]): boolean {
1819
});
1920
}
2021

22+
function chunkHasUncompressedPubkey(chunk: StackElement): boolean {
23+
if (Buffer.isBuffer(chunk) && chunk.length === 65) {
24+
if (ecc.isPoint(chunk)) return true;
25+
else return false;
26+
} else {
27+
return false;
28+
}
29+
}
30+
2131
// input: <>
2232
// witness: [redeemScriptSig ...] {redeemScript}
2333
// output: OP_0 {sha256(redeemScript)}
@@ -59,6 +69,9 @@ export function p2wsh(a: Payment, opts?: PaymentOpts): Payment {
5969
const _rchunks = lazy.value(() => {
6070
return bscript.decompile(a.redeem!.input!);
6171
}) as StackFunction;
72+
const _rochunks = lazy.value(() => {
73+
return bscript.decompile(a.redeem!.output!);
74+
}) as StackFunction;
6275

6376
let network = a.network;
6477
if (!network) {
@@ -187,15 +200,25 @@ export function p2wsh(a: Payment, opts?: PaymentOpts): Payment {
187200
!stacksEqual(a.witness, a.redeem.witness)
188201
)
189202
throw new TypeError('Witness and redeem.witness mismatch');
203+
if (
204+
(a.redeem.input && _rchunks().some(chunkHasUncompressedPubkey)) ||
205+
(a.redeem.output && _rochunks().some(chunkHasUncompressedPubkey))
206+
) {
207+
throw new TypeError(
208+
'redeem.input or redeem.output contains uncompressed pubkey',
209+
);
210+
}
190211
}
191212

192-
if (a.witness) {
213+
if (a.witness && a.witness.length > 0) {
214+
const wScript = a.witness[a.witness.length - 1];
215+
if (a.redeem && a.redeem.output && !a.redeem.output.equals(wScript))
216+
throw new TypeError('Witness and redeem.output mismatch');
193217
if (
194-
a.redeem &&
195-
a.redeem.output &&
196-
!a.redeem.output.equals(a.witness[a.witness.length - 1])
218+
a.witness.some(chunkHasUncompressedPubkey) ||
219+
(bscript.decompile(wScript) || []).some(chunkHasUncompressedPubkey)
197220
)
198-
throw new TypeError('Witness and redeem.output mismatch');
221+
throw new TypeError('Witness contains uncompressed pubkey');
199222
}
200223
}
201224

0 commit comments

Comments
 (0)