Skip to content

Commit 85ee2a3

Browse files
authored
Merge pull request #1573 from bitcoinjs/fixSegwitPayments
Throw errors when p2wsh or p2wpkh contain uncompressed pubkeys.
2 parents 4eb698d + bb89297 commit 85ee2a3

File tree

10 files changed

+125
-17
lines changed

10 files changed

+125
-17
lines changed

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
1+
# 5.1.8
2+
__fixed__
3+
- Throw errors when p2wsh or p2wpkh contain uncompressed pubkeys (#1573)
4+
5+
__added__
6+
- Add txInputs and txOutputs for Psbt (#1561)
7+
8+
__changed__
9+
- (Not exposed) Added BufferWriter to help ease maintenance of certain forks of this library (#1533)
10+
111
# 5.1.7
212
__fixed__
313
- Fixed Transaction class Output interface typing for TypeScript (#1506)

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "bitcoinjs-lib",
3-
"version": "5.1.7",
3+
"version": "5.1.8",
44
"description": "Client-side Bitcoin JavaScript library",
55
"main": "./src/index.js",
66
"types": "./types/index.d.ts",

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: 31 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,18 @@ function stacksEqual(a, b) {
1415
return x.equals(b[i]);
1516
});
1617
}
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+
}
1730
// input: <>
1831
// witness: [redeemScriptSig ...] {redeemScript}
1932
// output: OP_0 {sha256(redeemScript)}
@@ -166,14 +179,27 @@ function p2wsh(a, opts) {
166179
!stacksEqual(a.witness, a.redeem.witness)
167180
)
168181
throw new TypeError('Witness and redeem.witness mismatch');
182+
if (
183+
(a.redeem.input && _rchunks().some(chunkHasUncompressedPubkey)) ||
184+
(a.redeem.output &&
185+
(bscript.decompile(a.redeem.output) || []).some(
186+
chunkHasUncompressedPubkey,
187+
))
188+
) {
189+
throw new TypeError(
190+
'redeem.input or redeem.output contains uncompressed pubkey',
191+
);
192+
}
169193
}
170-
if (a.witness) {
194+
if (a.witness && a.witness.length > 0) {
195+
const wScript = a.witness[a.witness.length - 1];
196+
if (a.redeem && a.redeem.output && !a.redeem.output.equals(wScript))
197+
throw new TypeError('Witness and redeem.output mismatch');
171198
if (
172-
a.redeem &&
173-
a.redeem.output &&
174-
!a.redeem.output.equals(a.witness[a.witness.length - 1])
199+
a.witness.some(chunkHasUncompressedPubkey) ||
200+
(bscript.decompile(wScript) || []).some(chunkHasUncompressedPubkey)
175201
)
176-
throw new TypeError('Witness and redeem.output mismatch');
202+
throw new TypeError('Witness contains uncompressed pubkey');
177203
}
178204
}
179205
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: 33 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,19 @@ function stacksEqual(a: Buffer[], b: Buffer[]): boolean {
1819
});
1920
}
2021

22+
function chunkHasUncompressedPubkey(chunk: StackElement): boolean {
23+
if (
24+
Buffer.isBuffer(chunk) &&
25+
chunk.length === 65 &&
26+
chunk[0] === 0x04 &&
27+
ecc.isPoint(chunk)
28+
) {
29+
return true;
30+
} else {
31+
return false;
32+
}
33+
}
34+
2135
// input: <>
2236
// witness: [redeemScriptSig ...] {redeemScript}
2337
// output: OP_0 {sha256(redeemScript)}
@@ -187,15 +201,28 @@ export function p2wsh(a: Payment, opts?: PaymentOpts): Payment {
187201
!stacksEqual(a.witness, a.redeem.witness)
188202
)
189203
throw new TypeError('Witness and redeem.witness mismatch');
204+
if (
205+
(a.redeem.input && _rchunks().some(chunkHasUncompressedPubkey)) ||
206+
(a.redeem.output &&
207+
(bscript.decompile(a.redeem.output) || []).some(
208+
chunkHasUncompressedPubkey,
209+
))
210+
) {
211+
throw new TypeError(
212+
'redeem.input or redeem.output contains uncompressed pubkey',
213+
);
214+
}
190215
}
191216

192-
if (a.witness) {
217+
if (a.witness && a.witness.length > 0) {
218+
const wScript = a.witness[a.witness.length - 1];
219+
if (a.redeem && a.redeem.output && !a.redeem.output.equals(wScript))
220+
throw new TypeError('Witness and redeem.output mismatch');
193221
if (
194-
a.redeem &&
195-
a.redeem.output &&
196-
!a.redeem.output.equals(a.witness[a.witness.length - 1])
222+
a.witness.some(chunkHasUncompressedPubkey) ||
223+
(bscript.decompile(wScript) || []).some(chunkHasUncompressedPubkey)
197224
)
198-
throw new TypeError('Witness and redeem.output mismatch');
225+
throw new TypeError('Witness contains uncompressed pubkey');
199226
}
200227
}
201228

0 commit comments

Comments
 (0)