Skip to content

Commit

Permalink
Add FromHex for Hash, Hash8, PaymentId and Address; add ToHex for Add…
Browse files Browse the repository at this point in the history
…ress (#114)

* feat(hash): implement FromHex for Hash and Hash8

* feat(address): implement FromHex for PaymentId

* feat(address): implement ToHex and FromHex for Address

* docs(CHANGELOG): update CHANGELOG to reflect recent changes

* fix(changelog): put new changes in the Unreleased section
  • Loading branch information
LeoNero committed Jul 27, 2022
1 parent 24fb282 commit 631a460
Show file tree
Hide file tree
Showing 3 changed files with 169 additions and 2 deletions.
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- Change `u64` and `VarInt` to `Amount` where it makes sense to by [@LeoNero](https://github.com/LeoNero) ([#113](https://github.com/monero-rs/monero-rs/pull/113))
- Add `FromHex` for `Hash`, `Hash8`, `PaymentId`, and `Address` [@LeoNero](https://github.com/LeoNero) ([#114](https://github.com/monero-rs/monero-rs/pull/114))
- Add `ToHex` for `Address` [@LeoNero](https://github.com/LeoNero) ([#114](https://github.com/monero-rs/monero-rs/pull/114))

## [0.17.2] - 2022-07-19

### Added

- Add serde `serialize` for `Amount` and `SignedAmount` slices by [@LeoNero](https://github.com/LeoNero) ([#107](https://github.com/monero-rs/monero-rs/pull/107))
- Add serde `deserialize` for `Amount` and `SignedAmount` vectors by [@LeoNero](https://github.com/LeoNero) ([#107](https://github.com/monero-rs/monero-rs/pull/107))
- Change `u64` and `VarInt` to `Amount` where it makes sense to by [@LeoNero](https://github.com/LeoNero) ([#113](https://github.com/monero-rs/monero-rs/pull/113))

## [0.17.1] - 2022-07-12

Expand Down
74 changes: 73 additions & 1 deletion src/cryptonote/hash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,18 @@ impl Decodable for Hash {
}
}

impl hex::FromHex for Hash {
type Error = hex::FromHexError;

fn from_hex<T: AsRef<[u8]>>(hex: T) -> Result<Self, Self::Error> {
let hex = hex.as_ref();
let hex = hex.strip_prefix("0x".as_bytes()).unwrap_or(hex);

let buffer = <[u8; 32]>::from_hex(hex)?;
Ok(Hash(buffer))
}
}

#[sealed]
impl crate::consensus::encode::Encodable for Hash {
fn consensus_encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<usize, io::Error> {
Expand Down Expand Up @@ -111,6 +123,18 @@ impl Decodable for Hash8 {
}
}

impl hex::FromHex for Hash8 {
type Error = hex::FromHexError;

fn from_hex<T: AsRef<[u8]>>(hex: T) -> Result<Self, Self::Error> {
let hex = hex.as_ref();
let hex = hex.strip_prefix("0x".as_bytes()).unwrap_or(hex);

let buffer = <[u8; 8]>::from_hex(hex)?;
Ok(Hash8(buffer))
}
}

#[sealed]
impl crate::consensus::encode::Encodable for Hash8 {
fn consensus_encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<usize, io::Error> {
Expand All @@ -131,9 +155,11 @@ pub fn keccak_256(input: &[u8]) -> [u8; 32] {

#[cfg(test)]
mod tests {
#[cfg(feature = "serde")]
use std::str::FromStr;

use super::*;

use hex::{FromHex, FromHexError, ToHex};
#[cfg(feature = "serde")]
use serde_test::{assert_tokens, Token};

Expand Down Expand Up @@ -249,4 +275,50 @@ mod tests {
],
);
}

#[test]
fn test_to_from_hex_hash() {
let hash_wrong_length_str = "abcd";
assert_eq!(
Hash::from_hex(hash_wrong_length_str).unwrap_err(),
FromHexError::InvalidStringLength,
);

let hash_wrong_length_str = "a".repeat(66);
assert_eq!(
Hash::from_hex(hash_wrong_length_str).unwrap_err(),
FromHexError::InvalidStringLength,
);

let hash = Hash::new("");

let hash_str: String = hash.encode_hex();
assert_eq!(Hash::from_hex(hash_str.clone()).unwrap(), hash);

let hash_str_with_0x = format!("0x{hash_str}");
assert_eq!(Hash::from_hex(hash_str_with_0x).unwrap(), hash);
}

#[test]
fn test_to_from_hex_hash8() {
let hash8_wrong_length_str = "abcd";
assert_eq!(
Hash8::from_hex(hash8_wrong_length_str).unwrap_err(),
FromHexError::InvalidStringLength,
);

let hash8_wrong_length_str = "a".repeat(10);
assert_eq!(
Hash8::from_hex(hash8_wrong_length_str).unwrap_err(),
FromHexError::InvalidStringLength,
);

let hash8 = Hash8::from_str("0123456789abcdef").unwrap();

let hash8_str: String = hash8.encode_hex();
assert_eq!(Hash8::from_hex(hash8_str.clone()).unwrap(), hash8);

let hash8_str_with_0x = format!("0x{hash8_str}");
assert_eq!(Hash8::from_hex(hash8_str_with_0x).unwrap(), hash8);
}
}
90 changes: 90 additions & 0 deletions src/util/address.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,18 @@ fixed_hash::construct_fixed_hash! {
pub struct PaymentId(8);
}

impl hex::FromHex for PaymentId {
type Error = hex::FromHexError;

fn from_hex<T: AsRef<[u8]>>(hex: T) -> Result<Self, Self::Error> {
let hex = hex.as_ref();
let hex = hex.strip_prefix("0x".as_bytes()).unwrap_or(hex);

let buffer = <[u8; 8]>::from_hex(hex)?;
Ok(PaymentId(buffer))
}
}

/// A complete Monero typed address valid for a specific network.
#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)]
pub struct Address {
Expand Down Expand Up @@ -268,6 +280,27 @@ impl Address {
}
}

impl hex::ToHex for Address {
fn encode_hex<T: std::iter::FromIterator<char>>(&self) -> T {
self.as_bytes().encode_hex()
}

fn encode_hex_upper<T: std::iter::FromIterator<char>>(&self) -> T {
self.as_bytes().encode_hex_upper()
}
}

impl hex::FromHex for Address {
type Error = Error;

fn from_hex<T: AsRef<[u8]>>(hex: T) -> Result<Self, Self::Error> {
let hex = hex.as_ref();
let hex = hex.strip_prefix("0x".as_bytes()).unwrap_or(hex);
let bytes = hex::decode(hex).map_err(|_| Self::Error::InvalidFormat)?;
Self::from_bytes(&bytes)
}
}

impl fmt::Display for Address {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", base58::encode(self.as_bytes().as_slice()).unwrap())
Expand Down Expand Up @@ -326,6 +359,8 @@ impl crate::consensus::encode::Encodable for Address {

#[cfg(test)]
mod tests {
use hex::{FromHex, FromHexError, ToHex};

use crate::consensus::encode::{Decodable, Encodable};
use std::str::FromStr;

Expand Down Expand Up @@ -444,4 +479,59 @@ mod tests {
let add = Address::from_str(address).unwrap();
assert_eq!(address, add.to_string());
}

#[test]
fn test_to_from_hex_payment_id() {
let payment_id_wrong_length_str = "abcd";
assert_eq!(
PaymentId::from_hex(payment_id_wrong_length_str).unwrap_err(),
FromHexError::InvalidStringLength,
);

let payment_id_wrong_length_str = "a".repeat(10);
assert_eq!(
PaymentId::from_hex(payment_id_wrong_length_str).unwrap_err(),
FromHexError::InvalidStringLength,
);

let payment_id = PaymentId::from_str("0123456789abcdef").unwrap();

let payment_id_str: String = payment_id.encode_hex();
assert_eq!(
PaymentId::from_hex(payment_id_str.clone()).unwrap(),
payment_id
);

let payment_id_str_with_0x = format!("0x{payment_id_str}");
assert_eq!(
PaymentId::from_hex(payment_id_str_with_0x).unwrap(),
payment_id
);
}

#[test]
fn test_address_hex() {
let address_str = "4Byr22j9M2878Mtyb3fEPcBNwBZf5EXqn1Yi6VzR46618SFBrYysab2Cs1474CVDbsh94AJq7vuV3Z2DRq4zLcY3LHzo1Nbv3d8J6VhvCV";

let address_bytes = [
19, 17, 81, 127, 230, 166, 35, 81, 36, 161, 94, 154, 206, 60, 98, 195, 62, 12, 11, 234,
133, 228, 196, 77, 3, 68, 188, 84, 78, 94, 109, 238, 44, 115, 212, 211, 204, 198, 30,
73, 70, 235, 52, 160, 200, 39, 215, 134, 239, 249, 129, 47, 156, 14, 116, 18, 191, 112,
207, 139, 208, 54, 59, 92, 115, 88, 118, 184, 183, 41, 150, 255, 151, 133, 45, 85, 110,
];
let address_hex_lower = hex::encode(address_bytes);
let address_hex_lower_with_0x = format!("0x{}", address_hex_lower);
let address_hex_upper = hex::encode_upper(address_bytes);

let address = Address::from_str(address_str).unwrap();

assert_eq!(address.as_hex(), address_hex_lower);
assert_eq!(address.encode_hex::<String>(), address_hex_lower);
assert_eq!(address.encode_hex_upper::<String>(), address_hex_upper);

let address_from_hex = Address::from_hex(address_hex_lower).unwrap();
assert_eq!(address_from_hex, address);
let address_from_hex_with_0x = Address::from_hex(address_hex_lower_with_0x).unwrap();
assert_eq!(address_from_hex_with_0x, address);
}
}

0 comments on commit 631a460

Please sign in to comment.