Skip to content

Commit ee91afc

Browse files
committed
txscript: refactor CalcScriptInfo for nested P2SH analysis
Extract the nested P2SH analysis from CalcScriptInfo into a dedicated calcP2SHScriptInfo helper, and switch it to use finalOpcodeData for redeem-script extraction (matching the consolidation in NewEngine and GetWitnessSigOpCount). The helper also threads the inner witness-script expected-input contribution for nested P2WSH spends, which the previous inline code did not account for.
1 parent 63772e5 commit ee91afc

1 file changed

Lines changed: 86 additions & 35 deletions

File tree

txscript/standard.go

Lines changed: 86 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -656,6 +656,87 @@ type ScriptInfo struct {
656656
SigOps int
657657
}
658658

659+
// calcP2SHScriptInfo returns ScriptInfo fields for spends whose pkScript class
660+
// is pay-to-script-hash. It handles both cases hidden behind P2SH:
661+
// - ordinary legacy P2SH, where sigScript reveals a redeem script that executes
662+
// directly.
663+
// - nested witness spends, where sigScript reveals a redeem script that is
664+
// itself a witness program.
665+
//
666+
// The caller has already initialized si.ExpectedInputs from the outer pkScript,
667+
// so this helper is responsible for folding in any additional inputs required
668+
// by the redeem script layer and, for nested P2WSH, the inner witness script
669+
// layer as well.
670+
func calcP2SHScriptInfo(scriptVersion uint16, sigScript, pkScript []byte,
671+
witness wire.TxWitness, numInputs, expectedInputCount int,
672+
segwit bool) (int, int, int) {
673+
674+
// For P2SH, the consensus-visible redeem script is the final data push in
675+
// sigScript. This is the script that will ultimately execute for legacy
676+
// P2SH, and it is also the script we must inspect to decide whether the
677+
// spend is actually nested witness.
678+
redeemScript := finalOpcodeData(scriptVersion, sigScript)
679+
680+
// Classify the revealed redeem script so we can account for both its script
681+
// shape and the number of stack elements it expects from the spending
682+
// input.
683+
redeemClass := typeOfScript(scriptVersion, redeemScript)
684+
685+
// Fold the redeem-script layer into ExpectedInputs. A return value of -1
686+
// means the helper cannot determine the count for this script shape, so the
687+
// entire overall ExpectedInputs value becomes unknown as well.
688+
rsInputs := expectedInputs(redeemScript, redeemClass)
689+
if rsInputs == -1 {
690+
expectedInputCount = -1
691+
} else {
692+
expectedInputCount += rsInputs
693+
}
694+
695+
// If segwit is active and the actual redeem script is itself a witness
696+
// program, then this is a nested witness spend such as P2SH-P2WPKH or
697+
// P2SH-P2WSH. The key point is that this decision is based on the actual
698+
// redeem script revealed by sigScript.
699+
if segwit && IsWitnessProgram(redeemScript) {
700+
// Nested P2WSH adds one more layer of indirection: the witness program
701+
// commits to a witness script, and the final witness element carries
702+
// the serialized witness script itself. That inner script can require
703+
// its own stack arguments, so ExpectedInputs must include them too.
704+
if redeemClass == WitnessV0ScriptHashTy && len(witness) > 0 {
705+
// In P2WSH, the last witness item is the witness script being
706+
// executed.
707+
witnessScript := witness[len(witness)-1]
708+
709+
// Classify the inner witness script and ask how many stack elements
710+
// it expects from the preceding witness items.
711+
witnessScriptClass := typeOfScript(scriptVersion, witnessScript)
712+
wsInputs := expectedInputs(witnessScript, witnessScriptClass)
713+
714+
// Preserve the same unknown-count behavior as above: once any inner
715+
// layer reports an unknown expected-input count, the overall answer
716+
// is unknown. Otherwise, add the inner witness-script contribution
717+
// on top of the already-counted outer P2SH and redeem-script
718+
// layers.
719+
if wsInputs == -1 {
720+
expectedInputCount = -1
721+
} else if expectedInputCount != -1 {
722+
expectedInputCount += wsInputs
723+
}
724+
}
725+
726+
// Once the spend is known to be nested witness, sigops must be counted
727+
// with witness-aware rules, and the provided input items are the total
728+
// of the pushed sigScript items plus the witness stack items.
729+
sigOps := GetWitnessSigOpCount(sigScript, pkScript, witness)
730+
return expectedInputCount, sigOps, len(witness) + numInputs
731+
}
732+
733+
// Otherwise this is ordinary legacy P2SH. The revealed redeem script
734+
// executes directly, so sigops are counted from that script using legacy
735+
// rules, and the only provided inputs are the pushed items from sigScript.
736+
sigOps := countSigOpsV0(redeemScript, true)
737+
return expectedInputCount, sigOps, numInputs
738+
}
739+
659740
// CalcScriptInfo returns a structure providing data about the provided script
660741
// pair. It will error if the pair is in someway invalid such that they can not
661742
// be analysed, i.e. if they do not parse or the pkScript is not a push-only
@@ -700,21 +781,11 @@ func CalcScriptInfo(sigScript, pkScript []byte, witness wire.TxWitness,
700781

701782
switch {
702783
// Count sigops taking into account pay-to-script-hash.
703-
case si.PkScriptClass == ScriptHashTy && bip16 && !segwit:
704-
// The redeem script is the final data push of the signature script.
705-
redeemScript := finalOpcodeData(scriptVersion, sigScript)
706-
reedeemClass := typeOfScript(scriptVersion, redeemScript)
707-
rsInputs := expectedInputs(redeemScript, reedeemClass)
708-
if rsInputs == -1 {
709-
si.ExpectedInputs = -1
710-
} else {
711-
si.ExpectedInputs += rsInputs
712-
}
713-
si.SigOps = countSigOpsV0(redeemScript, true)
714-
715-
// All entries pushed to stack (or are OP_RESERVED and exec
716-
// will fail).
717-
si.NumInputs = numInputs
784+
case si.PkScriptClass == ScriptHashTy && bip16:
785+
si.ExpectedInputs, si.SigOps, si.NumInputs = calcP2SHScriptInfo(
786+
scriptVersion, sigScript, pkScript, witness, numInputs,
787+
si.ExpectedInputs, segwit,
788+
)
718789

719790
// If segwit is active, and this is a regular p2wkh output, then we'll
720791
// treat the script as a p2pkh output in essence.
@@ -723,26 +794,6 @@ func CalcScriptInfo(sigScript, pkScript []byte, witness wire.TxWitness,
723794
si.SigOps = GetWitnessSigOpCount(sigScript, pkScript, witness)
724795
si.NumInputs = len(witness)
725796

726-
// We'll attempt to detect the nested p2sh case so we can accurately
727-
// count the signature operations involved.
728-
case si.PkScriptClass == ScriptHashTy &&
729-
IsWitnessProgram(sigScript[1:]) && bip16 && segwit:
730-
731-
// Extract the pushed witness program from the sigScript so we
732-
// can determine the number of expected inputs.
733-
redeemClass := typeOfScript(scriptVersion, sigScript[1:])
734-
shInputs := expectedInputs(sigScript[1:], redeemClass)
735-
if shInputs == -1 {
736-
si.ExpectedInputs = -1
737-
} else {
738-
si.ExpectedInputs += shInputs
739-
}
740-
741-
si.SigOps = GetWitnessSigOpCount(sigScript, pkScript, witness)
742-
743-
si.NumInputs = len(witness)
744-
si.NumInputs += numInputs
745-
746797
// If segwit is active, and this is a p2wsh output, then we'll need to
747798
// examine the witness script to generate accurate script info.
748799
case si.PkScriptClass == WitnessV0ScriptHashTy && segwit:

0 commit comments

Comments
 (0)