Merge pull request #1573 from bitcoinjs/fixSegwitPayments

Throw errors when p2wsh or p2wpkh contain uncompressed pubkeys.
This commit is contained in:
Jonathan Underwood 2020-05-21 13:17:10 +09:00 committed by GitHub
commit 85ee2a3c5d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 127 additions and 19 deletions

View file

@ -1,3 +1,13 @@
# 5.1.8
__fixed__
- Throw errors when p2wsh or p2wpkh contain uncompressed pubkeys (#1573)
__added__
- Add txInputs and txOutputs for Psbt (#1561)
__changed__
- (Not exposed) Added BufferWriter to help ease maintenance of certain forks of this library (#1533)
# 5.1.7 # 5.1.7
__fixed__ __fixed__
- Fixed Transaction class Output interface typing for TypeScript (#1506) - Fixed Transaction class Output interface typing for TypeScript (#1506)

2
package-lock.json generated
View file

@ -1,6 +1,6 @@
{ {
"name": "bitcoinjs-lib", "name": "bitcoinjs-lib",
"version": "5.1.7", "version": "5.1.8",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {

View file

@ -1,6 +1,6 @@
{ {
"name": "bitcoinjs-lib", "name": "bitcoinjs-lib",
"version": "5.1.7", "version": "5.1.8",
"description": "Client-side Bitcoin JavaScript library", "description": "Client-side Bitcoin JavaScript library",
"main": "./src/index.js", "main": "./src/index.js",
"types": "./types/index.d.ts", "types": "./types/index.d.ts",

View file

@ -107,12 +107,14 @@ function p2wpkh(a, opts) {
if (hash.length > 0 && !hash.equals(pkh)) if (hash.length > 0 && !hash.equals(pkh))
throw new TypeError('Hash mismatch'); throw new TypeError('Hash mismatch');
else hash = pkh; else hash = pkh;
if (!ecc.isPoint(a.pubkey) || a.pubkey.length !== 33)
throw new TypeError('Invalid pubkey for p2wpkh');
} }
if (a.witness) { if (a.witness) {
if (a.witness.length !== 2) throw new TypeError('Witness is invalid'); if (a.witness.length !== 2) throw new TypeError('Witness is invalid');
if (!bscript.isCanonicalScriptSignature(a.witness[0])) if (!bscript.isCanonicalScriptSignature(a.witness[0]))
throw new TypeError('Witness has invalid signature'); throw new TypeError('Witness has invalid signature');
if (!ecc.isPoint(a.witness[1])) if (!ecc.isPoint(a.witness[1]) || a.witness[1].length !== 33)
throw new TypeError('Witness has invalid pubkey'); throw new TypeError('Witness has invalid pubkey');
if (a.signature && !a.signature.equals(a.witness[0])) if (a.signature && !a.signature.equals(a.witness[0]))
throw new TypeError('Signature mismatch'); throw new TypeError('Signature mismatch');

View file

@ -6,6 +6,7 @@ const bscript = require('../script');
const lazy = require('./lazy'); const lazy = require('./lazy');
const typef = require('typeforce'); const typef = require('typeforce');
const OPS = bscript.OPS; const OPS = bscript.OPS;
const ecc = require('tiny-secp256k1');
const bech32 = require('bech32'); const bech32 = require('bech32');
const EMPTY_BUFFER = Buffer.alloc(0); const EMPTY_BUFFER = Buffer.alloc(0);
function stacksEqual(a, b) { function stacksEqual(a, b) {
@ -14,6 +15,18 @@ function stacksEqual(a, b) {
return x.equals(b[i]); return x.equals(b[i]);
}); });
} }
function chunkHasUncompressedPubkey(chunk) {
if (
Buffer.isBuffer(chunk) &&
chunk.length === 65 &&
chunk[0] === 0x04 &&
ecc.isPoint(chunk)
) {
return true;
} else {
return false;
}
}
// input: <> // input: <>
// witness: [redeemScriptSig ...] {redeemScript} // witness: [redeemScriptSig ...] {redeemScript}
// output: OP_0 {sha256(redeemScript)} // output: OP_0 {sha256(redeemScript)}
@ -166,14 +179,27 @@ function p2wsh(a, opts) {
!stacksEqual(a.witness, a.redeem.witness) !stacksEqual(a.witness, a.redeem.witness)
) )
throw new TypeError('Witness and redeem.witness mismatch'); throw new TypeError('Witness and redeem.witness mismatch');
}
if (a.witness) {
if ( if (
a.redeem && (a.redeem.input && _rchunks().some(chunkHasUncompressedPubkey)) ||
a.redeem.output && (a.redeem.output &&
!a.redeem.output.equals(a.witness[a.witness.length - 1]) (bscript.decompile(a.redeem.output) || []).some(
) chunkHasUncompressedPubkey,
))
) {
throw new TypeError(
'redeem.input or redeem.output contains uncompressed pubkey',
);
}
}
if (a.witness && a.witness.length > 0) {
const wScript = a.witness[a.witness.length - 1];
if (a.redeem && a.redeem.output && !a.redeem.output.equals(wScript))
throw new TypeError('Witness and redeem.output mismatch'); throw new TypeError('Witness and redeem.output mismatch');
if (
a.witness.some(chunkHasUncompressedPubkey) ||
(bscript.decompile(wScript) || []).some(chunkHasUncompressedPubkey)
)
throw new TypeError('Witness contains uncompressed pubkey');
} }
} }
return Object.assign(o, a); return Object.assign(o, a);

View file

@ -113,6 +113,23 @@
"output": "OP_RESERVED ea6d525c0c955d90d3dbd29a81ef8bfb79003727" "output": "OP_RESERVED ea6d525c0c955d90d3dbd29a81ef8bfb79003727"
} }
}, },
{
"exception": "Invalid pubkey for p2wpkh",
"description": "Uncompressed pubkey in pubkey",
"arguments": {
"pubkey": "049fa211b0fca342589ca381cc96520c3d0e3924832158d9f29891936cac091e80687acdca51868ee1f89a3bb36bb16f186262938e1f94c1e7692924935b9b1457"
}
},
{
"exception": "Witness has invalid pubkey",
"description": "Uncompressed pubkey in witness",
"arguments": {
"witness": [
"300602010002010001",
"049fa211b0fca342589ca381cc96520c3d0e3924832158d9f29891936cac091e80687acdca51868ee1f89a3bb36bb16f186262938e1f94c1e7692924935b9b1457"
]
}
},
{ {
"exception": "Pubkey mismatch", "exception": "Pubkey mismatch",
"options": {}, "options": {},

View file

@ -344,6 +344,30 @@
} }
} }
}, },
{
"exception": "redeem.input or redeem.output contains uncompressed pubkey",
"arguments": {
"redeem": {
"output": "049fa211b0fca342589ca381cc96520c3d0e3924832158d9f29891936cac091e80687acdca51868ee1f89a3bb36bb16f186262938e1f94c1e7692924935b9b1457 OP_CHECKSIG"
}
}
},
{
"exception": "redeem.input or redeem.output contains uncompressed pubkey",
"arguments": {
"redeem": {
"input": "049fa211b0fca342589ca381cc96520c3d0e3924832158d9f29891936cac091e80687acdca51868ee1f89a3bb36bb16f186262938e1f94c1e7692924935b9b1457"
}
}
},
{
"exception": "Witness contains uncompressed pubkey",
"arguments": {
"witness": [
"049fa211b0fca342589ca381cc96520c3d0e3924832158d9f29891936cac091e80687acdca51868ee1f89a3bb36bb16f186262938e1f94c1e7692924935b9b1457"
]
}
},
{ {
"exception": "Invalid prefix or Network mismatch", "exception": "Invalid prefix or Network mismatch",
"arguments": { "arguments": {

View file

@ -2122,7 +2122,7 @@
}, },
{ {
"description": "Transaction w/ P2WSH(P2PK), signing with uncompressed public key", "description": "Transaction w/ P2WSH(P2PK), signing with uncompressed public key",
"exception": "BIP143 rejects uncompressed public keys in P2WPKH or P2WSH", "exception": "redeem.input or redeem.output contains uncompressed pubkey",
"inputs": [ "inputs": [
{ {
"txId": "2fddebc1a7e67e04fc6b77645ae9ae10eeaa35e168606587d79b031ebca33345", "txId": "2fddebc1a7e67e04fc6b77645ae9ae10eeaa35e168606587d79b031ebca33345",
@ -2148,7 +2148,7 @@
}, },
{ {
"description": "Transaction w/ P2SH(P2WSH(P2PK)), signing with uncompressed public key", "description": "Transaction w/ P2SH(P2WSH(P2PK)), signing with uncompressed public key",
"exception": "BIP143 rejects uncompressed public keys in P2WPKH or P2WSH", "exception": "redeem.input or redeem.output contains uncompressed pubkey",
"inputs": [ "inputs": [
{ {
"txId": "2fddebc1a7e67e04fc6b77645ae9ae10eeaa35e168606587d79b031ebca33345", "txId": "2fddebc1a7e67e04fc6b77645ae9ae10eeaa35e168606587d79b031ebca33345",

View file

@ -118,13 +118,15 @@ export function p2wpkh(a: Payment, opts?: PaymentOpts): Payment {
if (hash.length > 0 && !hash.equals(pkh)) if (hash.length > 0 && !hash.equals(pkh))
throw new TypeError('Hash mismatch'); throw new TypeError('Hash mismatch');
else hash = pkh; else hash = pkh;
if (!ecc.isPoint(a.pubkey) || a.pubkey.length !== 33)
throw new TypeError('Invalid pubkey for p2wpkh');
} }
if (a.witness) { if (a.witness) {
if (a.witness.length !== 2) throw new TypeError('Witness is invalid'); if (a.witness.length !== 2) throw new TypeError('Witness is invalid');
if (!bscript.isCanonicalScriptSignature(a.witness[0])) if (!bscript.isCanonicalScriptSignature(a.witness[0]))
throw new TypeError('Witness has invalid signature'); throw new TypeError('Witness has invalid signature');
if (!ecc.isPoint(a.witness[1])) if (!ecc.isPoint(a.witness[1]) || a.witness[1].length !== 33)
throw new TypeError('Witness has invalid pubkey'); throw new TypeError('Witness has invalid pubkey');
if (a.signature && !a.signature.equals(a.witness[0])) if (a.signature && !a.signature.equals(a.witness[0]))

View file

@ -1,10 +1,11 @@
import * as bcrypto from '../crypto'; import * as bcrypto from '../crypto';
import { bitcoin as BITCOIN_NETWORK } from '../networks'; import { bitcoin as BITCOIN_NETWORK } from '../networks';
import * as bscript from '../script'; import * as bscript from '../script';
import { Payment, PaymentOpts, StackFunction } from './index'; import { Payment, PaymentOpts, StackElement, StackFunction } from './index';
import * as lazy from './lazy'; import * as lazy from './lazy';
const typef = require('typeforce'); const typef = require('typeforce');
const OPS = bscript.OPS; const OPS = bscript.OPS;
const ecc = require('tiny-secp256k1');
const bech32 = require('bech32'); const bech32 = require('bech32');
@ -18,6 +19,19 @@ function stacksEqual(a: Buffer[], b: Buffer[]): boolean {
}); });
} }
function chunkHasUncompressedPubkey(chunk: StackElement): boolean {
if (
Buffer.isBuffer(chunk) &&
chunk.length === 65 &&
chunk[0] === 0x04 &&
ecc.isPoint(chunk)
) {
return true;
} else {
return false;
}
}
// input: <> // input: <>
// witness: [redeemScriptSig ...] {redeemScript} // witness: [redeemScriptSig ...] {redeemScript}
// output: OP_0 {sha256(redeemScript)} // output: OP_0 {sha256(redeemScript)}
@ -187,15 +201,28 @@ export function p2wsh(a: Payment, opts?: PaymentOpts): Payment {
!stacksEqual(a.witness, a.redeem.witness) !stacksEqual(a.witness, a.redeem.witness)
) )
throw new TypeError('Witness and redeem.witness mismatch'); throw new TypeError('Witness and redeem.witness mismatch');
if (
(a.redeem.input && _rchunks().some(chunkHasUncompressedPubkey)) ||
(a.redeem.output &&
(bscript.decompile(a.redeem.output) || []).some(
chunkHasUncompressedPubkey,
))
) {
throw new TypeError(
'redeem.input or redeem.output contains uncompressed pubkey',
);
}
} }
if (a.witness) { if (a.witness && a.witness.length > 0) {
if ( const wScript = a.witness[a.witness.length - 1];
a.redeem && if (a.redeem && a.redeem.output && !a.redeem.output.equals(wScript))
a.redeem.output &&
!a.redeem.output.equals(a.witness[a.witness.length - 1])
)
throw new TypeError('Witness and redeem.output mismatch'); throw new TypeError('Witness and redeem.output mismatch');
if (
a.witness.some(chunkHasUncompressedPubkey) ||
(bscript.decompile(wScript) || []).some(chunkHasUncompressedPubkey)
)
throw new TypeError('Witness contains uncompressed pubkey');
} }
} }