Add Address::from_script constructor
This commit is contained in:
parent
33e8ba3c7e
commit
bfdcfee28e
|
@ -282,6 +282,26 @@ impl Script {
|
|||
self.0[34] == opcodes::all::OP_CHECKSIG.into_u8())
|
||||
}
|
||||
|
||||
/// Checks whether a script pubkey is a Segregated Witness (segwit) program.
|
||||
#[inline]
|
||||
pub fn is_witness_program(&self) -> bool {
|
||||
// A scriptPubKey (or redeemScript as defined in BIP16/P2SH) that consists of a 1-byte
|
||||
// push opcode (for 0 to 16) followed by a data push between 2 and 40 bytes gets a new
|
||||
// special meaning. The value of the first push is called the "version byte". The following
|
||||
// byte vector pushed is called the "witness program".
|
||||
let min_vernum: u8 = opcodes::all::OP_PUSHNUM_1.into_u8();
|
||||
let max_vernum: u8 = opcodes::all::OP_PUSHNUM_16.into_u8();
|
||||
self.0.len() >= 4
|
||||
&& self.0.len() <= 42
|
||||
// Version 0 or PUSHNUM_1-PUSHNUM_16
|
||||
&& (self.0[0] == 0 || self.0[0] >= min_vernum && self.0[0] <= max_vernum)
|
||||
// Second byte push opcode 2-40 bytes
|
||||
&& self.0[1] >= opcodes::all::OP_PUSHBYTES_2.into_u8()
|
||||
&& self.0[1] <= opcodes::all::OP_PUSHBYTES_40.into_u8()
|
||||
// Check that the rest of the script has the correct size
|
||||
&& self.0.len() - 2 == self.0[1] as usize
|
||||
}
|
||||
|
||||
/// Checks whether a script pubkey is a p2wsh output
|
||||
#[inline]
|
||||
pub fn is_v0_p2wsh(&self) -> bool {
|
||||
|
|
|
@ -175,6 +175,62 @@ pub enum Payload {
|
|||
},
|
||||
}
|
||||
|
||||
impl Payload {
|
||||
/// Get a [Payload] from an output script (scriptPubkey).
|
||||
pub fn from_script(script: &script::Script) -> Option<Payload> {
|
||||
Some(if script.is_p2pkh() {
|
||||
Payload::PubkeyHash(Hash::from_slice(&script.as_bytes()[3..23]).unwrap())
|
||||
} else if script.is_p2sh() {
|
||||
Payload::ScriptHash(Hash::from_slice(&script.as_bytes()[2..22]).unwrap())
|
||||
} else if script.is_witness_program() {
|
||||
// We can unwrap the u5 check and assume script length
|
||||
// because [Script::is_witness_program] makes sure of this.
|
||||
Payload::WitnessProgram {
|
||||
version: {
|
||||
// Since we passed the [is_witness_program] check,
|
||||
// the first byte is either 0x00 or 0x50 + version.
|
||||
let mut verop = script.as_bytes()[0];
|
||||
if verop > 0x50 {
|
||||
verop -= 0x50;
|
||||
}
|
||||
bech32::u5::try_from_u8(verop).expect("checked before")
|
||||
},
|
||||
program: script.as_bytes()[2..].to_vec(),
|
||||
}
|
||||
} else {
|
||||
return None;
|
||||
})
|
||||
}
|
||||
|
||||
/// Generates a script pubkey spending to this [Payload].
|
||||
pub fn script_pubkey(&self) -> script::Script {
|
||||
match *self {
|
||||
Payload::PubkeyHash(ref hash) => script::Builder::new()
|
||||
.push_opcode(opcodes::all::OP_DUP)
|
||||
.push_opcode(opcodes::all::OP_HASH160)
|
||||
.push_slice(&hash[..])
|
||||
.push_opcode(opcodes::all::OP_EQUALVERIFY)
|
||||
.push_opcode(opcodes::all::OP_CHECKSIG),
|
||||
Payload::ScriptHash(ref hash) => script::Builder::new()
|
||||
.push_opcode(opcodes::all::OP_HASH160)
|
||||
.push_slice(&hash[..])
|
||||
.push_opcode(opcodes::all::OP_EQUAL),
|
||||
Payload::WitnessProgram {
|
||||
version: ver,
|
||||
program: ref prog,
|
||||
} => {
|
||||
assert!(ver.to_u8() <= 16);
|
||||
let mut verop = ver.to_u8();
|
||||
if verop > 0 {
|
||||
verop = 0x50 + verop;
|
||||
}
|
||||
script::Builder::new().push_opcode(verop.into()).push_slice(&prog)
|
||||
}
|
||||
}
|
||||
.into_script()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
/// A Bitcoin address
|
||||
pub struct Address {
|
||||
|
@ -298,25 +354,17 @@ impl Address {
|
|||
self.address_type().is_some()
|
||||
}
|
||||
|
||||
/// Get an [Address] from an output script (scriptPubkey).
|
||||
pub fn from_script(script: &script::Script, network: Network) -> Option<Address> {
|
||||
Some(Address {
|
||||
payload: Payload::from_script(script)?,
|
||||
network: network,
|
||||
})
|
||||
}
|
||||
|
||||
/// Generates a script pubkey spending to this address
|
||||
pub fn script_pubkey(&self) -> script::Script {
|
||||
match self.payload {
|
||||
Payload::PubkeyHash(ref hash) => script::Builder::new()
|
||||
.push_opcode(opcodes::all::OP_DUP)
|
||||
.push_opcode(opcodes::all::OP_HASH160)
|
||||
.push_slice(&hash[..])
|
||||
.push_opcode(opcodes::all::OP_EQUALVERIFY)
|
||||
.push_opcode(opcodes::all::OP_CHECKSIG),
|
||||
Payload::ScriptHash(ref hash) => script::Builder::new()
|
||||
.push_opcode(opcodes::all::OP_HASH160)
|
||||
.push_slice(&hash[..])
|
||||
.push_opcode(opcodes::all::OP_EQUAL),
|
||||
Payload::WitnessProgram {
|
||||
version: ver,
|
||||
program: ref prog,
|
||||
} => script::Builder::new().push_int(ver.to_u8() as i64).push_slice(&prog),
|
||||
}
|
||||
.into_script()
|
||||
self.payload.script_pubkey()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -481,6 +529,10 @@ mod tests {
|
|||
Address::from_str(&addr.to_string()).unwrap(), *addr,
|
||||
"string round-trip failed for {}", addr,
|
||||
);
|
||||
assert_eq!(
|
||||
Address::from_script(&addr.script_pubkey(), addr.network).as_ref(), Some(addr),
|
||||
"script round-trip failed for {}", addr,
|
||||
);
|
||||
//TODO: add serde roundtrip after no-strason PR
|
||||
}
|
||||
|
||||
|
@ -557,6 +609,21 @@ mod tests {
|
|||
roundtrips(&addr);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_non_existent_segwit_version() {
|
||||
let version = 13;
|
||||
// 40-byte program
|
||||
let program = hex!("654f6ea368e0acdfd92976b7c2103a1b26313f430654f6ea368e0acdfd92976b7c2103a1b26313f4");
|
||||
let addr = Address {
|
||||
payload: Payload::WitnessProgram {
|
||||
version: u5::try_from_u8(version).expect("0<32"),
|
||||
program: program,
|
||||
},
|
||||
network: Network::Bitcoin,
|
||||
};
|
||||
roundtrips(&addr);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bip173_vectors() {
|
||||
let addrstr = "BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4";
|
||||
|
|
Loading…
Reference in New Issue