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())
|
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 {
|
||||||
|
|
|
@ -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";
|
||||||
|
|
Loading…
Reference in New Issue