Skip to content

Commit

Permalink
Key hex parse (#1904)
Browse files Browse the repository at this point in the history
## Description

Especially for tests I find hex keys nicer. So this does to PublicKey
and PrivateKey what we are already doing with Hash: allow parsing as
either base32 or hex. There is no potential for mixing these up, since
the length will be different except for the case of 1 or 2 bytes, and we
got 32 bytes here.

## Change checklist

- [ ] Self-review.
- [ ] Documentation updates if relevant.
- [ ] Tests if relevant.
  • Loading branch information
rklaehn authored Dec 19, 2023
1 parent c8784a4 commit 1d53636
Show file tree
Hide file tree
Showing 3 changed files with 39 additions and 36 deletions.
27 changes: 27 additions & 0 deletions iroh-base/src/base32.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub use data_encoding::{DecodeError, DecodeKind};
use hex::FromHexError;

/// Convert to a base32 string
pub fn fmt(bytes: impl AsRef<[u8]>) -> String {
Expand Down Expand Up @@ -38,3 +39,29 @@ pub fn parse_array<const N: usize>(input: &str) -> Result<[u8; N], DecodeError>
pub fn parse_vec(input: &str) -> Result<Vec<u8>, DecodeError> {
data_encoding::BASE32_NOPAD.decode(input.to_ascii_uppercase().as_bytes())
}

/// Error when parsing a hex or base32 string.
#[derive(thiserror::Error, Debug)]
pub enum HexOrBase32ParseError {
/// Error when decoding the base32.
#[error("base32: {0}")]
Base32(#[from] data_encoding::DecodeError),
/// Error when decoding the public key.
#[error("hex: {0}")]
Hex(#[from] FromHexError),
}

/// Parse a fixed length hex or base32 string into a byte array
///
/// For fixed length we can know the encoding by the length of the string.
pub fn parse_array_hex_or_base32<const LEN: usize>(
input: &str,
) -> std::result::Result<[u8; LEN], HexOrBase32ParseError> {
let mut bytes = [0u8; LEN];
if input.len() == LEN * 2 {
hex::decode_to_slice(input, &mut bytes)?;
Ok(bytes)
} else {
Ok(parse_array(input)?)
}
}
28 changes: 4 additions & 24 deletions iroh-base/src/hash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ use bao_tree::blake3;
use postcard::experimental::max_size::MaxSize;
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};

use crate::base32::{parse_array_hex_or_base32, HexOrBase32ParseError};

/// Hash type used throughout.
#[derive(PartialEq, Eq, Copy, Clone, Hash)]
pub struct Hash(blake3::Hash);
Expand Down Expand Up @@ -118,32 +120,10 @@ impl fmt::Display for Hash {
}

impl FromStr for Hash {
type Err = anyhow::Error;
type Err = HexOrBase32ParseError;

fn from_str(s: &str) -> Result<Self, Self::Err> {
let sb = s.as_bytes();
if sb.len() == 64 {
// this is most likely a hex encoded hash
// try to decode it as hex
let mut bytes = [0u8; 32];
if hex::decode_to_slice(sb, &mut bytes).is_ok() {
return Ok(Self::from(bytes));
}
}
anyhow::ensure!(sb.len() == 52, "invalid base32 length");
// this is a base32 encoded hash, we can decode it directly
let mut t = [0u8; 52];
t.copy_from_slice(sb);
// hack since data_encoding doesn't have BASE32LOWER_NOPAD as a const
std::str::from_utf8_mut(t.as_mut())
.unwrap()
.make_ascii_uppercase();
// decode the bytes
let mut res = [0u8; 32];
data_encoding::BASE32_NOPAD
.decode_mut(&t, &mut res)
.map_err(|_e| anyhow::anyhow!("invalid base32"))?;
Ok(Self::from(res))
parse_array_hex_or_base32(s).map(Hash::from)
}
}

Expand Down
20 changes: 8 additions & 12 deletions iroh-net/src/key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use std::{

pub use ed25519_dalek::{Signature, PUBLIC_KEY_LENGTH};
use ed25519_dalek::{SignatureError, SigningKey, VerifyingKey};
use iroh_base::base32;
use iroh_base::base32::{self, HexOrBase32ParseError};
use once_cell::sync::OnceCell;
use rand_core::CryptoRngCore;
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -236,7 +236,7 @@ impl Display for PublicKey {
pub enum KeyParsingError {
/// Error when decoding the base32.
#[error("decoding: {0}")]
Base32(#[from] data_encoding::DecodeError),
Base32(#[from] HexOrBase32ParseError),
/// Error when decoding the public key.
#[error("key: {0}")]
Key(#[from] ed25519_dalek::SignatureError),
Expand All @@ -249,9 +249,8 @@ impl FromStr for PublicKey {
type Err = KeyParsingError;

fn from_str(s: &str) -> Result<Self, Self::Err> {
let bytes = data_encoding::BASE32_NOPAD.decode(s.to_ascii_uppercase().as_bytes())?;
let key = PublicKey::try_from(&bytes[..])?;
Ok(key)
let bytes = base32::parse_array_hex_or_base32::<32>(s)?;
Ok(Self::try_from(bytes.as_ref())?)
}
}

Expand All @@ -278,7 +277,7 @@ impl FromStr for SecretKey {
type Err = KeyParsingError;

fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(SecretKey::from(base32::parse_array::<32>(s)?))
Ok(SecretKey::from(base32::parse_array_hex_or_base32::<32>(s)?))
}
}

Expand Down Expand Up @@ -402,12 +401,9 @@ mod tests {

#[test]
fn test_public_key_postcard() {
let public_key = PublicKey::try_from(
hex::decode("ae58ff8833241ac82d6ff7611046ed67b5072d142c588d0063e942d9a75502b6")
.unwrap()
.as_slice(),
)
.unwrap();
let public_key =
PublicKey::from_str("ae58ff8833241ac82d6ff7611046ed67b5072d142c588d0063e942d9a75502b6")
.unwrap();
let bytes = postcard::to_stdvec(&public_key).unwrap();
let expected =
parse_hexdump("ae58ff8833241ac82d6ff7611046ed67b5072d142c588d0063e942d9a75502b6")
Expand Down

0 comments on commit 1d53636

Please sign in to comment.