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:
parent
dba71d9253
commit
16e2a3519b
|
@ -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"
|
||||
|
|
|
@ -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>);
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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]) }
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue