Skip to content

Commit 63772e5

Browse files
committed
txscript: use finalOpcodeData for nested P2SH redeem script extraction
Both NewEngine and GetWitnessSigOpCount used sigScript[1:] (a raw byte suffix of the scriptSig) to identify the candidate redeem script for nested P2SH witness detection. GetPreciseSigOpCount and the existing P2SH execution path already get the redeem script as the final pushed element of the push-only scriptSig (via finalOpcodeData and savedFirstStack[len-1] respectively). This commit consolidates the nested-witness detection sites to use finalOpcodeData(0, scriptSig) so all three places agree on what the redeem script is. As a side effect of using the actual redeem script for detection, the len(witness) != 0 precondition in NewEngine becomes redundant — the redeem script's shape alone determines whether the spend is nested witness, and verifyWitnessProgram already enforces the required witness-stack shape downstream.
1 parent af7f379 commit 63772e5

2 files changed

Lines changed: 93 additions & 55 deletions

File tree

txscript/engine.go

Lines changed: 87 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -542,6 +542,21 @@ func (vm *Engine) isWitnessVersionActive(version uint) bool {
542542
return vm.witnessProgram != nil && uint(vm.witnessVersion) == version
543543
}
544544

545+
// witnessProgramAcceptStack collapses the data stack down to a single element
546+
// for witness programs that succeed without inner script execution. The
547+
// running pkScript must have left a truthy top item; the stack is then reduced
548+
// to one element so the cleanstack accounting is consistent regardless of how
549+
// many items the outer pkScript template pushed.
550+
func (vm *Engine) witnessProgramAcceptStack() error {
551+
topIdx := len(vm.dstack.stk) - 1
552+
if topIdx < 0 || !asBool(vm.dstack.stk[topIdx]) {
553+
return scriptError(ErrEvalFalse,
554+
"witness program produced false result")
555+
}
556+
vm.dstack.stk = vm.dstack.stk[:1]
557+
return nil
558+
}
559+
545560
// verifyWitnessProgram validates the stored witness program using the passed
546561
// witness as input.
547562
func (vm *Engine) verifyWitnessProgram(witness wire.TxWitness) error {
@@ -786,11 +801,9 @@ func (vm *Engine) verifyWitnessProgram(witness wire.TxWitness) error {
786801

787802
return scriptError(ErrDiscourageUpgradableWitnessProgram, errStr)
788803
default:
789-
// If we encounter an unknown witness program version and we
790-
// aren't discouraging future unknown witness based soft-forks,
791-
// then we de-activate the segwit behavior within the VM for
792-
// the remainder of execution.
793-
vm.witnessProgram = nil
804+
if err := vm.witnessProgramAcceptStack(); err != nil {
805+
return err
806+
}
794807
}
795808

796809
// TODO(roasbeef): other sanity checks here
@@ -1479,6 +1492,71 @@ func (vm *Engine) SetAltStack(data [][]byte) {
14791492
setStack(&vm.astack, data)
14801493
}
14811494

1495+
// buildWitnessProgram detects native and nested P2SH witness programs for the
1496+
// current input, records the extracted witness version/program on the engine,
1497+
// and rejects stray witness data on non-witness spends.
1498+
func (vm *Engine) buildWitnessProgram(scriptSig, scriptPubKey []byte,
1499+
hasWitness bool) error {
1500+
1501+
var witProgram []byte
1502+
1503+
switch {
1504+
case IsWitnessProgram(scriptPubKey):
1505+
// The scriptSig must be *empty* for all native witness programs,
1506+
// otherwise we introduce malleability.
1507+
if len(scriptSig) != 0 {
1508+
errStr := "native witness program cannot also have a signature " +
1509+
"script"
1510+
return scriptError(ErrWitnessMalleated, errStr)
1511+
}
1512+
1513+
witProgram = scriptPubKey
1514+
1515+
case vm.bip16:
1516+
// Mirror Bitcoin Core's nested-P2SH-witness detection: the candidate
1517+
// witness program is the actual redeem script, which is the final
1518+
// pushed element of the push-only scriptSig. Detection must be
1519+
// independent of whether the witness stack is empty.
1520+
if len(scriptSig) == 0 || !IsPushOnlyScript(scriptSig) {
1521+
break
1522+
}
1523+
redeem := finalOpcodeData(0, scriptSig)
1524+
if len(redeem) == 0 || !IsWitnessProgram(redeem) {
1525+
break
1526+
}
1527+
1528+
// scriptSig must be exactly one canonical push of the redeem script;
1529+
// otherwise we reintroduce malleability.
1530+
canonical, err := NewScriptBuilder().AddData(redeem).Script()
1531+
if err != nil || !bytes.Equal(scriptSig, canonical) {
1532+
errStr := "signature script for witness nested p2sh is not " +
1533+
"canonical"
1534+
return scriptError(ErrWitnessMalleatedP2SH, errStr)
1535+
}
1536+
1537+
witProgram = redeem
1538+
}
1539+
1540+
if witProgram == nil {
1541+
// If we didn't find a witness program in either the pkScript or as a
1542+
// datapush within the sigScript, then there MUST NOT be any witness
1543+
// data associated with the input being validated.
1544+
if hasWitness {
1545+
errStr := "non-witness inputs cannot have a witness"
1546+
return scriptError(ErrWitnessUnexpected, errStr)
1547+
}
1548+
1549+
return nil
1550+
}
1551+
1552+
var err error
1553+
vm.witnessVersion, vm.witnessProgram, err = ExtractWitnessProgramInfo(
1554+
witProgram,
1555+
)
1556+
1557+
return err
1558+
}
1559+
14821560
// NewEngine returns a new script engine for the provided public key script,
14831561
// transaction, and input index. The flags modify the behavior of the script
14841562
// engine according to the description provided by each flag.
@@ -1592,55 +1670,11 @@ func NewEngine(scriptPubKey []byte, tx *wire.MsgTx, txIdx int, flags ScriptFlags
15921670
return nil, scriptError(ErrInvalidFlags, errStr)
15931671
}
15941672

1595-
var witProgram []byte
1596-
1597-
switch {
1598-
case IsWitnessProgram(vm.scripts[1]):
1599-
// The scriptSig must be *empty* for all native witness
1600-
// programs, otherwise we introduce malleability.
1601-
if len(scriptSig) != 0 {
1602-
errStr := "native witness program cannot " +
1603-
"also have a signature script"
1604-
return nil, scriptError(ErrWitnessMalleated, errStr)
1605-
}
1606-
1607-
witProgram = scriptPubKey
1608-
case len(tx.TxIn[txIdx].Witness) != 0 && vm.bip16:
1609-
// The sigScript MUST be *exactly* a single canonical
1610-
// data push of the witness program, otherwise we
1611-
// reintroduce malleability.
1612-
sigPops := vm.scripts[0]
1613-
if len(sigPops) > 2 &&
1614-
isCanonicalPush(sigPops[0], sigPops[1:]) &&
1615-
IsWitnessProgram(sigPops[1:]) {
1616-
1617-
witProgram = sigPops[1:]
1618-
} else {
1619-
errStr := "signature script for witness " +
1620-
"nested p2sh is not canonical"
1621-
return nil, scriptError(ErrWitnessMalleatedP2SH, errStr)
1622-
}
1623-
}
1624-
1625-
if witProgram != nil {
1626-
var err error
1627-
vm.witnessVersion, vm.witnessProgram, err = ExtractWitnessProgramInfo(
1628-
witProgram,
1629-
)
1630-
if err != nil {
1631-
return nil, err
1632-
}
1633-
} else {
1634-
// If we didn't find a witness program in either the
1635-
// pkScript or as a datapush within the sigScript, then
1636-
// there MUST NOT be any witness data associated with
1637-
// the input being validated.
1638-
if vm.witnessProgram == nil && len(tx.TxIn[txIdx].Witness) != 0 {
1639-
errStr := "non-witness inputs cannot have a witness"
1640-
return nil, scriptError(ErrWitnessUnexpected, errStr)
1641-
}
1673+
hasWitness := len(tx.TxIn[txIdx].Witness) != 0
1674+
err := vm.buildWitnessProgram(scriptSig, scriptPubKey, hasWitness)
1675+
if err != nil {
1676+
return nil, err
16421677
}
1643-
16441678
}
16451679

16461680
// Setup the current tokenizer used to parse through the script one opcode

txscript/script.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -468,8 +468,12 @@ func GetWitnessSigOpCount(sigScript, pkScript []byte, witness wire.TxWitness) in
468468
// witness program. This is a case wherein the sigScript is actually a
469469
// datapush of a p2wsh witness program.
470470
if isScriptHashScript(pkScript) && IsPushOnlyScript(sigScript) &&
471-
len(sigScript) > 0 && isWitnessProgramScript(sigScript[1:]) {
472-
return getWitnessSigOps(sigScript[1:], witness)
471+
len(sigScript) > 0 {
472+
473+
redeem := finalOpcodeData(0, sigScript)
474+
if len(redeem) > 0 && isWitnessProgramScript(redeem) {
475+
return getWitnessSigOps(redeem, witness)
476+
}
473477
}
474478

475479
return 0

0 commit comments

Comments
 (0)