From 3ae48e24a7e7f7aba9e121212186e9bb23177258 Mon Sep 17 00:00:00 2001 From: jowparks Date: Fri, 18 Oct 2024 13:16:27 -0700 Subject: [PATCH] hex encode/decode pushed to ironfish-zkp (#5561) --- ironfish-rust/src/errors.rs | 7 ++ ironfish-rust/src/serializing/mod.rs | 109 +---------------------- ironfish-zkp/src/hex.rs | 128 +++++++++++++++++++++++++++ ironfish-zkp/src/lib.rs | 1 + 4 files changed, 137 insertions(+), 108 deletions(-) create mode 100644 ironfish-zkp/src/hex.rs diff --git a/ironfish-rust/src/errors.rs b/ironfish-rust/src/errors.rs index 0de86e44fc..5406264073 100644 --- a/ironfish-rust/src/errors.rs +++ b/ironfish-rust/src/errors.rs @@ -33,6 +33,7 @@ pub enum IronfishErrorKind { FailedXChaCha20Poly1305Decryption, FailedXChaCha20Poly1305Encryption, FailedHkdfExpansion, + HexError, IllegalValue, InconsistentWitness, InvalidAssetIdentifier, @@ -145,3 +146,9 @@ impl From for IronfishError { IronfishError::new_with_source(IronfishErrorKind::FrostLibError, e) } } + +impl From for IronfishError { + fn from(e: ironfish_zkp::hex::HexError) -> IronfishError { + IronfishError::new_with_source(IronfishErrorKind::HexError, e) + } +} diff --git a/ironfish-rust/src/serializing/mod.rs b/ironfish-rust/src/serializing/mod.rs index 17c671c1e7..2e1b76960e 100644 --- a/ironfish-rust/src/serializing/mod.rs +++ b/ironfish-rust/src/serializing/mod.rs @@ -5,6 +5,7 @@ pub mod aead; pub mod fr; use crate::errors::{IronfishError, IronfishErrorKind}; +pub use ironfish_zkp::hex::{bytes_to_hex, hex_to_bytes, hex_to_vec_bytes}; /// Helper functions to convert pairing parts to bytes /// @@ -16,8 +17,6 @@ use group::GroupEncoding; use std::io; -const HEX_CHARS: &[u8; 16] = b"0123456789abcdef"; - pub(crate) fn read_scalar(mut reader: R) -> Result { let mut fr_repr = F::Repr::default(); reader.read_exact(fr_repr.as_mut())?; @@ -43,109 +42,3 @@ pub(crate) fn read_point_unchecked( Option::from(G::from_bytes_unchecked(&point_repr)) .ok_or_else(|| IronfishError::new(IronfishErrorKind::InvalidData)) } - -/// Output the bytes as a hexadecimal String -pub fn bytes_to_hex(bytes: &[u8]) -> String { - let mut hex: Vec = vec![0; bytes.len() * 2]; - - for (i, b) in bytes.iter().enumerate() { - hex[i * 2] = HEX_CHARS[(b >> 4) as usize]; - hex[i * 2 + 1] = HEX_CHARS[(b & 0x0f) as usize]; - } - - unsafe { String::from_utf8_unchecked(hex) } -} - -/// Output the hexadecimal String as bytes -pub fn hex_to_bytes(hex: &str) -> Result<[u8; SIZE], IronfishError> { - if hex.len() != SIZE * 2 { - return Err(IronfishError::new(IronfishErrorKind::InvalidData)); - } - - let mut bytes = [0; SIZE]; - - let hex_iter = hex.as_bytes().chunks_exact(2); - - for (i, hex) in hex_iter.enumerate() { - bytes[i] = hex_to_u8(hex[0])? << 4 | hex_to_u8(hex[1])?; - } - - Ok(bytes) -} - -pub fn hex_to_vec_bytes(hex: &str) -> Result, IronfishError> { - if hex.len() % 2 != 0 { - return Err(IronfishError::new(IronfishErrorKind::InvalidData)); - } - - let mut bytes = Vec::new(); - - let hex_iter = hex.as_bytes().chunks_exact(2); - - for hex in hex_iter { - bytes.push(hex_to_u8(hex[0])? << 4 | hex_to_u8(hex[1])?); - } - - Ok(bytes) -} - -#[inline] -fn hex_to_u8(char: u8) -> Result { - match char { - b'0'..=b'9' => Ok(char - b'0'), - b'a'..=b'f' => Ok(char - b'a' + 10), - b'A'..=b'F' => Ok(char - b'A' + 10), - _ => Err(IronfishError::new(IronfishErrorKind::InvalidData)), - } -} - -#[cfg(test)] -mod test { - use crate::serializing::{bytes_to_hex, hex_to_bytes, hex_to_vec_bytes}; - - #[test] - fn test_hex_to_vec_bytes_valid() { - let hex = "A1B2C3"; - let expected_bytes = vec![161, 178, 195]; - - let result = hex_to_vec_bytes(hex).expect("valid hex"); - - assert_eq!(result, expected_bytes); - } - - #[test] - fn test_hex_to_vec_bytes_invalid_char() { - let hex = "A1B2G3"; - hex_to_vec_bytes(hex).expect_err("invalid hex should throw an error"); - } - - #[test] - fn test_hex_to_vec_bytes_invalid_hex_with_odd_length() { - let hex = "A1B2C"; - hex_to_vec_bytes(hex).expect_err("invalid hex should throw an error"); - } - - #[test] - fn hex_serde() { - const HEX_STRING: &str = "68656C6C6F20776F726C6420616E64207374756666"; - const HEX_LOWER: &str = "68656c6c6f20776f726c6420616e64207374756666"; - const BYTE_LENGTH: usize = HEX_STRING.len() / 2; - // Same as above with the last character removed, which makes the hex - // invalid as the length of a hex string must be divisible by 2 - const INVALID_HEX: &str = "68656C6C6F20776F726C6420616E6420737475666"; - - hex_to_bytes::(INVALID_HEX).expect_err("invalid hex should throw an error"); - - let bytes: [u8; BYTE_LENGTH] = hex_to_bytes(HEX_STRING).expect("converts hex to bytes"); - let lower_bytes: [u8; BYTE_LENGTH] = - hex_to_bytes(HEX_STRING).expect("converts hex to bytes"); - - assert_eq!(bytes, lower_bytes); - - let hex = bytes_to_hex(&bytes); - let lower_hex = bytes_to_hex(&lower_bytes); - - assert_eq!(HEX_LOWER, hex); - assert_eq!(HEX_LOWER, lower_hex); - } -} diff --git a/ironfish-zkp/src/hex.rs b/ironfish-zkp/src/hex.rs new file mode 100644 index 0000000000..14fbc34209 --- /dev/null +++ b/ironfish-zkp/src/hex.rs @@ -0,0 +1,128 @@ +use std::fmt; + +const HEX_CHARS: &[u8; 16] = b"0123456789abcdef"; + +#[derive(Debug)] +pub enum HexError { + ByteLengthMismatch, + InvalidCharacter, + OddStringLength, +} + +impl fmt::Display for HexError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + HexError::ByteLengthMismatch => write!(f, "Byte length mismatch"), + HexError::InvalidCharacter => write!(f, "Invalid character"), + HexError::OddStringLength => write!(f, "Odd string length"), + } + } +} + +impl std::error::Error for HexError {} + +/// Output the bytes as a hexadecimal String +pub fn bytes_to_hex(bytes: &[u8]) -> String { + let mut hex: Vec = vec![0; bytes.len() * 2]; + + for (i, b) in bytes.iter().enumerate() { + hex[i * 2] = HEX_CHARS[(b >> 4) as usize]; + hex[i * 2 + 1] = HEX_CHARS[(b & 0x0f) as usize]; + } + + unsafe { String::from_utf8_unchecked(hex) } +} + +/// Output the hexadecimal String as bytes +pub fn hex_to_bytes(hex: &str) -> Result<[u8; SIZE], HexError> { + if hex.len() != SIZE * 2 { + return Err(HexError::ByteLengthMismatch); + } + + let mut bytes = [0; SIZE]; + + let hex_iter = hex.as_bytes().chunks_exact(2); + + for (i, hex) in hex_iter.enumerate() { + bytes[i] = hex_to_u8(hex[0])? << 4 | hex_to_u8(hex[1])?; + } + + Ok(bytes) +} + +pub fn hex_to_vec_bytes(hex: &str) -> Result, HexError> { + if hex.len() % 2 != 0 { + return Err(HexError::OddStringLength); + } + + let mut bytes = Vec::new(); + + let hex_iter = hex.as_bytes().chunks_exact(2); + + for hex in hex_iter { + bytes.push(hex_to_u8(hex[0])? << 4 | hex_to_u8(hex[1])?); + } + + Ok(bytes) +} + +#[inline] +fn hex_to_u8(char: u8) -> Result { + match char { + b'0'..=b'9' => Ok(char - b'0'), + b'a'..=b'f' => Ok(char - b'a' + 10), + b'A'..=b'F' => Ok(char - b'A' + 10), + _ => Err(HexError::InvalidCharacter), + } +} + +#[cfg(test)] +mod test { + use crate::hex::{bytes_to_hex, hex_to_bytes, hex_to_vec_bytes}; + + #[test] + fn test_hex_to_vec_bytes_valid() { + let hex = "A1B2C3"; + let expected_bytes = vec![161, 178, 195]; + + let result = hex_to_vec_bytes(hex).expect("valid hex"); + + assert_eq!(result, expected_bytes); + } + + #[test] + fn test_hex_to_vec_bytes_invalid_char() { + let hex = "A1B2G3"; + hex_to_vec_bytes(hex).expect_err("invalid hex should throw an error"); + } + + #[test] + fn test_hex_to_vec_bytes_invalid_hex_with_odd_length() { + let hex = "A1B2C"; + hex_to_vec_bytes(hex).expect_err("invalid hex should throw an error"); + } + + #[test] + fn hex_serde() { + const HEX_STRING: &str = "68656C6C6F20776F726C6420616E64207374756666"; + const HEX_LOWER: &str = "68656c6c6f20776f726c6420616e64207374756666"; + const BYTE_LENGTH: usize = HEX_STRING.len() / 2; + // Same as above with the last character removed, which makes the hex + // invalid as the length of a hex string must be divisible by 2 + const INVALID_HEX: &str = "68656C6C6F20776F726C6420616E6420737475666"; + + hex_to_bytes::(INVALID_HEX).expect_err("invalid hex should throw an error"); + + let bytes: [u8; BYTE_LENGTH] = hex_to_bytes(HEX_STRING).expect("converts hex to bytes"); + let lower_bytes: [u8; BYTE_LENGTH] = + hex_to_bytes(HEX_STRING).expect("converts hex to bytes"); + + assert_eq!(bytes, lower_bytes); + + let hex = bytes_to_hex(&bytes); + let lower_hex = bytes_to_hex(&lower_bytes); + + assert_eq!(HEX_LOWER, hex); + assert_eq!(HEX_LOWER, lower_hex); + } +} diff --git a/ironfish-zkp/src/lib.rs b/ironfish-zkp/src/lib.rs index 6b68d662f6..3ea6dd5b85 100644 --- a/ironfish-zkp/src/lib.rs +++ b/ironfish-zkp/src/lib.rs @@ -1,5 +1,6 @@ mod circuits; pub mod constants; +pub mod hex; pub mod primitives; pub mod util;