txscript: add initial awareness of new P2A output script
What changed, and why it matters
This commit adds support in btcd for a new Bitcoin output type called Pay-to-Anchor (P2A). It is a feature addition, not a fix for a known vulnerability. P2A outputs are designed to be 'anyone-can-spend' scripts used for fee-bumping (CPFP). The change itself appears defensive and correct, but because it introduces a new script class and address type, it could affect how wallets and downstream tools classify transactions. There is no direct evidence in the commit or supplied references that this is a security patch or that it addresses an active exploit.
Treat as a normal feature commit. Reviewers should verify that the P2A script bytes match the network-wide standard (0x4e73), that the new address type is handled consistently across the codebase, and that downstream consumers correctly interpret P2A as anyone-can-spend. No urgent security action is indicated by this commit alone.
Security signals we found
New script class added for P2A (Pay-to-Anchor) outputs
P2A outputs are anyone-can-spend by design (0 expected inputs)
No vulnerability disclosure, CVE, or security advisory referenced in commit
Defensive ordering: P2A checked before generic witness v1 taproot classification
Constant duplicated from btcutil to avoid import cycle
Evidence from the diff
The patch extends txscript/standard.go to recognize the P2A output script (OP_1 OP_DATA_2 0x4e73). It adds a new ScriptClass (PayToAnchorTy), detection helpers (IsPayToAnchorScript, extractPayToAnchor), expected input count (0, because anyone can spend), address generation for btcutil.AddressPayToAnchor, and extraction in ExtractPkScriptAddrs. The code comments note that P2A is intentionally duplicated from btcutil to avoid an import cycle. The implementation checks P2A before generic Taproot classification, which is the correct ordering because P2A is a specific witness v1 program. No bug fix, bounds-checking correction, or vulnerability disclosure is present in the diff.
Changed components
txscript/standard.goScriptClass enumerationexpectedInputs functionPayToAddrScript functionExtractPkScriptAddrs functionInspect captured patch +66 / −0
diff --git a/txscript/standard.go b/txscript/standard.go
index ed11a60..6af7b18 100644
--- a/txscript/standard.go
+++ b/txscript/standard.go
@@ -5,6 +5,7 @@
package txscript
import (
+ "bytes"
"fmt"
"github.com/btcsuite/btcd/btcutil"
@@ -50,6 +51,15 @@ const (
ScriptVerifyConstScriptCode
)
+// PayToAnchorScript is the fixed script bytes for a pay-to-anchor output.
+// This is OP_1 OP_DATA_2 0x4e73 (witness v1 with program 0x4e73).
+//
+// The same constant is also defined unexported as
+// btcutil.payToAnchorScript; the duplication is intentional to avoid an
+// import cycle (btcutil cannot import txscript). Keep both definitions in
+// sync.
+var PayToAnchorScript = []byte{OP_1, OP_DATA_2, 0x4e, 0x73}
+
// ScriptClass is an enumeration for the list of standard types of script.
type ScriptClass byte
@@ -64,6 +74,7 @@ const (
MultiSigTy // Multi signature.
NullDataTy // Empty data-only (provably prunable).
WitnessV1TaprootTy // Taproot output
+ PayToAnchorTy // Pay to anchor (P2A)
WitnessUnknownTy // Witness unknown
)
@@ -79,6 +90,7 @@ var scriptClassToName = []string{
MultiSigTy: "multisig",
NullDataTy: "nulldata",
WitnessV1TaprootTy: "witness_v1_taproot",
+ PayToAnchorTy: "anchor",
WitnessUnknownTy: "witness_unknown",
}
@@ -490,6 +502,26 @@ func extractAnnex(witness [][]byte) ([]byte, error) {
return lastElement, nil
}
+// extractPayToAnchor extracts the Pay-to-Anchor data from the passed script
+// if it is a standard pay-to-anchor script. P2A scripts have the format:
+// OP_1 <0x4e73>. It will return nil otherwise.
+func extractPayToAnchor(script []byte) []byte {
+ // P2A script is exactly: OP_1 OP_DATA_2 0x4e 0x73.
+ if bytes.Equal(script, PayToAnchorScript) {
+ // Return the anchor data bytes for valid P2A scripts.
+ return script[2:4]
+ }
+
+ return nil
+}
+
+// IsPayToAnchorScript returns whether or not the passed script is a standard
+// pay-to-anchor (P2A) script. P2A scripts are used for anchor outputs that
+// allow anyone to spend them for CPFP (Child Pays For Parent) fee bumping.
+func IsPayToAnchorScript(script []byte) bool {
+ return extractPayToAnchor(script) != nil
+}
+
// isNullDataScript returns whether or not the passed script is a standard
// null data script.
//
@@ -549,6 +581,9 @@ func typeOfScript(scriptVersion uint16, script []byte) ScriptClass {
}
case TaprootWitnessVersion:
switch {
+ // Check P2A before taproot since P2A is a specific taproot program.
+ case IsPayToAnchorScript(script):
+ return PayToAnchorTy
case isWitnessTaprootScript(script):
return WitnessV1TaprootTy
}
@@ -620,6 +655,10 @@ func expectedInputs(script []byte, class ScriptClass) int {
// Not including script. That is handled by the caller.
return 1
+ case PayToAnchorTy:
+ // P2A outputs require no inputs for spending (anyone can spend).
+ return 0
+
case MultiSigTy:
// Standard multisig has a push a small number for the number
// of sigs and number of keys. Check the first push instruction
@@ -874,6 +913,12 @@ func payToWitnessTaprootScript(rawKey []byte) ([]byte, error) {
return NewScriptBuilder().AddOp(OP_1).AddData(rawKey).Script()
}
+// payToAnchorScript wraps the given 2-byte witness program in the canonical
+// pay-to-anchor envelope (OP_1 OP_DATA_2 <program>).
+func payToAnchorScript(witnessProgram []byte) ([]byte, error) {
+ return NewScriptBuilder().AddOp(OP_1).AddData(witnessProgram).Script()
+}
+
// payToPubkeyScript creates a new script to pay a transaction output to a
// public key. It is expected that the input is a valid pubkey.
func payToPubKeyScript(serializedPubKey []byte) ([]byte, error) {
@@ -926,6 +971,12 @@ func PayToAddrScript(addr btcutil.Address) ([]byte, error) {
nilAddrErrStr)
}
return payToWitnessTaprootScript(addr.ScriptAddress())
+ case *btcutil.AddressPayToAnchor:
+ if addr == nil {
+ return nil, scriptError(ErrUnsupportedAddress,
+ nilAddrErrStr)
+ }
+ return payToAnchorScript(addr.ScriptAddress())
}
str := fmt.Sprintf("unable to generate payment script for unsupported "+
@@ -1062,6 +1113,21 @@ func ExtractPkScriptAddrs(pkScript []byte,
return NullDataTy, nil, 0, nil
}
+ // Check for pay-to-anchor script.
+ if IsPayToAnchorScript(pkScript) {
+ // P2A scripts have a single deterministic bech32 address per
+ // network (e.g. bc1pfeessrawgf on mainnet) and require no
+ // signatures (anyone can spend them).
+ var addrs []btcutil.Address
+ if addr, err := btcutil.NewAddressPayToAnchor(
+ chainParams,
+ ); err == nil {
+
+ addrs = append(addrs, addr)
+ }
+ return PayToAnchorTy, addrs, 0, nil
+ }
+
if hash := extractWitnessPubKeyHash(pkScript); hash != nil {
var addrs []btcutil.Address
addr, err := btcutil.NewAddressWitnessPubKeyHash(hash, chainParams)
Why this scored 35/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.