use std::{
convert::TryInto,
fmt,
io::{self, Cursor, Read},
str::FromStr,
};
use crate::{
crypto::{PrivateKey, PrivateKeyError, PublicKey},
hash::{double_sha512, ripemd160_sha512, sha512},
io::{ReadFrom, WriteTo},
object::Tag,
priv_util::ToHexString,
};
pub use crate::stream::StreamNumber as Stream;
pub type Version = crate::object::ObjectVersion;
pub type Hash = crate::hash::Hash160;
#[derive(Clone, Debug)]
pub enum Error {
UnsupportedVersion(Version),
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::UnsupportedVersion(version) => write!(f, "unsupported version: {}", version),
}
}
}
impl std::error::Error for Error {}
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub struct Address {
version: Version,
stream: Stream,
hash: Hash,
}
pub(crate) fn count_zeros(bytes: impl AsRef<[u8]>) -> usize {
let mut count = 0;
for _ in bytes.as_ref().iter().take_while(|b| **b == 0) {
count += 1;
}
count
}
pub(crate) const DEFAULT_ZEROS: usize = 1;
impl Address {
pub fn from_public_keys(
version: Version,
stream: Stream,
public_signing_key: &PublicKey,
public_encryption_key: &PublicKey,
) -> Result<Self, Error> {
if version.as_u64() < 2 || version.as_u64() > 4 {
return Err(Error::UnsupportedVersion(version));
}
let mut bytes = Vec::new();
bytes.append(&mut public_signing_key.encode());
bytes.append(&mut public_encryption_key.encode());
let hash = ripemd160_sha512(bytes);
Ok(Self {
version,
stream,
hash,
})
}
pub fn version(&self) -> Version {
self.version
}
pub fn stream(&self) -> Stream {
self.stream
}
pub fn hash(&self) -> &Hash {
&self.hash
}
pub fn broadcast_tag(&self) -> Tag {
let mut bytes = Vec::new();
self.version.write_to(&mut bytes).unwrap();
self.stream.write_to(&mut bytes).unwrap();
self.hash.as_ref().write_to(&mut bytes).unwrap();
Tag::new(double_sha512(bytes)[32..].try_into().unwrap())
}
pub fn broadcast_private_encryption_key(&self) -> Result<PrivateKey, PrivateKeyError> {
let mut bytes = Vec::new();
self.version.write_to(&mut bytes).unwrap();
self.stream.write_to(&mut bytes).unwrap();
self.hash.as_ref().write_to(&mut bytes).unwrap();
let hash = if self.version.as_u64() <= 3 {
sha512(bytes)[..32].try_into().unwrap()
} else {
double_sha512(bytes)[..32].try_into().unwrap()
};
PrivateKey::new(hash)
}
}
impl fmt::Display for Address {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut bytes = Vec::new();
self.version.write_to(&mut bytes).unwrap();
self.stream.write_to(&mut bytes).unwrap();
let hash = match self.version.as_u64() {
1 => &self.hash[..],
2 | 3 => &self.hash[std::cmp::min(count_zeros(&self.hash), 2)..],
4 => &self.hash[count_zeros(&self.hash)..],
_ => panic!("program error: unsupported version: {}", self.version),
};
hash.write_to(&mut bytes).unwrap();
let checksum = &double_sha512(&bytes)[..4];
checksum.write_to(&mut bytes).unwrap();
write!(f, "BM-{}", koibumi_base58::encode(bytes))
}
}
#[derive(Debug)]
pub enum ParseError {
Base58Error(koibumi_base58::InvalidCharacter),
TooShort {
min: usize,
len: usize,
},
InvalidChecksum {
expected: [u8; 4],
actual: [u8; 4],
},
IoError(io::Error),
UnsupportedVersion(Version),
InvalidHashLength {
min: usize,
max: usize,
len: usize,
},
HashStartsWithZero,
}
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Base58Error(err) => err.fmt(f),
Self::InvalidChecksum { expected, actual } => write!(
f,
"checksum should be {}, but {}",
expected.as_ref().to_hex_string(),
actual.as_ref().to_hex_string()
),
Self::IoError(err) => err.fmt(f),
Self::UnsupportedVersion(version) => write!(f, "unsupported version: {}", version),
Self::TooShort { min, len } => write!(f, "length must be >={}, but {}", min, len),
Self::InvalidHashLength { min, max, len } => write!(
f,
"invalid hash length: min={}, max={}, len={}",
min, max, len
),
Self::HashStartsWithZero => "hash starts with zero".fmt(f),
}
}
}
impl std::error::Error for ParseError {}
impl From<koibumi_base58::InvalidCharacter> for ParseError {
fn from(err: koibumi_base58::InvalidCharacter) -> Self {
Self::Base58Error(err)
}
}
impl From<io::Error> for ParseError {
fn from(err: io::Error) -> Self {
Self::IoError(err)
}
}
pub(crate) const VERSION: Version = Version::new(4);
fn pad_zeros(hash: Vec<u8>) -> Vec<u8> {
const HASH_LEN: usize = 20;
let mut bytes = [0; HASH_LEN];
bytes[HASH_LEN - hash.len()..].copy_from_slice(&hash);
bytes.to_vec()
}
impl FromStr for Address {
type Err = ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let s = if s.len() >= 3 && &s[..3] == "BM-" {
&s[3..]
} else {
&s[..]
};
let bytes = koibumi_base58::decode(s)?;
if bytes.len() < 4 {
return Err(ParseError::TooShort {
min: 4,
len: bytes.len(),
});
}
let checksum = &bytes[bytes.len() - 4..];
let body = &bytes[..bytes.len() - 4];
let double_hash = double_sha512(body);
let actual_checksum = &double_hash[..4];
if actual_checksum != checksum {
return Err(ParseError::InvalidChecksum {
expected: checksum.try_into().unwrap(),
actual: actual_checksum.try_into().unwrap(),
});
}
let mut bytes = Cursor::new(body);
let version = Version::read_from(&mut bytes)?;
if version.as_u64() < 1 || version.as_u64() > 4 {
return Err(ParseError::UnsupportedVersion(version));
}
let stream = Stream::read_from(&mut bytes)?;
let mut hash = Vec::new();
bytes.read_to_end(&mut hash)?;
match version.as_u64() {
1 => {
if hash.len() != 20 {
return Err(ParseError::InvalidHashLength {
min: 20,
max: 20,
len: hash.len(),
});
}
Ok(Address {
version,
stream,
hash: Hash::new(hash[..].try_into().unwrap()),
})
}
2 | 3 => {
if hash.len() < 18 || hash.len() > 20 {
return Err(ParseError::InvalidHashLength {
min: 18,
max: 20,
len: hash.len(),
});
}
let hash = pad_zeros(hash);
Ok(Address {
version,
stream,
hash: Hash::new(hash[..].try_into().unwrap()),
})
}
4 => {
if hash.len() < 4 || hash.len() > 20 {
return Err(ParseError::InvalidHashLength {
min: 4,
max: 20,
len: hash.len(),
});
}
if hash[0] == 0 {
return Err(ParseError::HashStartsWithZero);
}
let hash = pad_zeros(hash);
Ok(Address {
version,
stream,
hash: Hash::new(hash[..].try_into().unwrap()),
})
}
_ => panic!("program error: unsupported version: {}", version),
}
}
}
#[test]
fn test_address_from_str() {
let address = "BM-NC1qK8oQk3yya1ZfHt368uXQq9W6U2om"
.parse::<Address>()
.unwrap();
assert_eq!(address.version(), 4.into());
assert_eq!(address.stream(), 1.into());
}