diff --git a/src/address.rs b/src/address.rs deleted file mode 100644 index 5ba93995..00000000 --- a/src/address.rs +++ /dev/null @@ -1,19 +0,0 @@ -//! A Bitcoin address, or simply address, is an identifier of 26-35 alphanumeric characters, beginning with the number 1 -//! or 3, that represents a possible destination for a bitcoin payment. -//! -//! https://en.bitcoin.it/wiki/Address - -/// There are two address formats currently in use. -/// https://bitcoin.org/en/developer-reference#address-conversion -pub enum Format { - /// Pay to PubKey Hash - /// Common P2PKH which begin with the number 1, eg: 1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2. - /// https://bitcoin.org/en/glossary/p2pkh-address - P2PKH, - /// Pay to Script Hash - /// Newer P2SH type starting with the number 3, eg: 3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLy. - /// https://bitcoin.org/en/glossary/p2sh-address - P2SH, -} - - diff --git a/src/keys/address.rs b/src/keys/address.rs new file mode 100644 index 00000000..4c158faf --- /dev/null +++ b/src/keys/address.rs @@ -0,0 +1,71 @@ +//! A Bitcoin address, or simply address, is an identifier of 26-35 alphanumeric characters, beginning with the number 1 +//! or 3, that represents a possible destination for a bitcoin payment. +//! +//! https://en.bitcoin.it/wiki/Address + +use std::fmt; +use std::ops::Deref; +use base58::ToBase58; +use network::Network; +use hash::H160; +use keys::{DisplayLayout, checksum}; + +/// There are two address formats currently in use. +/// https://bitcoin.org/en/developer-reference#address-conversion +#[derive(Debug, PartialEq, Clone, Copy)] +pub enum Type { + /// Pay to PubKey Hash + /// Common P2PKH which begin with the number 1, eg: 1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2. + /// https://bitcoin.org/en/glossary/p2pkh-address + P2PKH, + /// Pay to Script Hash + /// Newer P2SH type starting with the number 3, eg: 3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLy. + /// https://bitcoin.org/en/glossary/p2sh-address + P2SH, +} + +#[derive(Debug, PartialEq)] +pub struct Address { + /// The type of the address. + pub kind: Type, + /// The network of the address. + pub network: Network, + /// Public key hash. + pub hash: H160, +} + +pub struct AddressDisplayLayout([u8; 25]); + +impl Deref for AddressDisplayLayout { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DisplayLayout for Address { + type Target = AddressDisplayLayout; + + fn layout(&self) -> Self::Target { + let mut result = [0u8; 25]; + + result[0] = match (self.network, self.kind) { + (Network::Mainnet, Type::P2PKH) => 0, + (Network::Mainnet, Type::P2SH) => 5, + (Network::Testnet, Type::P2PKH) => 111, + (Network::Testnet, Type::P2SH) => 196, + }; + + result[1..21].copy_from_slice(&self.hash); + let cs = checksum(&result[0..21]); + result[21..25].copy_from_slice(&cs); + AddressDisplayLayout(result) + } +} + +impl fmt::Display for Address { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.layout().to_base58().fmt(f) + } +} diff --git a/src/keys/checksum.rs b/src/keys/checksum.rs new file mode 100644 index 00000000..931ce1f3 --- /dev/null +++ b/src/keys/checksum.rs @@ -0,0 +1,9 @@ +use crypto::dhash; + +/// Data checksum +pub fn checksum(data: &[u8]) -> [u8; 4] { + let mut result = [0u8; 4]; + // TODO: check if this checksum is valid + result.copy_from_slice(&dhash(data)[28..]); + result +} diff --git a/src/keys/display.rs b/src/keys/display.rs new file mode 100644 index 00000000..c723e37b --- /dev/null +++ b/src/keys/display.rs @@ -0,0 +1,7 @@ +use std::ops::Deref; + +pub trait DisplayLayout { + type Target: Deref; + + fn layout(&self) -> Self::Target; +} diff --git a/src/keys/keypair.rs b/src/keys/keypair.rs index e7844544..6d75e78a 100644 --- a/src/keys/keypair.rs +++ b/src/keys/keypair.rs @@ -1,10 +1,21 @@ +//! Bitcoin KeyPair +//! +//! Fields: +//! - secret - 32 bytes +//! - public - 65 bytes +//! - private - secret with additional network identifier (and compressed flag?) +//! - address_hash - 20 bytes derived from public +//! - address - address_hash with network identifier and format type + use std::fmt; use rustc_serialize::hex::ToHex; use rcrypto::sha2::Sha256; use rcrypto::ripemd160::Ripemd160; use rcrypto::digest::Digest; use secp256k1::key; -use keys::{Secret, Public, Error, SECP256K1, Address}; +use hash::H160; +use network::Network; +use keys::{Secret, Public, Error, SECP256K1, Address, Type}; pub struct KeyPair { secret: Secret, @@ -51,7 +62,7 @@ impl KeyPair { } } - pub fn address(&self) -> Address { + pub fn address_hash(&self) -> H160 { let mut tmp = [0u8; 32]; let mut result = [0u8; 20]; let mut sha2 = Sha256::new(); @@ -62,12 +73,20 @@ impl KeyPair { rmd.result(&mut result); result } + + pub fn address(&self, network: Network) -> Address { + Address { + kind: Type::P2PKH, + network: network, + hash: self.address_hash(), + } + } } #[cfg(test)] mod tests { use super::KeyPair; - use base58::{ToBase58, FromBase58}; + use base58::{FromBase58}; #[test] fn test_generating_address() { @@ -78,6 +97,6 @@ mod tests { let mut address = [0u8; 20]; address.copy_from_slice(&"16meyfSoQV6twkAAxPe51RtMVz7PGRmWna".from_base58().unwrap()[1..21]); let kp = KeyPair::from_secret(secret).unwrap(); - assert_eq!(kp.address(), address); + assert_eq!(kp.address_hash(), address); } } diff --git a/src/keys/mod.rs b/src/keys/mod.rs index d3014f3a..5b193541 100644 --- a/src/keys/mod.rs +++ b/src/keys/mod.rs @@ -1,13 +1,20 @@ +mod address; +mod checksum; +pub mod display; pub mod generator; pub mod keypair; mod error; +mod private; use secp256k1; -use hash::{H160, H256, H520}; +use hash::{H256, H520}; +pub use self::address::{Type, Address}; +pub use self::checksum::checksum; +pub use self::display::DisplayLayout; pub use self::keypair::KeyPair; pub use self::error::Error; +pub use self::private::Private; -pub type Address = H160; pub type Secret = H256; pub type Public = H520; diff --git a/src/keys/private.rs b/src/keys/private.rs new file mode 100644 index 00000000..aa2da810 --- /dev/null +++ b/src/keys/private.rs @@ -0,0 +1,39 @@ +use std::fmt; +use base58::ToBase58; +use network::Network; +use keys::{Secret, DisplayLayout}; + +#[derive(Debug, PartialEq)] +pub struct Private { + /// The network on which this key should be used. + pub network: Network, + /// ECDSA key. + pub secret: Secret, + /// True if this private key represents a compressed address. + pub compressed: bool, +} + +impl DisplayLayout for Private { + type Target = Vec; + + fn layout(&self) -> Self::Target { + let mut result = vec![]; + let network_byte = match self.network { + Network::Mainnet => 128, + Network::Testnet => 239, + }; + + result.push(network_byte); + result.extend(&self.secret); + if self.compressed { + result.push(1); + } + result + } +} + +impl fmt::Display for Private { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.layout().to_base58().fmt(f) + } +} diff --git a/src/lib.rs b/src/lib.rs index cc5b878b..0a5c77e7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,7 +10,6 @@ extern crate secp256k1; extern crate base58; pub mod keys; -pub mod address; pub mod block; pub mod block_header; pub mod compact_integer; diff --git a/src/network.rs b/src/network.rs index 2581768d..27ea105d 100644 --- a/src/network.rs +++ b/src/network.rs @@ -5,7 +5,7 @@ const MAGIC_TESTNET: u32 = 0x0709110B; /// Bitcoin network /// https://bitcoin.org/en/glossary/mainnet -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone, Copy)] pub enum Network { /// The original and main network for Bitcoin transactions, where satoshis have real economic value. Mainnet,