Add support for pay to anchor outputs

Add support for the newly created Pay2Anchor output-type.

See https://github.com/bitcoin/bitcoin/pull/30352
This commit is contained in:
Erik De Smedt 2025-02-24 12:42:04 +01:00
parent 0157dc21c3
commit f7ea6e50b5
3 changed files with 46 additions and 2 deletions

View File

@ -91,6 +91,8 @@ pub enum AddressType {
P2wsh, P2wsh,
/// Pay to taproot. /// Pay to taproot.
P2tr, P2tr,
/// Pay to anchor.
P2a
} }
impl fmt::Display for AddressType { impl fmt::Display for AddressType {
@ -101,6 +103,7 @@ impl fmt::Display for AddressType {
AddressType::P2wpkh => "p2wpkh", AddressType::P2wpkh => "p2wpkh",
AddressType::P2wsh => "p2wsh", AddressType::P2wsh => "p2wsh",
AddressType::P2tr => "p2tr", AddressType::P2tr => "p2tr",
AddressType::P2a => "p2a",
}) })
} }
} }
@ -114,6 +117,7 @@ impl FromStr for AddressType {
"p2wpkh" => Ok(AddressType::P2wpkh), "p2wpkh" => Ok(AddressType::P2wpkh),
"p2wsh" => Ok(AddressType::P2wsh), "p2wsh" => Ok(AddressType::P2wsh),
"p2tr" => Ok(AddressType::P2tr), "p2tr" => Ok(AddressType::P2tr),
"p2a" => Ok(AddressType::P2a),
_ => Err(UnknownAddressTypeError(s.to_owned())), _ => Err(UnknownAddressTypeError(s.to_owned())),
} }
} }
@ -572,6 +576,8 @@ impl Address {
Some(AddressType::P2wsh) Some(AddressType::P2wsh)
} else if program.is_p2tr() { } else if program.is_p2tr() {
Some(AddressType::P2tr) Some(AddressType::P2tr)
} else if program.is_p2a() {
Some(AddressType::P2a)
} else { } else {
None None
}, },
@ -1526,4 +1532,23 @@ mod tests {
assert_eq!(&rinsed.address, foo_checked.address.as_unchecked()); assert_eq!(&rinsed.address, foo_checked.address.as_unchecked());
assert_eq!(rinsed, foo_unchecked); assert_eq!(rinsed, foo_unchecked);
} }
#[test]
fn pay_to_anchor_address_regtest() {
// Verify that p2a uses the expected address for regtest.
// This test-vector is borrowed from the bitcoin source code.
let address_str = "bcrt1pfeesnyr2tx";
let script = ScriptBuf::new_p2a();
let address_unchecked = address_str.parse().unwrap();
let address = Address::from_script(&script, Network::Regtest).unwrap();
assert_eq!(address.as_unchecked(), &address_unchecked);
assert_eq!(address.to_string(), address_str);
// Verify that the address is considered standard
// and that the output type is P2a
assert!(address.is_spend_standard());
assert_eq!(address.address_type(), Some(AddressType::P2a));
}
} }

View File

