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.
This commit is contained in:
Andrew Poelstra 2015-10-14 15:29:19 -05:00
parent dba71d9253
commit 16e2a3519b
6 changed files with 369 additions and 4 deletions

View File

@ -1,7 +1,7 @@
[package]
name = "bitcoin"
version = "0.3.0"
version = "0.3.1"
authors = ["Andrew Poelstra <apoelstra@wpsoftware.net>"]
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"

View File

@ -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<u8>);

180
src/util/address.rs Normal file
View File

@ -0,0 +1,180 @@
// Rust Bitcoin Library
// Written in 2014 by
// Andrew Poelstra <apoelstra@wpsoftware.net>
// 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 <http://creativecommons.org/publicdomain/zero/1.0/>.
//
//! # 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<u8> {
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<u8>) -> Result<Address, base58::Error> {
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));
}
}

154
src/util/contracthash.rs Normal file
View File

@ -0,0 +1,154 @@
// Rust Bitcoin Library
// Written in 2015 by
// Andrew Poelstra <apoelstra@wpsoftware.net>
//
// 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 <http://creativecommons.org/publicdomain/zero/1.0/>.
//
//! # 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<TemplateElement>);
impl Template {
/// Instantiate a template
pub fn to_script(&self, keys: &[PublicKey]) -> Result<script::Script, Error> {
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<Vec<PublicKey>, 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<address::Address, Error> {
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());
}
}

View File

@ -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]) }

View File

@ -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;