Add Address::from_script constructor

This commit is contained in:
Steven Roose 2019-04-24 08:02:05 +01:00
parent 33e8ba3c7e
commit bfdcfee28e
No known key found for this signature in database
GPG Key ID: 2F2A88D7F8D68E87
2 changed files with 104 additions and 17 deletions

View File

@ -282,6 +282,26 @@ impl Script {
self.0[34] == opcodes::all::OP_CHECKSIG.into_u8()) 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 /// Checks whether a script pubkey is a p2wsh output
#[inline] #[inline]
pub fn is_v0_p2wsh(&self) -> bool { pub fn is_v0_p2wsh(&self) -> bool {

View File

@ -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)] #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
/// A Bitcoin address /// A Bitcoin address
pub struct Address { pub struct Address {
@ -298,25 +354,17 @@ impl Address {
self.address_type().is_some() 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 /// Generates a script pubkey spending to this address
pub fn script_pubkey(&self) -> script::Script { pub fn script_pubkey(&self) -> script::Script {
match self.payload { self.payload.script_pubkey()
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()
} }
} }
@ -481,6 +529,10 @@ mod tests {
Address::from_str(&addr.to_string()).unwrap(), *addr, Address::from_str(&addr.to_string()).unwrap(), *addr,
"string round-trip failed for {}", 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 //TODO: add serde roundtrip after no-strason PR
} }
@ -557,6 +609,21 @@ mod tests {
roundtrips(&addr); 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] #[test]
fn test_bip173_vectors() { fn test_bip173_vectors() {
let addrstr = "BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4"; let addrstr = "BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4";