From 16e2a3519b8966dbce45bc9fcd28bf3c0321d5d3 Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Wed, 14 Oct 2015 15:29:19 -0500 Subject: [PATCH] Implement basic contract-hash support Does not do stuff like validating the form of contracts, since this seems like more of an application thing. Does not even distinguish a "nonce", just assumes the contract has whatever uniqueness is needed baked in. --- Cargo.toml | 4 +- src/blockdata/script.rs | 12 ++- src/util/address.rs | 180 +++++++++++++++++++++++++++++++++++++++ src/util/contracthash.rs | 154 +++++++++++++++++++++++++++++++++ src/util/hash.rs | 21 ++++- src/util/mod.rs | 2 + 6 files changed, 369 insertions(+), 4 deletions(-) create mode 100644 src/util/address.rs create mode 100644 src/util/contracthash.rs diff --git a/Cargo.toml b/Cargo.toml index 0179366..fd50efd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "bitcoin" -version = "0.3.0" +version = "0.3.1" authors = ["Andrew Poelstra "] license = "CC0-1.0" homepage = "https://github.com/apoelstra/rust-bitcoin/" @@ -24,7 +24,7 @@ num_cpus = "0.2" rand = "0.3" rust-crypto = "0.2" rustc-serialize = "0.3" -secp256k1 = "0.2" +secp256k1 = "0.3" serde = "0.6" serde_json = "0.6" time = "0.1" diff --git a/src/blockdata/script.rs b/src/blockdata/script.rs index e0cce15..021cf6f 100644 --- a/src/blockdata/script.rs +++ b/src/blockdata/script.rs @@ -27,7 +27,7 @@ use std::hash; use std::char::from_digit; use std::default::Default; -use std::ops; +use std::{fmt, ops}; use serialize::hex::ToHex; use crypto::digest::Digest; @@ -55,6 +55,16 @@ impl Clone for Script { } } +impl fmt::LowerHex for Script { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + try!(f.write_str("Script(")); + for &ch in self.0.iter() { + try!(write!(f, "{:02x}", ch)); + } + f.write_str(")") + } +} + #[derive(PartialEq, Eq, Debug, Clone)] /// An object which can be used to construct a script piece by piece pub struct Builder(Vec); diff --git a/src/util/address.rs b/src/util/address.rs new file mode 100644 index 0000000..8aa4839 --- /dev/null +++ b/src/util/address.rs @@ -0,0 +1,180 @@ +// Rust Bitcoin Library +// Written in 2014 by +// Andrew Poelstra +// To the extent possible under law, the author(s) have dedicated all +// copyright and related and neighboring rights to this software to +// the public domain worldwide. This software is distributed without +// any warranty. +// +// You should have received a copy of the CC0 Public Domain Dedication +// along with this software. +// If not, see . +// + +//! # Addresses +//! +//! Support for ordinary base58 Bitcoin addresses +//! + +use secp256k1::Secp256k1; +use secp256k1::key::PublicKey; + +use blockdata::script; +use blockdata::opcodes; +use network::constants::Network; +use util::hash::Hash160; +use util::base58::{self, FromBase58, ToBase58}; + +/// The method used to produce an address +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub enum Type { + /// Standard pay-to-pkhash address + PubkeyHash, + /// New-fangled P2SH address + ScriptHash +} + +#[derive(Clone, PartialEq, Eq)] +/// A Bitcoin address +pub struct Address { + /// The type of the address + pub ty: Type, + /// The network on which this address is usable + pub network: Network, + /// The pubkeyhash that this address encodes + pub hash: Hash160 +} + +impl Address { + /// Creates an address from a public key + #[inline] + pub fn from_key(network: Network, pk: &PublicKey, compressed: bool) -> Address { + let secp = Secp256k1::without_caps(); + Address { + ty: Type::PubkeyHash, + network: network, + hash: Hash160::from_data(&pk.serialize_vec(&secp, compressed)[..]) + } + } + + /// Generates a script pubkey spending to this address + #[inline] + pub fn script_pubkey(&self) -> script::Script { + let mut script = script::Builder::new(); + match self.ty { + Type::PubkeyHash => { + script.push_opcode(opcodes::All::OP_DUP); + script.push_opcode(opcodes::All::OP_HASH160); + script.push_slice(&self.hash[..]); + script.push_opcode(opcodes::All::OP_EQUALVERIFY); + script.push_opcode(opcodes::All::OP_CHECKSIG); + } + Type::ScriptHash => { + script.push_opcode(opcodes::All::OP_HASH160); + script.push_slice(&self.hash[..]); + script.push_opcode(opcodes::All::OP_EQUAL); + } + } + script.into_script() + } +} + +impl ToBase58 for Address { + fn base58_layout(&self) -> Vec { + let mut ret = vec![ + match (self.network, self.ty) { + (Network::Bitcoin, Type::PubkeyHash) => 0, + (Network::Bitcoin, Type::ScriptHash) => 5, + (Network::Testnet, Type::PubkeyHash) => 111, + (Network::Testnet, Type::ScriptHash) => 196 + } + ]; + ret.extend(self.hash[..].iter().cloned()); + ret + } +} + +impl FromBase58 for Address { + fn from_base58_layout(data: Vec) -> Result { + if data.len() != 21 { + return Err(base58::Error::InvalidLength(data.len())); + } + + let (network, ty) = match data[0] { + 0 => (Network::Bitcoin, Type::PubkeyHash), + 5 => (Network::Bitcoin, Type::ScriptHash), + 111 => (Network::Testnet, Type::PubkeyHash), + 196 => (Network::Testnet, Type::ScriptHash), + x => { return Err(base58::Error::InvalidVersion(vec![x])); } + }; + + Ok(Address { + ty: ty, + network: network, + hash: Hash160::from(&data[1..]) + }) + } +} + +impl ::std::fmt::Debug for Address { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + write!(f, "{}", self.to_base58check()) + } +} + +#[cfg(test)] +mod tests { + use secp256k1::Secp256k1; + use secp256k1::key::PublicKey; + use serialize::hex::FromHex; + + use blockdata::script::Script; + use network::constants::Network::{Bitcoin, Testnet}; + use util::hash::Hash160; + use util::base58::{FromBase58, ToBase58}; + use super::*; + + macro_rules! hex (($hex:expr) => ($hex.from_hex().unwrap())); + macro_rules! hex_key (($secp:expr, $hex:expr) => (PublicKey::from_slice($secp, &hex!($hex)).unwrap())); + macro_rules! hex_script (($hex:expr) => (Script::from(hex!($hex)))); + + #[test] + fn test_p2pkh_address_58() { + let addr = Address { + ty: Type::PubkeyHash, + network: Bitcoin, + hash: Hash160::from(&"162c5ea71c0b23f5b9022ef047c4a86470a5b070".from_hex().unwrap()[..]) + }; + + assert_eq!(addr.script_pubkey(), hex_script!("76a914162c5ea71c0b23f5b9022ef047c4a86470a5b07088ac")); + assert_eq!(&addr.to_base58check(), "132F25rTsvBdp9JzLLBHP5mvGY66i1xdiM"); + assert_eq!(FromBase58::from_base58check("132F25rTsvBdp9JzLLBHP5mvGY66i1xdiM"), Ok(addr)); + } + + #[test] + fn test_p2pkh_from_key() { + let secp = Secp256k1::without_caps(); + + let key = hex_key!(&secp, "048d5141948c1702e8c95f438815794b87f706a8d4cd2bffad1dc1570971032c9b6042a0431ded2478b5c9cf2d81c124a5e57347a3c63ef0e7716cf54d613ba183"); + let addr = Address::from_key(Bitcoin, &key, false); + assert_eq!(&addr.to_base58check(), "1QJVDzdqb1VpbDK7uDeyVXy9mR27CJiyhY"); + + let key = hex_key!(&secp, &"03df154ebfcf29d29cc10d5c2565018bce2d9edbab267c31d2caf44a63056cf99f"); + let addr = Address::from_key(Testnet, &key, true); + assert_eq!(&addr.to_base58check(), "mqkhEMH6NCeYjFybv7pvFC22MFeaNT9AQC"); + } + + #[test] + fn test_p2sh_address_58() { + let addr = Address { + ty: Type::ScriptHash, + network: Bitcoin, + hash: Hash160::from(&"162c5ea71c0b23f5b9022ef047c4a86470a5b070".from_hex().unwrap()[..]) + }; + + assert_eq!(addr.script_pubkey(), hex_script!("a914162c5ea71c0b23f5b9022ef047c4a86470a5b07087")); + assert_eq!(&addr.to_base58check(), "33iFwdLuRpW1uK1RTRqsoi8rR4NpDzk66k"); + assert_eq!(FromBase58::from_base58check("33iFwdLuRpW1uK1RTRqsoi8rR4NpDzk66k"), Ok(addr)); + } +} + diff --git a/src/util/contracthash.rs b/src/util/contracthash.rs new file mode 100644 index 0000000..245efe3 --- /dev/null +++ b/src/util/contracthash.rs @@ -0,0 +1,154 @@ +// Rust Bitcoin Library +// Written in 2015 by +// Andrew Poelstra +// +// To the extent possible under law, the author(s) have dedicated all +// copyright and related and neighboring rights to this software to +// the public domain worldwide. This software is distributed without +// any warranty. +// +// You should have received a copy of the CC0 Public Domain Dedication +// along with this software. +// If not, see . +// + +//! # Pay-to-contract-hash supporte +//! See Appendix A of the Blockstream sidechains whitepaper +//! at http://blockstream.com/sidechains.pdf for details of +//! what this does. + +use secp256k1::{self, ContextFlag, Secp256k1}; +use secp256k1::key::{PublicKey, SecretKey}; +use blockdata::{opcodes, script}; +use crypto::{hmac, sha2}; +use crypto::mac::Mac; + +use network::constants::Network; +use util::{address, hash}; + +/// Encoding of "pubkey here" in script; from bitcoin core `src/script/script.h` +static PUBKEY: u8 = 0xFE; + +/// A contract-hash error +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum Error { + /// Contract hashed to an out-of-range value (this is basically impossible + /// and much more likely suggests memory corruption or hardware failure) + BadTweak(secp256k1::Error), + /// Other secp256k1 related error + Secp(secp256k1::Error), + /// Did not have enough keys to instantiate a script template + TooFewKeys(usize) +} + +/// An element of a script template +enum TemplateElement { + Op(opcodes::All), + Key +} + +/// A script template +pub struct Template(Vec); + +impl Template { + /// Instantiate a template + pub fn to_script(&self, keys: &[PublicKey]) -> Result { + let secp = Secp256k1::with_caps(ContextFlag::None); + let mut key_index = 0; + let mut ret = script::Builder::new(); + for elem in &self.0 { + match *elem { + TemplateElement::Op(opcode) => ret.push_opcode(opcode), + TemplateElement::Key => { + if key_index == keys.len() { + return Err(Error::TooFewKeys(key_index)); + } + ret.push_slice(&keys[key_index].serialize_vec(&secp, true)[..]); + key_index += 1; + } + } + } + Ok(ret.into_script()) + } +} + +impl<'a> From<&'a [u8]> for Template { + fn from(slice: &'a [u8]) -> Template { + Template(slice.iter().map(|&byte| { + if byte == PUBKEY { + TemplateElement::Key + } else { + TemplateElement::Op(opcodes::All::from(byte)) + } + }).collect()) + } +} + +/// Tweak keys using some arbitrary data +pub fn tweak_keys(secp: &Secp256k1, keys: &[PublicKey], contract: &[u8]) -> Result, Error> { + let mut ret = Vec::with_capacity(keys.len()); + for mut key in keys.iter().cloned() { + let mut hmac_raw = [0; 32]; + let mut hmac = hmac::Hmac::new(sha2::Sha256::new(), &key.serialize_vec(&secp, true)); + hmac.input(contract); + hmac.raw_result(&mut hmac_raw); + let hmac_sk = try!(SecretKey::from_slice(&secp, &hmac_raw).map_err(Error::BadTweak)); + try!(key.add_exp_assign(&secp, &hmac_sk).map_err(Error::Secp)); + ret.push(key); + } + Ok(ret) +} + +/// Takes a contract, template and key set and runs through all the steps +pub fn create_address(secp: &Secp256k1, + network: Network, + contract: &[u8], + keys: &[PublicKey], + template: &Template) + -> Result { + let keys = try!(tweak_keys(secp, keys, contract)); + let script = try!(template.to_script(&keys)); + Ok(address::Address { + network: network, + ty: address::Type::ScriptHash, + hash: hash::Hash160::from_data(&script[..]) + }) +} + +#[cfg(test)] +mod tests { + use secp256k1::Secp256k1; + use secp256k1::key::PublicKey; + use serialize::hex::FromHex; + + use network::constants::Network; + use util::base58::ToBase58; + + use super::*; + + macro_rules! hex (($hex:expr) => ($hex.from_hex().unwrap())); + macro_rules! hex_key (($secp:expr, $hex:expr) => (PublicKey::from_slice($secp, &hex!($hex)).unwrap())); + macro_rules! alpha_template(() => (Template::from(&hex!("55fefefefefefefe57AE")[..]))); + macro_rules! alpha_keys(($secp:expr) => ( + &[hex_key!($secp, "0269992fb441ae56968e5b77d46a3e53b69f136444ae65a94041fc937bdb28d933"), + hex_key!($secp, "021df31471281d4478df85bfce08a10aab82601dca949a79950f8ddf7002bd915a"), + hex_key!($secp, "02174c82021492c2c6dfcbfa4187d10d38bed06afb7fdcd72c880179fddd641ea1"), + hex_key!($secp, "033f96e43d72c33327b6a4631ccaa6ea07f0b106c88b9dc71c9000bb6044d5e88a"), + hex_key!($secp, "0313d8748790f2a86fb524579b46ce3c68fedd58d2a738716249a9f7d5458a15c2"), + hex_key!($secp, "030b632eeb079eb83648886122a04c7bf6d98ab5dfb94cf353ee3e9382a4c2fab0"), + hex_key!($secp, "02fb54a7fcaa73c307cfd70f3fa66a2e4247a71858ca731396343ad30c7c4009ce")] + )); + + #[test] + fn sanity() { + let secp = Secp256k1::new(); + let keys = alpha_keys!(&secp); + // This is the first withdraw ever, in alpha a94f95cc47b444c10449c0eed51d895e4970560c4a1a9d15d46124858abc3afe + let contract = hex!("5032534894ffbf32c1f1c0d3089b27c98fd991d5d7329ebd7d711223e2cde5a9417a1fa3e852c576"); + + let addr = create_address(&secp, Network::Testnet, &contract, keys, &alpha_template!()).unwrap(); + assert_eq!(addr.to_base58check(), "2N3zXjbwdTcPsJiy8sUK9FhWJhqQCxA8Jjr".to_owned()); + } +} + + diff --git a/src/util/hash.rs b/src/util/hash.rs index 69629c5..3383e79 100644 --- a/src/util/hash.rs +++ b/src/util/hash.rs @@ -45,6 +45,10 @@ impl ::std::fmt::Debug for Sha256dHash { pub struct Ripemd160Hash([u8; 20]); impl_array_newtype!(Ripemd160Hash, u8, 20); +/// A Bitcoin hash160, 20-bytes, computed from x as RIPEMD160(SHA256(x)) +pub struct Hash160([u8; 20]); +impl_array_newtype!(Hash160, u8, 20); + /// A 32-bit hash obtained by truncating a real hash #[derive(Copy, Clone, PartialEq, Eq, Debug)] pub struct Hash32((u8, u8, u8, u8)); @@ -68,8 +72,23 @@ impl Ripemd160Hash { } } +impl Hash160 { + /// Create a hash by hashing some data + pub fn from_data(data: &[u8]) -> Hash160 { + let mut tmp = [0; 32]; + let mut ret = [0; 20]; + let mut sha2 = Sha256::new(); + let mut rmd = Ripemd160::new(); + sha2.input(data); + sha2.result(&mut tmp); + rmd.input(&tmp); + rmd.result(&mut ret); + Hash160(ret) + } +} + // This doesn't make much sense to me, but is implicit behaviour -// in the C++ reference client +// in the C++ reference client, so we need it for consensus. impl Default for Sha256dHash { #[inline] fn default() -> Sha256dHash { Sha256dHash([0u8; 32]) } diff --git a/src/util/mod.rs b/src/util/mod.rs index ad6d506..67ef470 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -16,7 +16,9 @@ //! //! Functions needed by all parts of the Bitcoin library +pub mod address; pub mod base58; +pub mod contracthash; pub mod hash; pub mod iter; pub mod misc;