base58: remove Base58 traits, replace with encode/decode functions
This commit is contained in:
parent
c8702ab55d
commit
ee1dfcf4a2
|
@ -15,6 +15,9 @@
|
||||||
//! Support for ordinary base58 Bitcoin addresses and private keys
|
//! Support for ordinary base58 Bitcoin addresses and private keys
|
||||||
//!
|
//!
|
||||||
|
|
||||||
|
use std::str::FromStr;
|
||||||
|
use std::string::ToString;
|
||||||
|
|
||||||
use secp256k1::{self, Secp256k1};
|
use secp256k1::{self, Secp256k1};
|
||||||
use secp256k1::key::{PublicKey, SecretKey};
|
use secp256k1::key::{PublicKey, SecretKey};
|
||||||
|
|
||||||
|
@ -22,7 +25,7 @@ use blockdata::script;
|
||||||
use blockdata::opcodes;
|
use blockdata::opcodes;
|
||||||
use network::constants::Network;
|
use network::constants::Network;
|
||||||
use util::hash::Hash160;
|
use util::hash::Hash160;
|
||||||
use util::base58::{self, FromBase58, ToBase58};
|
use util::base58;
|
||||||
|
|
||||||
/// The method used to produce an address
|
/// The method used to produce an address
|
||||||
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||||
|
@ -98,23 +101,26 @@ impl Address {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToBase58 for Address {
|
impl ToString for Address {
|
||||||
fn base58_layout(&self) -> Vec<u8> {
|
fn to_string(&self) -> String {
|
||||||
let mut ret = vec![
|
let mut prefixed = [0; 21];
|
||||||
match (self.network, self.ty) {
|
prefixed[0] = match (self.network, self.ty) {
|
||||||
(Network::Bitcoin, Type::PubkeyHash) => 0,
|
(Network::Bitcoin, Type::PubkeyHash) => 0,
|
||||||
(Network::Bitcoin, Type::ScriptHash) => 5,
|
(Network::Bitcoin, Type::ScriptHash) => 5,
|
||||||
(Network::Testnet, Type::PubkeyHash) => 111,
|
(Network::Testnet, Type::PubkeyHash) => 111,
|
||||||
(Network::Testnet, Type::ScriptHash) => 196
|
(Network::Testnet, Type::ScriptHash) => 196
|
||||||
}
|
};
|
||||||
];
|
prefixed[1..].copy_from_slice(&self.hash[..]);
|
||||||
ret.extend(self.hash[..].iter().cloned());
|
base58::check_encode_slice(&prefixed[..])
|
||||||
ret
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromBase58 for Address {
|
impl FromStr for Address {
|
||||||
fn from_base58_layout(data: Vec<u8>) -> Result<Address, base58::Error> {
|
type Err = base58::Error;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Address, base58::Error> {
|
||||||
|
let data = try!(base58::from_check(s));
|
||||||
|
|
||||||
if data.len() != 21 {
|
if data.len() != 21 {
|
||||||
return Err(base58::Error::InvalidLength(data.len()));
|
return Err(base58::Error::InvalidLength(data.len()));
|
||||||
}
|
}
|
||||||
|
@ -137,7 +143,7 @@ impl FromBase58 for Address {
|
||||||
|
|
||||||
impl ::std::fmt::Debug for Address {
|
impl ::std::fmt::Debug for Address {
|
||||||
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
|
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
|
||||||
write!(f, "{}", self.to_base58check())
|
write!(f, "{}", self.to_string())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -195,22 +201,29 @@ impl Privkey {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToBase58 for Privkey {
|
impl ToString for Privkey {
|
||||||
fn base58_layout(&self) -> Vec<u8> {
|
fn to_string(&self) -> String {
|
||||||
let mut ret = vec![
|
let mut ret = [0; 34];
|
||||||
match self.network {
|
ret[0] = match self.network {
|
||||||
Network::Bitcoin => 128,
|
Network::Bitcoin => 128,
|
||||||
Network::Testnet => 239
|
Network::Testnet => 239
|
||||||
}
|
};
|
||||||
];
|
ret[1..33].copy_from_slice(&self.key[..]);
|
||||||
ret.extend(&self.key[..]);
|
if self.compressed {
|
||||||
if self.compressed { ret.push(1); }
|
ret[33] = 1;
|
||||||
ret
|
base58::check_encode_slice(&ret[..])
|
||||||
|
} else {
|
||||||
|
base58::check_encode_slice(&ret[..33])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromBase58 for Privkey {
|
impl FromStr for Privkey {
|
||||||
fn from_base58_layout(data: Vec<u8>) -> Result<Privkey, base58::Error> {
|
type Err = base58::Error;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Privkey, base58::Error> {
|
||||||
|
let data = try!(base58::from_check(s));
|
||||||
|
|
||||||
let compressed = match data.len() {
|
let compressed = match data.len() {
|
||||||
33 => false,
|
33 => false,
|
||||||
34 => true,
|
34 => true,
|
||||||
|
@ -237,6 +250,9 @@ impl FromBase58 for Privkey {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use std::str::FromStr;
|
||||||
|
use std::string::ToString;
|
||||||
|
|
||||||
use secp256k1::Secp256k1;
|
use secp256k1::Secp256k1;
|
||||||
use secp256k1::key::PublicKey;
|
use secp256k1::key::PublicKey;
|
||||||
use serialize::hex::FromHex;
|
use serialize::hex::FromHex;
|
||||||
|
@ -244,7 +260,6 @@ mod tests {
|
||||||
use blockdata::script::Script;
|
use blockdata::script::Script;
|
||||||
use network::constants::Network::{Bitcoin, Testnet};
|
use network::constants::Network::{Bitcoin, Testnet};
|
||||||
use util::hash::Hash160;
|
use util::hash::Hash160;
|
||||||
use util::base58::{FromBase58, ToBase58};
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
macro_rules! hex (($hex:expr) => ($hex.from_hex().unwrap()));
|
macro_rules! hex (($hex:expr) => ($hex.from_hex().unwrap()));
|
||||||
|
@ -260,8 +275,8 @@ mod tests {
|
||||||
};
|
};
|
||||||
|
|
||||||
assert_eq!(addr.script_pubkey(), hex_script!("76a914162c5ea71c0b23f5b9022ef047c4a86470a5b07088ac"));
|
assert_eq!(addr.script_pubkey(), hex_script!("76a914162c5ea71c0b23f5b9022ef047c4a86470a5b07088ac"));
|
||||||
assert_eq!(&addr.to_base58check(), "132F25rTsvBdp9JzLLBHP5mvGY66i1xdiM");
|
assert_eq!(&addr.to_string(), "132F25rTsvBdp9JzLLBHP5mvGY66i1xdiM");
|
||||||
assert_eq!(FromBase58::from_base58check("132F25rTsvBdp9JzLLBHP5mvGY66i1xdiM"), Ok(addr));
|
assert_eq!(Address::from_str("132F25rTsvBdp9JzLLBHP5mvGY66i1xdiM"), Ok(addr));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -270,11 +285,11 @@ mod tests {
|
||||||
|
|
||||||
let key = hex_key!(&secp, "048d5141948c1702e8c95f438815794b87f706a8d4cd2bffad1dc1570971032c9b6042a0431ded2478b5c9cf2d81c124a5e57347a3c63ef0e7716cf54d613ba183");
|
let key = hex_key!(&secp, "048d5141948c1702e8c95f438815794b87f706a8d4cd2bffad1dc1570971032c9b6042a0431ded2478b5c9cf2d81c124a5e57347a3c63ef0e7716cf54d613ba183");
|
||||||
let addr = Address::from_key(Bitcoin, &key, false);
|
let addr = Address::from_key(Bitcoin, &key, false);
|
||||||
assert_eq!(&addr.to_base58check(), "1QJVDzdqb1VpbDK7uDeyVXy9mR27CJiyhY");
|
assert_eq!(&addr.to_string(), "1QJVDzdqb1VpbDK7uDeyVXy9mR27CJiyhY");
|
||||||
|
|
||||||
let key = hex_key!(&secp, &"03df154ebfcf29d29cc10d5c2565018bce2d9edbab267c31d2caf44a63056cf99f");
|
let key = hex_key!(&secp, &"03df154ebfcf29d29cc10d5c2565018bce2d9edbab267c31d2caf44a63056cf99f");
|
||||||
let addr = Address::from_key(Testnet, &key, true);
|
let addr = Address::from_key(Testnet, &key, true);
|
||||||
assert_eq!(&addr.to_base58check(), "mqkhEMH6NCeYjFybv7pvFC22MFeaNT9AQC");
|
assert_eq!(&addr.to_string(), "mqkhEMH6NCeYjFybv7pvFC22MFeaNT9AQC");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -286,8 +301,8 @@ mod tests {
|
||||||
};
|
};
|
||||||
|
|
||||||
assert_eq!(addr.script_pubkey(), hex_script!("a914162c5ea71c0b23f5b9022ef047c4a86470a5b07087"));
|
assert_eq!(addr.script_pubkey(), hex_script!("a914162c5ea71c0b23f5b9022ef047c4a86470a5b07087"));
|
||||||
assert_eq!(&addr.to_base58check(), "33iFwdLuRpW1uK1RTRqsoi8rR4NpDzk66k");
|
assert_eq!(&addr.to_string(), "33iFwdLuRpW1uK1RTRqsoi8rR4NpDzk66k");
|
||||||
assert_eq!(FromBase58::from_base58check("33iFwdLuRpW1uK1RTRqsoi8rR4NpDzk66k"), Ok(addr));
|
assert_eq!(Address::from_str("33iFwdLuRpW1uK1RTRqsoi8rR4NpDzk66k"), Ok(addr));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -295,31 +310,31 @@ mod tests {
|
||||||
let script = hex_script!("552103a765fc35b3f210b95223846b36ef62a4e53e34e2925270c2c7906b92c9f718eb2103c327511374246759ec8d0b89fa6c6b23b33e11f92c5bc155409d86de0c79180121038cae7406af1f12f4786d820a1466eec7bc5785a1b5e4a387eca6d797753ef6db2103252bfb9dcaab0cd00353f2ac328954d791270203d66c2be8b430f115f451b8a12103e79412d42372c55dd336f2eb6eb639ef9d74a22041ba79382c74da2338fe58ad21035049459a4ebc00e876a9eef02e72a3e70202d3d1f591fc0dd542f93f642021f82102016f682920d9723c61b27f562eb530c926c00106004798b6471e8c52c60ee02057ae");
|
let script = hex_script!("552103a765fc35b3f210b95223846b36ef62a4e53e34e2925270c2c7906b92c9f718eb2103c327511374246759ec8d0b89fa6c6b23b33e11f92c5bc155409d86de0c79180121038cae7406af1f12f4786d820a1466eec7bc5785a1b5e4a387eca6d797753ef6db2103252bfb9dcaab0cd00353f2ac328954d791270203d66c2be8b430f115f451b8a12103e79412d42372c55dd336f2eb6eb639ef9d74a22041ba79382c74da2338fe58ad21035049459a4ebc00e876a9eef02e72a3e70202d3d1f591fc0dd542f93f642021f82102016f682920d9723c61b27f562eb530c926c00106004798b6471e8c52c60ee02057ae");
|
||||||
let addr = Address::from_script(Testnet, &script);
|
let addr = Address::from_script(Testnet, &script);
|
||||||
|
|
||||||
assert_eq!(&addr.to_base58check(), "2N3zXjbwdTcPsJiy8sUK9FhWJhqQCxA8Jjr");
|
assert_eq!(&addr.to_string(), "2N3zXjbwdTcPsJiy8sUK9FhWJhqQCxA8Jjr");
|
||||||
assert_eq!(FromBase58::from_base58check("2N3zXjbwdTcPsJiy8sUK9FhWJhqQCxA8Jjr"), Ok(addr));
|
assert_eq!(Address::from_str("2N3zXjbwdTcPsJiy8sUK9FhWJhqQCxA8Jjr"), Ok(addr));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_key_derivation() {
|
fn test_key_derivation() {
|
||||||
// testnet compressed
|
// testnet compressed
|
||||||
let sk: Privkey = FromBase58::from_base58check("cVt4o7BGAig1UXywgGSmARhxMdzP5qvQsxKkSsc1XEkw3tDTQFpy").unwrap();
|
let sk = Privkey::from_str("cVt4o7BGAig1UXywgGSmARhxMdzP5qvQsxKkSsc1XEkw3tDTQFpy").unwrap();
|
||||||
assert_eq!(sk.network(), Testnet);
|
assert_eq!(sk.network(), Testnet);
|
||||||
assert_eq!(sk.is_compressed(), true);
|
assert_eq!(sk.is_compressed(), true);
|
||||||
assert_eq!(&sk.to_base58check(), "cVt4o7BGAig1UXywgGSmARhxMdzP5qvQsxKkSsc1XEkw3tDTQFpy");
|
assert_eq!(&sk.to_string(), "cVt4o7BGAig1UXywgGSmARhxMdzP5qvQsxKkSsc1XEkw3tDTQFpy");
|
||||||
|
|
||||||
let secp = Secp256k1::new();
|
let secp = Secp256k1::new();
|
||||||
let pk = sk.to_address(&secp).unwrap();
|
let pk = sk.to_address(&secp).unwrap();
|
||||||
assert_eq!(&pk.to_base58check(), "mqwpxxvfv3QbM8PU8uBx2jaNt9btQqvQNx");
|
assert_eq!(&pk.to_string(), "mqwpxxvfv3QbM8PU8uBx2jaNt9btQqvQNx");
|
||||||
|
|
||||||
// mainnet uncompressed
|
// mainnet uncompressed
|
||||||
let sk: Privkey = FromBase58::from_base58check("5JYkZjmN7PVMjJUfJWfRFwtuXTGB439XV6faajeHPAM9Z2PT2R3").unwrap();
|
let sk = Privkey::from_str("5JYkZjmN7PVMjJUfJWfRFwtuXTGB439XV6faajeHPAM9Z2PT2R3").unwrap();
|
||||||
assert_eq!(sk.network(), Bitcoin);
|
assert_eq!(sk.network(), Bitcoin);
|
||||||
assert_eq!(sk.is_compressed(), false);
|
assert_eq!(sk.is_compressed(), false);
|
||||||
assert_eq!(&sk.to_base58check(), "5JYkZjmN7PVMjJUfJWfRFwtuXTGB439XV6faajeHPAM9Z2PT2R3");
|
assert_eq!(&sk.to_string(), "5JYkZjmN7PVMjJUfJWfRFwtuXTGB439XV6faajeHPAM9Z2PT2R3");
|
||||||
|
|
||||||
let secp = Secp256k1::new();
|
let secp = Secp256k1::new();
|
||||||
let pk = sk.to_address(&secp).unwrap();
|
let pk = sk.to_address(&secp).unwrap();
|
||||||
assert_eq!(&pk.to_base58check(), "1GhQvF6dL8xa6wBxLnWmHcQsurx9RxiMc8");
|
assert_eq!(&pk.to_string(), "1GhQvF6dL8xa6wBxLnWmHcQsurx9RxiMc8");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
use std::{error, fmt};
|
use std::{error, fmt};
|
||||||
|
|
||||||
use byteorder::{ByteOrder, LittleEndian, WriteBytesExt};
|
use byteorder::{ByteOrder, LittleEndian};
|
||||||
use util::hash::Sha256dHash;
|
use util::hash::Sha256dHash;
|
||||||
|
|
||||||
/// An error that might occur during base58 decoding
|
/// An error that might occur during base58 decoding
|
||||||
|
@ -84,171 +84,158 @@ static BASE58_DIGITS: [Option<u8>; 128] = [
|
||||||
Some(55), Some(56), Some(57), None, None, None, None, None, // 120-127
|
Some(55), Some(56), Some(57), None, None, None, None, None, // 120-127
|
||||||
];
|
];
|
||||||
|
|
||||||
/// Trait for objects which can be read as base58
|
/// Decode base58-encoded string into a byte vector
|
||||||
pub trait FromBase58: Sized {
|
pub fn from(data: &str) -> Result<Vec<u8>, Error> {
|
||||||
/// Constructs an object from the byte-encoding (base 256)
|
// 11/15 is just over log_256(58)
|
||||||
/// representation of its base58 format
|
let mut scratch = vec![0u8; 1 + data.len() * 11 / 15];
|
||||||
fn from_base58_layout(data: Vec<u8>) -> Result<Self, Error>;
|
// Build in base 256
|
||||||
|
for d58 in data.bytes() {
|
||||||
/// Obtain an object from its base58 encoding
|
// Compute "X = X * 58 + next_digit" in base 256
|
||||||
fn from_base58(data: &str) -> Result<Self, Error> {
|
if d58 as usize > BASE58_DIGITS.len() {
|
||||||
// 11/15 is just over log_256(58)
|
return Err(Error::BadByte(d58));
|
||||||
let mut scratch = vec![0u8; 1 + data.len() * 11 / 15];
|
|
||||||
// Build in base 256
|
|
||||||
for d58 in data.bytes() {
|
|
||||||
// Compute "X = X * 58 + next_digit" in base 256
|
|
||||||
if d58 as usize > BASE58_DIGITS.len() {
|
|
||||||
return Err(Error::BadByte(d58));
|
|
||||||
}
|
|
||||||
let mut carry = match BASE58_DIGITS[d58 as usize] {
|
|
||||||
Some(d58) => d58 as u32,
|
|
||||||
None => { return Err(Error::BadByte(d58)); }
|
|
||||||
};
|
|
||||||
for d256 in scratch.iter_mut().rev() {
|
|
||||||
carry += *d256 as u32 * 58;
|
|
||||||
*d256 = carry as u8;
|
|
||||||
carry /= 256;
|
|
||||||
}
|
|
||||||
assert_eq!(carry, 0);
|
|
||||||
}
|
}
|
||||||
|
let mut carry = match BASE58_DIGITS[d58 as usize] {
|
||||||
// Copy leading zeroes directly
|
Some(d58) => d58 as u32,
|
||||||
let mut ret: Vec<u8> = data.bytes().take_while(|&x| x == BASE58_CHARS[0])
|
None => { return Err(Error::BadByte(d58)); }
|
||||||
.map(|_| 0)
|
};
|
||||||
.collect();
|
for d256 in scratch.iter_mut().rev() {
|
||||||
// Copy rest of string
|
carry += *d256 as u32 * 58;
|
||||||
ret.extend(scratch.into_iter().skip_while(|&x| x == 0));
|
*d256 = carry as u8;
|
||||||
FromBase58::from_base58_layout(ret)
|
carry /= 256;
|
||||||
}
|
|
||||||
|
|
||||||
/// Obtain an object from its base58check encoding
|
|
||||||
fn from_base58check(data: &str) -> Result<Self, Error> {
|
|
||||||
let mut ret: Vec<u8> = try!(FromBase58::from_base58(data));
|
|
||||||
if ret.len() < 4 {
|
|
||||||
return Err(Error::TooShort(ret.len()));
|
|
||||||
}
|
|
||||||
let ck_start = ret.len() - 4;
|
|
||||||
let expected = Sha256dHash::from_data(&ret[..ck_start]).into_le().low_u32();
|
|
||||||
let actual = LittleEndian::read_u32(&ret[ck_start..(ck_start + 4)]);
|
|
||||||
if expected != actual {
|
|
||||||
return Err(Error::BadChecksum(expected, actual));
|
|
||||||
}
|
|
||||||
|
|
||||||
ret.truncate(ck_start);
|
|
||||||
FromBase58::from_base58_layout(ret)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Directly encode a slice as base58
|
|
||||||
pub fn base58_encode_slice(data: &[u8]) -> String {
|
|
||||||
// 7/5 is just over log_58(256)
|
|
||||||
let mut scratch = vec![0u8; 1 + data.len() * 7 / 5];
|
|
||||||
// Build in base 58
|
|
||||||
for &d256 in &data.base58_layout() {
|
|
||||||
// Compute "X = X * 256 + next_digit" in base 58
|
|
||||||
let mut carry = d256 as u32;
|
|
||||||
for d58 in scratch.iter_mut().rev() {
|
|
||||||
carry += (*d58 as u32) << 8;
|
|
||||||
*d58 = (carry % 58) as u8;
|
|
||||||
carry /= 58;
|
|
||||||
}
|
}
|
||||||
assert_eq!(carry, 0);
|
assert_eq!(carry, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy leading zeroes directly
|
// Copy leading zeroes directly
|
||||||
let mut ret: Vec<u8> = data.iter().take_while(|&&x| x == 0)
|
let mut ret: Vec<u8> = data.bytes().take_while(|&x| x == BASE58_CHARS[0])
|
||||||
.map(|_| BASE58_CHARS[0])
|
.map(|_| 0)
|
||||||
.collect();
|
.collect();
|
||||||
// Copy rest of string
|
// Copy rest of string
|
||||||
ret.extend(scratch.into_iter().skip_while(|&x| x == 0)
|
ret.extend(scratch.into_iter().skip_while(|&x| x == 0));
|
||||||
.map(|x| BASE58_CHARS[x as usize]));
|
Ok(ret)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decode a base58check-encoded string
|
||||||
|
pub fn from_check(data: &str) -> Result<Vec<u8>, Error> {
|
||||||
|
let mut ret: Vec<u8> = try!(from(data));
|
||||||
|
if ret.len() < 4 {
|
||||||
|
return Err(Error::TooShort(ret.len()));
|
||||||
|
}
|
||||||
|
let ck_start = ret.len() - 4;
|
||||||
|
let expected = Sha256dHash::from_data(&ret[..ck_start]).into_le().low_u32();
|
||||||
|
let actual = LittleEndian::read_u32(&ret[ck_start..(ck_start + 4)]);
|
||||||
|
if expected != actual {
|
||||||
|
return Err(Error::BadChecksum(expected, actual));
|
||||||
|
}
|
||||||
|
|
||||||
|
ret.truncate(ck_start);
|
||||||
|
Ok(ret)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode_iter<I>(data: I) -> String
|
||||||
|
where
|
||||||
|
I: Iterator<Item = u8> + Clone,
|
||||||
|
{
|
||||||
|
let (len, _) = data.size_hint();
|
||||||
|
|
||||||
|
// 7/5 is just over log_58(256)
|
||||||
|
let mut ret = Vec::with_capacity(1 + len * 7 / 5);
|
||||||
|
|
||||||
|
let mut leading_zero_count = 0;
|
||||||
|
let mut leading_zeroes = true;
|
||||||
|
// Build string in little endian with 0-58 in place of characters...
|
||||||
|
for d256 in data {
|
||||||
|
let mut carry = d256 as usize;
|
||||||
|
if leading_zeroes && carry == 0 {
|
||||||
|
leading_zero_count += 1;
|
||||||
|
} else {
|
||||||
|
leading_zeroes = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for ch in ret.iter_mut() {
|
||||||
|
let new_ch = *ch as usize * 256 + carry;
|
||||||
|
*ch = (new_ch % 58) as u8;
|
||||||
|
carry = new_ch / 58;
|
||||||
|
}
|
||||||
|
while carry > 0 {
|
||||||
|
ret.push((carry % 58) as u8);
|
||||||
|
carry /= 58;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ... then reverse it and convert to chars
|
||||||
|
for _ in 0..leading_zero_count {
|
||||||
|
ret.push(0);
|
||||||
|
}
|
||||||
|
ret.reverse();
|
||||||
|
for ch in ret.iter_mut() {
|
||||||
|
*ch = BASE58_CHARS[*ch as usize];
|
||||||
|
}
|
||||||
String::from_utf8(ret).unwrap()
|
String::from_utf8(ret).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Trait for objects which can be written as base58
|
/// Directly encode a slice as base58
|
||||||
pub trait ToBase58 {
|
pub fn encode_slice(data: &[u8]) -> String {
|
||||||
/// The serialization to be converted into base58
|
encode_iter(data.iter().cloned())
|
||||||
fn base58_layout(&self) -> Vec<u8>;
|
|
||||||
|
|
||||||
/// Obtain a string with the base58 encoding of the object
|
|
||||||
fn to_base58(&self) -> String {
|
|
||||||
base58_encode_slice(&self.base58_layout()[..])
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Obtain a string with the base58check encoding of the object
|
|
||||||
/// (Tack the first 4 256-digits of the object's Bitcoin hash onto the end.)
|
|
||||||
fn to_base58check(&self) -> String {
|
|
||||||
let mut data = self.base58_layout();
|
|
||||||
let checksum = Sha256dHash::from_data(&data).into_le().low_u32();
|
|
||||||
data.write_u32::<LittleEndian>(checksum).unwrap();
|
|
||||||
base58_encode_slice(&data)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Trivial implementations for slices and vectors
|
/// Obtain a string with the base58check encoding of a slice
|
||||||
impl<'a> ToBase58 for &'a [u8] {
|
/// (Tack the first 4 256-digits of the object's Bitcoin hash onto the end.)
|
||||||
fn base58_layout(&self) -> Vec<u8> { self.to_vec() }
|
pub fn check_encode_slice(data: &[u8]) -> String {
|
||||||
fn to_base58(&self) -> String { base58_encode_slice(*self) }
|
let checksum = Sha256dHash::from_data(&data);
|
||||||
}
|
encode_iter(
|
||||||
|
data.iter()
|
||||||
impl<'a> ToBase58 for Vec<u8> {
|
.cloned()
|
||||||
fn base58_layout(&self) -> Vec<u8> { self.clone() }
|
.chain(checksum[0..4].iter().cloned())
|
||||||
fn to_base58(&self) -> String { base58_encode_slice(&self[..]) }
|
)
|
||||||
}
|
|
||||||
|
|
||||||
impl FromBase58 for Vec<u8> {
|
|
||||||
fn from_base58_layout(data: Vec<u8>) -> Result<Vec<u8>, Error> {
|
|
||||||
Ok(data)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use serialize::hex::FromHex;
|
use serialize::hex::FromHex;
|
||||||
|
|
||||||
use super::ToBase58;
|
use super::*;
|
||||||
use super::FromBase58;
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_base58_encode() {
|
fn test_base58_encode() {
|
||||||
// Basics
|
// Basics
|
||||||
assert_eq!(&(&[0][..]).to_base58(), "1");
|
assert_eq!(&encode_slice(&[0][..]), "1");
|
||||||
assert_eq!(&(&[1][..]).to_base58(), "2");
|
assert_eq!(&encode_slice(&[1][..]), "2");
|
||||||
assert_eq!(&(&[58][..]).to_base58(), "21");
|
assert_eq!(&encode_slice(&[58][..]), "21");
|
||||||
assert_eq!(&(&[13, 36][..]).to_base58(), "211");
|
assert_eq!(&encode_slice(&[13, 36][..]), "211");
|
||||||
|
|
||||||
// Leading zeroes
|
// Leading zeroes
|
||||||
assert_eq!(&(&[0, 13, 36][..]).to_base58(), "1211");
|
assert_eq!(&encode_slice(&[0, 13, 36][..]), "1211");
|
||||||
assert_eq!(&(&[0, 0, 0, 0, 13, 36][..]).to_base58(), "1111211");
|
assert_eq!(&encode_slice(&[0, 0, 0, 0, 13, 36][..]), "1111211");
|
||||||
|
|
||||||
// Addresses
|
// Addresses
|
||||||
assert_eq!(&"00f8917303bfa8ef24f292e8fa1419b20460ba064d".from_hex().unwrap().to_base58check(),
|
let addr = "00f8917303bfa8ef24f292e8fa1419b20460ba064d".from_hex().unwrap();
|
||||||
"1PfJpZsjreyVrqeoAfabrRwwjQyoSQMmHH");
|
assert_eq!(&check_encode_slice(&addr[..]), "1PfJpZsjreyVrqeoAfabrRwwjQyoSQMmHH");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_base58_decode() {
|
fn test_base58_decode() {
|
||||||
// Basics
|
// Basics
|
||||||
assert_eq!(FromBase58::from_base58("1").ok(), Some(vec![0u8]));
|
assert_eq!(from("1").ok(), Some(vec![0u8]));
|
||||||
assert_eq!(FromBase58::from_base58("2").ok(), Some(vec![1u8]));
|
assert_eq!(from("2").ok(), Some(vec![1u8]));
|
||||||
assert_eq!(FromBase58::from_base58("21").ok(), Some(vec![58u8]));
|
assert_eq!(from("21").ok(), Some(vec![58u8]));
|
||||||
assert_eq!(FromBase58::from_base58("211").ok(), Some(vec![13u8, 36]));
|
assert_eq!(from("211").ok(), Some(vec![13u8, 36]));
|
||||||
|
|
||||||
// Leading zeroes
|
// Leading zeroes
|
||||||
assert_eq!(FromBase58::from_base58("1211").ok(), Some(vec![0u8, 13, 36]));
|
assert_eq!(from("1211").ok(), Some(vec![0u8, 13, 36]));
|
||||||
assert_eq!(FromBase58::from_base58("111211").ok(), Some(vec![0u8, 0, 0, 13, 36]));
|
assert_eq!(from("111211").ok(), Some(vec![0u8, 0, 0, 13, 36]));
|
||||||
|
|
||||||
// Addresses
|
// Addresses
|
||||||
assert_eq!(FromBase58::from_base58check("1PfJpZsjreyVrqeoAfabrRwwjQyoSQMmHH").ok(),
|
assert_eq!(from_check("1PfJpZsjreyVrqeoAfabrRwwjQyoSQMmHH").ok(),
|
||||||
Some("00f8917303bfa8ef24f292e8fa1419b20460ba064d".from_hex().unwrap()))
|
Some("00f8917303bfa8ef24f292e8fa1419b20460ba064d".from_hex().unwrap()))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_base58_roundtrip() {
|
fn test_base58_roundtrip() {
|
||||||
let s = "xprv9wTYmMFdV23N2TdNG573QoEsfRrWKQgWeibmLntzniatZvR9BmLnvSxqu53Kw1UmYPxLgboyZQaXwTCg8MSY3H2EU4pWcQDnRnrVA1xe8fs";
|
let s = "xprv9wTYmMFdV23N2TdNG573QoEsfRrWKQgWeibmLntzniatZvR9BmLnvSxqu53Kw1UmYPxLgboyZQaXwTCg8MSY3H2EU4pWcQDnRnrVA1xe8fs";
|
||||||
let v: Vec<u8> = FromBase58::from_base58check(s).unwrap();
|
let v: Vec<u8> = from_check(s).unwrap();
|
||||||
assert_eq!(&v.to_base58check(), s);
|
assert_eq!(check_encode_slice(&v[..]), s);
|
||||||
assert_eq!(FromBase58::from_base58check(&v.to_base58check()).ok(), Some(v));
|
assert_eq!(from_check(&check_encode_slice(&v[..])).ok(), Some(v));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,9 +19,11 @@
|
||||||
use std::default::Default;
|
use std::default::Default;
|
||||||
use std::io::Cursor;
|
use std::io::Cursor;
|
||||||
use std::{error, fmt};
|
use std::{error, fmt};
|
||||||
|
use std::str::FromStr;
|
||||||
|
use std::string::ToString;
|
||||||
use serde::{Serialize, Deserialize, Serializer, Deserializer};
|
use serde::{Serialize, Deserialize, Serializer, Deserializer};
|
||||||
|
|
||||||
use byteorder::{BigEndian, ByteOrder, ReadBytesExt, WriteBytesExt};
|
use byteorder::{BigEndian, ByteOrder, ReadBytesExt};
|
||||||
use crypto::digest::Digest;
|
use crypto::digest::Digest;
|
||||||
use crypto::hmac::Hmac;
|
use crypto::hmac::Hmac;
|
||||||
use crypto::mac::Mac;
|
use crypto::mac::Mac;
|
||||||
|
@ -33,7 +35,6 @@ use secp256k1::{self, Secp256k1};
|
||||||
|
|
||||||
use network::constants::Network;
|
use network::constants::Network;
|
||||||
use util::base58;
|
use util::base58;
|
||||||
use util::base58::{FromBase58, ToBase58};
|
|
||||||
|
|
||||||
/// A chain code
|
/// A chain code
|
||||||
pub struct ChainCode([u8; 32]);
|
pub struct ChainCode([u8; 32]);
|
||||||
|
@ -337,33 +338,36 @@ impl ExtendedPubKey {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToBase58 for ExtendedPrivKey {
|
impl ToString for ExtendedPrivKey {
|
||||||
fn base58_layout(&self) -> Vec<u8> {
|
fn to_string(&self) -> String {
|
||||||
let mut ret = Vec::with_capacity(78);
|
let mut ret = [0; 78];
|
||||||
ret.extend(match self.network {
|
ret[0..4].copy_from_slice(&match self.network {
|
||||||
Network::Bitcoin => [0x04, 0x88, 0xAD, 0xE4],
|
Network::Bitcoin => [0x04, 0x88, 0xAD, 0xE4],
|
||||||
Network::Testnet => [0x04, 0x35, 0x83, 0x94]
|
Network::Testnet => [0x04, 0x35, 0x83, 0x94],
|
||||||
}.iter().cloned());
|
}[..]);
|
||||||
ret.push(self.depth as u8);
|
ret[4] = self.depth as u8;
|
||||||
ret.extend(self.parent_fingerprint[..].iter().cloned());
|
ret[5..9].copy_from_slice(&self.parent_fingerprint[..]);
|
||||||
match self.child_number {
|
match self.child_number {
|
||||||
ChildNumber::Hardened(n) => {
|
ChildNumber::Hardened(n) => {
|
||||||
ret.write_u32::<BigEndian>(n + (1 << 31)).unwrap();
|
BigEndian::write_u32(&mut ret[9..13], n + (1 << 31));
|
||||||
}
|
}
|
||||||
ChildNumber::Normal(n) => {
|
ChildNumber::Normal(n) => {
|
||||||
ret.write_u32::<BigEndian>(n).unwrap();
|
BigEndian::write_u32(&mut ret[9..13], n);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ret.extend(self.chain_code[..].iter().cloned());
|
ret[13..45].copy_from_slice(&self.chain_code[..]);
|
||||||
ret.push(0);
|
ret[45] = 0;
|
||||||
ret.extend(self.secret_key[..].iter().cloned());
|
ret[46..78].copy_from_slice(&self.secret_key[..]);
|
||||||
ret
|
base58::check_encode_slice(&ret[..])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromBase58 for ExtendedPrivKey {
|
impl FromStr for ExtendedPrivKey {
|
||||||
fn from_base58_layout(data: Vec<u8>) -> Result<ExtendedPrivKey, base58::Error> {
|
type Err = base58::Error;
|
||||||
|
|
||||||
|
fn from_str(inp: &str) -> Result<ExtendedPrivKey, base58::Error> {
|
||||||
let s = Secp256k1::with_caps(secp256k1::ContextFlag::None);
|
let s = Secp256k1::with_caps(secp256k1::ContextFlag::None);
|
||||||
|
let data = try!(base58::from_check(inp));
|
||||||
|
|
||||||
if data.len() != 78 {
|
if data.len() != 78 {
|
||||||
return Err(base58::Error::InvalidLength(data.len()));
|
return Err(base58::Error::InvalidLength(data.len()));
|
||||||
|
@ -386,40 +390,41 @@ impl FromBase58 for ExtendedPrivKey {
|
||||||
child_number: child_number,
|
child_number: child_number,
|
||||||
chain_code: ChainCode::from(&data[13..45]),
|
chain_code: ChainCode::from(&data[13..45]),
|
||||||
secret_key: try!(SecretKey::from_slice(&s,
|
secret_key: try!(SecretKey::from_slice(&s,
|
||||||
&data[46..78]).map_err(|e|
|
&data[46..78]).map_err(|e|
|
||||||
base58::Error::Other(e.to_string())))
|
base58::Error::Other(e.to_string())))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToBase58 for ExtendedPubKey {
|
impl ToString for ExtendedPubKey {
|
||||||
fn base58_layout(&self) -> Vec<u8> {
|
fn to_string(&self) -> String {
|
||||||
let mut ret = Vec::with_capacity(78);
|
let mut ret = [0; 78];
|
||||||
ret.extend(match self.network {
|
ret[0..4].copy_from_slice(&match self.network {
|
||||||
Network::Bitcoin => [0x04u8, 0x88, 0xB2, 0x1E],
|
Network::Bitcoin => [0x04u8, 0x88, 0xB2, 0x1E],
|
||||||
Network::Testnet => [0x04u8, 0x35, 0x87, 0xCF]
|
Network::Testnet => [0x04u8, 0x35, 0x87, 0xCF],
|
||||||
}.iter().cloned());
|
}[..]);
|
||||||
ret.push(self.depth as u8);
|
ret[4] = self.depth as u8;
|
||||||
ret.extend(self.parent_fingerprint[..].iter().cloned());
|
ret[5..9].copy_from_slice(&self.parent_fingerprint[..]);
|
||||||
let mut be_n = [0; 4];
|
|
||||||
match self.child_number {
|
match self.child_number {
|
||||||
ChildNumber::Hardened(n) => {
|
ChildNumber::Hardened(n) => {
|
||||||
BigEndian::write_u32(&mut be_n, n + (1 << 31));
|
BigEndian::write_u32(&mut ret[9..13], n + (1 << 31));
|
||||||
}
|
}
|
||||||
ChildNumber::Normal(n) => {
|
ChildNumber::Normal(n) => {
|
||||||
BigEndian::write_u32(&mut be_n, n);
|
BigEndian::write_u32(&mut ret[9..13], n);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ret.extend(be_n.iter().cloned());
|
ret[13..45].copy_from_slice(&self.chain_code[..]);
|
||||||
ret.extend(self.chain_code[..].iter().cloned());
|
ret[45..78].copy_from_slice(&self.public_key.serialize()[..]);
|
||||||
ret.extend(self.public_key.serialize()[..].iter().cloned());
|
base58::check_encode_slice(&ret[..])
|
||||||
ret
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromBase58 for ExtendedPubKey {
|
impl FromStr for ExtendedPubKey {
|
||||||
fn from_base58_layout(data: Vec<u8>) -> Result<ExtendedPubKey, base58::Error> {
|
type Err = base58::Error;
|
||||||
|
|
||||||
|
fn from_str(inp: &str) -> Result<ExtendedPubKey, base58::Error> {
|
||||||
let s = Secp256k1::with_caps(secp256k1::ContextFlag::None);
|
let s = Secp256k1::with_caps(secp256k1::ContextFlag::None);
|
||||||
|
let data = try!(base58::from_check(inp));
|
||||||
|
|
||||||
if data.len() != 78 {
|
if data.len() != 78 {
|
||||||
return Err(base58::Error::InvalidLength(data.len()));
|
return Err(base58::Error::InvalidLength(data.len()));
|
||||||
|
@ -442,19 +447,21 @@ impl FromBase58 for ExtendedPubKey {
|
||||||
child_number: child_number,
|
child_number: child_number,
|
||||||
chain_code: ChainCode::from(&data[13..45]),
|
chain_code: ChainCode::from(&data[13..45]),
|
||||||
public_key: try!(PublicKey::from_slice(&s,
|
public_key: try!(PublicKey::from_slice(&s,
|
||||||
&data[45..78]).map_err(|e|
|
&data[45..78]).map_err(|e|
|
||||||
base58::Error::Other(e.to_string())))
|
base58::Error::Other(e.to_string())))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use std::str::FromStr;
|
||||||
|
use std::string::ToString;
|
||||||
|
|
||||||
use secp256k1::Secp256k1;
|
use secp256k1::Secp256k1;
|
||||||
use serialize::hex::FromHex;
|
use serialize::hex::FromHex;
|
||||||
|
|
||||||
use network::constants::Network::{self, Bitcoin};
|
use network::constants::Network::{self, Bitcoin};
|
||||||
use util::base58::{FromBase58, ToBase58};
|
|
||||||
|
|
||||||
use super::{ChildNumber, ExtendedPrivKey, ExtendedPubKey};
|
use super::{ChildNumber, ExtendedPrivKey, ExtendedPubKey};
|
||||||
use super::ChildNumber::{Hardened, Normal};
|
use super::ChildNumber::{Hardened, Normal};
|
||||||
|
@ -484,11 +491,11 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check result against expected base58
|
// Check result against expected base58
|
||||||
assert_eq!(&sk.to_base58check()[..], expected_sk);
|
assert_eq!(&sk.to_string()[..], expected_sk);
|
||||||
assert_eq!(&pk.to_base58check()[..], expected_pk);
|
assert_eq!(&pk.to_string()[..], expected_pk);
|
||||||
// Check decoded base58 against result
|
// Check decoded base58 against result
|
||||||
let decoded_sk = FromBase58::from_base58check(expected_sk);
|
let decoded_sk = ExtendedPrivKey::from_str(expected_sk);
|
||||||
let decoded_pk = FromBase58::from_base58check(expected_pk);
|
let decoded_pk = ExtendedPubKey::from_str(expected_pk);
|
||||||
assert_eq!(Ok(sk), decoded_sk);
|
assert_eq!(Ok(sk), decoded_sk);
|
||||||
assert_eq!(Ok(pk), decoded_pk);
|
assert_eq!(Ok(pk), decoded_pk);
|
||||||
}
|
}
|
||||||
|
|
|
@ -295,7 +295,6 @@ mod tests {
|
||||||
|
|
||||||
use blockdata::script::Script;
|
use blockdata::script::Script;
|
||||||
use network::constants::Network;
|
use network::constants::Network;
|
||||||
use util::base58::ToBase58;
|
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
@ -320,7 +319,7 @@ mod tests {
|
||||||
let contract = hex!("5032534894ffbf32c1f1c0d3089b27c98fd991d5d7329ebd7d711223e2cde5a9417a1fa3e852c576");
|
let contract = hex!("5032534894ffbf32c1f1c0d3089b27c98fd991d5d7329ebd7d711223e2cde5a9417a1fa3e852c576");
|
||||||
|
|
||||||
let addr = create_address(&secp, Network::Testnet, &contract, keys, &alpha_template!()).unwrap();
|
let addr = create_address(&secp, Network::Testnet, &contract, keys, &alpha_template!()).unwrap();
|
||||||
assert_eq!(addr.to_base58check(), "2N3zXjbwdTcPsJiy8sUK9FhWJhqQCxA8Jjr".to_owned());
|
assert_eq!(addr.to_string(), "2N3zXjbwdTcPsJiy8sUK9FhWJhqQCxA8Jjr".to_owned());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
Loading…
Reference in New Issue