@ -12,6 +12,7 @@ use crate::key::{
use crate::opcodes::all::*; use crate::opcodes::all::*;
use crate::script::witness_program::WitnessProgram; use crate::script::witness_program::WitnessProgram;
use crate::script::witness_version::WitnessVersion; use crate::script::witness_version::WitnessVersion;
use crate::script::witness_program::P2A_PROGRAM;
use crate::script::{ use crate::script::{
self, Builder, PushBytes, RedeemScriptSizeError, Script, ScriptBuf, ScriptExt as _, ScriptHash, self, Builder, PushBytes, RedeemScriptSizeError, Script, ScriptBuf, ScriptExt as _, ScriptHash,
WScriptHash, WitnessScriptSizeError, WScriptHash, WitnessScriptSizeError,
@ -170,6 +171,11 @@ define_extension_trait! {
new_witness_program_unchecked(WitnessVersion::V1, output_key.serialize()) new_witness_program_unchecked(WitnessVersion::V1, output_key.serialize())
} }
/// Generates pay to anchor output.
fn new_p2a() -> Self {
new_witness_program_unchecked(WitnessVersion::V1, P2A_PROGRAM)
}
/// Generates P2WSH-type of scriptPubkey with a given [`WitnessProgram`]. /// Generates P2WSH-type of scriptPubkey with a given [`WitnessProgram`].
fn new_witness_program(witness_program: &WitnessProgram) -> Self { fn new_witness_program(witness_program: &WitnessProgram) -> Self {
Builder::new() Builder::new()
@ -183,14 +189,14 @@ define_extension_trait! {
/// Generates P2WSH-type of scriptPubkey with a given [`WitnessVersion`] and the program bytes. /// Generates P2WSH-type of scriptPubkey with a given [`WitnessVersion`] and the program bytes.
/// Does not do any checks on version or program length. /// Does not do any checks on version or program length.
/// ///
/// Convenience method used by `new_p2wpkh`, `new_p2wsh`, `new_p2tr`, and `new_p2tr_tweaked`. /// Convenience method used by `new_p2a`, `new_p2wpkh`, `new_p2wsh`, `new_p2tr`, and `new_p2tr_tweaked`.
pub(super) fn new_witness_program_unchecked<T: AsRef<PushBytes>>( pub(super) fn new_witness_program_unchecked<T: AsRef<PushBytes>>(
version: WitnessVersion, version: WitnessVersion,
program: T, program: T,
) -> ScriptBuf { ) -> ScriptBuf {
let program = program.as_ref(); let program = program.as_ref();
debug_assert!(program.len() >= 2 && program.len() <= 40); debug_assert!(program.len() >= 2 && program.len() <= 40);
// In SegWit v0, the program must be 20 or 32 bytes long. // In SegWit v0, the program must be either 20 (P2WPKH) bytes or 32 (P2WSH) bytes long
debug_assert!(version != WitnessVersion::V0 || program.len() == 20 || program.len() == 32); debug_assert!(version != WitnessVersion::V0 || program.len() == 20 || program.len() == 32);
Builder::new().push_opcode(version.into()).push_slice(program).into_script() Builder::new().push_opcode(version.into()).push_slice(program).into_script()
} }

View File

@ -25,6 +25,9 @@ pub const MIN_SIZE: usize = 2;
/// The maximum byte size of a segregated witness program. /// The maximum byte size of a segregated witness program.
pub const MAX_SIZE: usize = 40; pub const MAX_SIZE: usize = 40;
/// The P2A program which is given by 0x4e73.
pub(crate) const P2A_PROGRAM: [u8;2] = [78, 115];
/// The segregated witness program. /// The segregated witness program.
/// ///
/// The segregated witness program is technically only the program bytes _excluding_ the witness /// The segregated witness program is technically only the program bytes _excluding_ the witness
@ -105,6 +108,11 @@ impl WitnessProgram {
WitnessProgram::new_p2tr(pubkey) WitnessProgram::new_p2tr(pubkey)
} }
/// Constructs a new pay to anchor address
pub const fn p2a() -> Self {
WitnessProgram { version: WitnessVersion::V1, program: ArrayVec::from_slice(&P2A_PROGRAM)}
}
/// Returns the witness program version. /// Returns the witness program version.
pub fn version(&self) -> WitnessVersion { self.version } pub fn version(&self) -> WitnessVersion { self.version }
@ -128,6 +136,11 @@ impl WitnessProgram {
/// Returns true if this witness program is for a P2TR output. /// Returns true if this witness program is for a P2TR output.
pub fn is_p2tr(&self) -> bool { self.version == WitnessVersion::V1 && self.program.len() == 32 } pub fn is_p2tr(&self) -> bool { self.version == WitnessVersion::V1 && self.program.len() == 32 }
/// Returns true if this is a pay to anchor output.
pub fn is_p2a(&self) -> bool {
self.version == WitnessVersion::V1 && self.program == P2A_PROGRAM
}
} }
/// Witness program error. /// Witness program error.