txscript: handle variable length P2PKH signatures in ComputePkScript

Since P2PKH signatures have variable lengths, we would attempt to parse
P2PKH scripts as P2SH if they didn't fit the previous length
constraints.
This commit is contained in:
Wilmer Paulino 2019-05-30 12:29:45 -07:00
parent 16327141da
commit 545bc5d474
No known key found for this signature in database
GPG key ID: 6DF57B9F9514972F
3 changed files with 40 additions and 26 deletions

View file

@ -85,10 +85,10 @@ func (sig *Signature) IsEqual(otherSig *Signature) bool {
sig.S.Cmp(otherSig.S) == 0 sig.S.Cmp(otherSig.S) == 0
} }
// minSigLen is the minimum length of a DER encoded signature and is // MinSigLen is the minimum length of a DER encoded signature and is when both R
// when both R and S are 1 byte each. // and S are 1 byte each.
// 0x30 + <1-byte> + 0x02 + 0x01 + <byte> + 0x2 + 0x01 + <byte> // 0x30 + <1-byte> + 0x02 + 0x01 + <byte> + 0x2 + 0x01 + <byte>
const minSigLen = 8 const MinSigLen = 8
func parseSig(sigStr []byte, curve elliptic.Curve, der bool) (*Signature, error) { func parseSig(sigStr []byte, curve elliptic.Curve, der bool) (*Signature, error) {
// Originally this code used encoding/asn1 in order to parse the // Originally this code used encoding/asn1 in order to parse the
@ -103,7 +103,7 @@ func parseSig(sigStr []byte, curve elliptic.Curve, der bool) (*Signature, error)
signature := &Signature{} signature := &Signature{}
if len(sigStr) < minSigLen { if len(sigStr) < MinSigLen {
return nil, errors.New("malformed signature: too short") return nil, errors.New("malformed signature: too short")
} }
// 0x30 // 0x30
@ -118,7 +118,7 @@ func parseSig(sigStr []byte, curve elliptic.Curve, der bool) (*Signature, error)
// siglen should be less than the entire message and greater than // siglen should be less than the entire message and greater than
// the minimal message size. // the minimal message size.
if int(siglen+2) > len(sigStr) || int(siglen+2) < minSigLen { if int(siglen+2) > len(sigStr) || int(siglen+2) < MinSigLen {
return nil, errors.New("malformed signature: bad length") return nil, errors.New("malformed signature: bad length")
} }
// trim the slice we're working on so we only look at what matters. // trim the slice we're working on so we only look at what matters.

View file

@ -13,13 +13,23 @@ import (
) )
const ( const (
// pubKeyHashSigScriptLen is the length of a signature script attempting // minPubKeyHashSigScriptLen is the minimum length of a signature script
// to spend a P2PKH script. The only other possible length value is 107 // that spends a P2PKH output. The length is composed of the following:
// bytes, due to the signature within it. This length is determined by // Signature length (1 byte)
// the following: // Signature (min 8 bytes)
// 0x47 or 0x48 (71 or 72 byte data push) | <71 or 72 byte sig> | // Signature hash type (1 byte)
// 0x21 (33 byte data push) | <33 byte compressed pubkey> // Public key length (1 byte)
pubKeyHashSigScriptLen = 106 // Public key (33 byte)
minPubKeyHashSigScriptLen = 1 + btcec.MinSigLen + 1 + 1 + 33
// maxPubKeyHashSigScriptLen is the maximum length of a signature script
// that spends a P2PKH output. The length is composed of the following:
// Signature length (1 byte)
// Signature (max 72 bytes)
// Signature hash type (1 byte)
// Public key length (1 byte)
// Public key (33 byte)
maxPubKeyHashSigScriptLen = 1 + 72 + 1 + 1 + 33
// compressedPubKeyLen is the length in bytes of a compressed public // compressedPubKeyLen is the length in bytes of a compressed public
// key. // key.
@ -161,11 +171,19 @@ func ComputePkScript(sigScript []byte, witness wire.TxWitness) (PkScript, error)
// We'll start by checking the input's signature script, if provided. // We'll start by checking the input's signature script, if provided.
switch { switch {
case len(sigScript) == 0:
break
// Since we only support P2PKH and P2SH scripts as the only non-witness
// script types, we should expect to see a push only script.
case !IsPushOnlyScript(sigScript):
return pkScript, ErrUnsupportedScriptType
// If a signature script is provided with a length long enough to // If a signature script is provided with a length long enough to
// represent a P2PKH script, then we'll attempt to parse the compressed // represent a P2PKH script, then we'll attempt to parse the compressed
// public key from it. // public key from it.
case len(sigScript) == pubKeyHashSigScriptLen || case len(sigScript) >= minPubKeyHashSigScriptLen &&
len(sigScript) == pubKeyHashSigScriptLen+1: len(sigScript) <= maxPubKeyHashSigScriptLen:
// The public key should be found as the last part of the // The public key should be found as the last part of the
// signature script. We'll attempt to parse it to ensure this is // signature script. We'll attempt to parse it to ensure this is
@ -183,14 +201,12 @@ func ComputePkScript(sigScript []byte, witness wire.TxWitness) (PkScript, error)
return pkScript, nil return pkScript, nil
} }
// If it isn't, we'll assume it is a P2SH signature script.
fallthrough fallthrough
// If we failed to parse a compressed public key from the script in the // If we failed to parse a compressed public key from the script in the
// case above, or if the script length is not that of a P2PKH one, and // case above, or if the script length is not that of a P2PKH one, we
// our redeem script is only composed of data pushed, we can assume it's // can assume it's a P2SH signature script.
// a P2SH signature script. default:
case len(sigScript) > 0 && IsPushOnlyScript(sigScript):
// The redeem script will always be the last data push of the // The redeem script will always be the last data push of the
// signature script, so we'll parse the script into opcodes to // signature script, so we'll parse the script into opcodes to
// obtain it. // obtain it.
@ -209,9 +225,6 @@ func ComputePkScript(sigScript []byte, witness wire.TxWitness) (PkScript, error)
pkScript.class = ScriptHashTy pkScript.class = ScriptHashTy
copy(pkScript.script[:], script) copy(pkScript.script[:], script)
return pkScript, nil return pkScript, nil
case len(sigScript) > 0:
return pkScript, ErrUnsupportedScriptType
} }
// If a witness was provided instead, we'll use the last item of the // If a witness was provided instead, we'll use the last item of the

View file

@ -208,9 +208,9 @@ func TestComputePkScript(t *testing.T) {
{ {
name: "P2PKH sigScript", name: "P2PKH sigScript",
sigScript: []byte{ sigScript: []byte{
// OP_DATA_71, // OP_DATA_73,
0x47, 0x49,
// <71-byte sig> // <73-byte sig>
0x30, 0x44, 0x02, 0x20, 0x65, 0x92, 0xd8, 0x8e, 0x30, 0x44, 0x02, 0x20, 0x65, 0x92, 0xd8, 0x8e,
0x1d, 0x0a, 0x4a, 0x3c, 0xc5, 0x9f, 0x92, 0xae, 0x1d, 0x0a, 0x4a, 0x3c, 0xc5, 0x9f, 0x92, 0xae,
0xfe, 0x62, 0x54, 0x74, 0xa9, 0x4d, 0x13, 0xa5, 0xfe, 0x62, 0x54, 0x74, 0xa9, 0x4d, 0x13, 0xa5,
@ -219,7 +219,8 @@ func TestComputePkScript(t *testing.T) {
0x36, 0x96, 0x19, 0x1f, 0xb7, 0x00, 0xc5, 0xa7, 0x36, 0x96, 0x19, 0x1f, 0xb7, 0x00, 0xc5, 0xa7,
0x7e, 0x22, 0xd9, 0xfb, 0x6b, 0x42, 0x67, 0x42, 0x7e, 0x22, 0xd9, 0xfb, 0x6b, 0x42, 0x67, 0x42,
0xa4, 0x2c, 0xac, 0xdb, 0x74, 0xa2, 0x7c, 0x43, 0xa4, 0x2c, 0xac, 0xdb, 0x74, 0xa2, 0x7c, 0x43,
0xcd, 0x89, 0xa0, 0xf9, 0x44, 0x54, 0x01, 0xcd, 0x89, 0xa0, 0xf9, 0x44, 0x54, 0x12, 0x74,
0x01,
// OP_DATA_33 // OP_DATA_33
0x21, 0x21,
// <33-byte compressed pubkey> // <33-byte compressed pubkey>