From 408454d94410018201062dcacd793a52293ce6cd Mon Sep 17 00:00:00 2001 From: Kevin Heavey Date: Wed, 12 Feb 2025 10:20:27 +0000 Subject: [PATCH 1/4] use five8 in pubkey, signature, hash and keypair crates --- Cargo.lock | 14 +++++++++++++- Cargo.toml | 1 + hash/Cargo.toml | 3 ++- hash/src/lib.rs | 29 ++++++++++++++--------------- keypair/Cargo.toml | 2 +- keypair/src/lib.rs | 6 ++++-- pubkey/Cargo.toml | 2 +- pubkey/src/lib.rs | 33 ++++++++++++++++----------------- signature/Cargo.toml | 2 +- signature/src/lib.rs | 29 ++++++++++++++--------------- 10 files changed, 67 insertions(+), 54 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 09797f85..be638bd3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -991,6 +991,15 @@ version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" +[[package]] +name = "five8" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75b8549488b4715defcb0d8a8a1c1c76a80661b5fa106b4ca0e7fce59d7d875" +dependencies = [ + "five8_core", +] + [[package]] name = "five8_const" version = "0.1.3" @@ -2993,6 +3002,7 @@ dependencies = [ "bs58", "bytemuck", "bytemuck_derive", + "five8", "js-sys", "serde", "serde_derive", @@ -3068,9 +3078,9 @@ dependencies = [ name = "solana-keypair" version = "2.2.0" dependencies = [ - "bs58", "ed25519-dalek", "ed25519-dalek-bip32", + "five8", "rand 0.7.3", "serde_json", "solana-derivation-path", @@ -3459,6 +3469,7 @@ dependencies = [ "bytemuck", "bytemuck_derive", "curve25519-dalek 4.1.3", + "five8", "five8_const", "getrandom 0.2.15", "js-sys", @@ -3797,6 +3808,7 @@ dependencies = [ "bs58", "curve25519-dalek 4.1.3", "ed25519-dalek", + "five8", "rand 0.8.5", "serde", "serde-big-array", diff --git a/Cargo.toml b/Cargo.toml index 06f956e1..a84836e7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -152,6 +152,7 @@ digest = "0.10.7" ed25519-dalek = "=1.0.1" ed25519-dalek-bip32 = "0.2.0" env_logger = "0.9.3" +five8 = "0.2.1" five8_const = "0.1.3" getrandom = "0.2.10" hex = "0.4.3" diff --git a/hash/Cargo.toml b/hash/Cargo.toml index a9d77e64..a66392d7 100644 --- a/hash/Cargo.toml +++ b/hash/Cargo.toml @@ -16,9 +16,9 @@ rustdoc-args = ["--cfg=docsrs"] [dependencies] borsh = { workspace = true, optional = true } -bs58 = { workspace = true, default-features = false } bytemuck = { workspace = true, optional = true } bytemuck_derive = { workspace = true, optional = true } +five8 = { workspace = true } serde = { workspace = true, optional = true } serde_derive = { workspace = true, optional = true } solana-atomic-u64 = { workspace = true } @@ -31,6 +31,7 @@ solana-frozen-abi-macro = { workspace = true, optional = true, features = [ solana-sanitize = { workspace = true } [dev-dependencies] +bs58 = { workspace = true, default-features = false } solana-hash = { path = ".", features = ["dev-context-only-utils"] } [target.'cfg(target_arch = "wasm32")'.dependencies] diff --git a/hash/src/lib.rs b/hash/src/lib.rs index 46bc7dcc..bd0af69b 100644 --- a/hash/src/lib.rs +++ b/hash/src/lib.rs @@ -14,8 +14,8 @@ use std::string::ToString; use { core::{ convert::TryFrom, - fmt, mem, - str::{from_utf8, FromStr}, + fmt, + str::{from_utf8_unchecked, FromStr}, }, solana_sanitize::Sanitize, }; @@ -68,11 +68,9 @@ impl AsRef<[u8]> for Hash { fn write_as_base58(f: &mut fmt::Formatter, h: &Hash) -> fmt::Result { let mut out = [0u8; MAX_BASE58_LEN]; - let out_slice: &mut [u8] = &mut out; - // This will never fail because the only possible error is BufferTooSmall, - // and we will never call it with too small a buffer. - let len = bs58::encode(h.0).onto(out_slice).unwrap(); - let as_str = from_utf8(&out[..len]).unwrap(); + let len = five8::encode_32(&h.0, &mut out) as usize; + // any sequence of base58 chars is valid utf8 + let as_str = unsafe { from_utf8_unchecked(&out[..len]) }; f.write_str(as_str) } @@ -110,18 +108,19 @@ impl FromStr for Hash { type Err = ParseHashError; fn from_str(s: &str) -> Result { + use five8::DecodeError; if s.len() > MAX_BASE58_LEN { return Err(ParseHashError::WrongSize); } let mut bytes = [0; HASH_BYTES]; - let decoded_size = bs58::decode(s) - .onto(&mut bytes) - .map_err(|_| ParseHashError::Invalid)?; - if decoded_size != mem::size_of::() { - Err(ParseHashError::WrongSize) - } else { - Ok(bytes.into()) - } + five8::decode_32(s, &mut bytes).map_err(|e| match e { + DecodeError::InvalidChar(_) => ParseHashError::Invalid, + DecodeError::TooLong + | DecodeError::TooShort + | DecodeError::LargestTermTooHigh + | DecodeError::OutputTooLong => ParseHashError::WrongSize, + })?; + Ok(Self::from(bytes)) } } diff --git a/keypair/Cargo.toml b/keypair/Cargo.toml index bf0d24ae..4be09b40 100644 --- a/keypair/Cargo.toml +++ b/keypair/Cargo.toml @@ -10,7 +10,7 @@ license = { workspace = true } edition = { workspace = true } [dependencies] -bs58 = { workspace = true, features = ["std"] } +five8 = { workspace = true } ed25519-dalek = { workspace = true } ed25519-dalek-bip32 = { workspace = true, optional = true } rand0-7 = { workspace = true } diff --git a/keypair/src/lib.rs b/keypair/src/lib.rs index 4e6d1824..9dd6e0d1 100644 --- a/keypair/src/lib.rs +++ b/keypair/src/lib.rs @@ -70,13 +70,15 @@ impl Keypair { /// Recovers a `Keypair` from a base58-encoded string pub fn from_base58_string(s: &str) -> Self { let mut buf = [0u8; ed25519_dalek::KEYPAIR_LENGTH]; - bs58::decode(s).onto(&mut buf).unwrap(); + five8::decode_64(s, &mut buf).unwrap(); Self::from_bytes(&buf).unwrap() } /// Returns this `Keypair` as a base58-encoded string pub fn to_base58_string(&self) -> String { - bs58::encode(&self.0.to_bytes()).into_string() + let mut out = [0u8; five8::BASE58_ENCODED_64_MAX_LEN]; + let len = five8::encode_64(&self.0.to_bytes(), &mut out); + out[..len as usize].iter().map(|c| *c as char).collect() } /// Gets this `Keypair`'s SecretKey diff --git a/pubkey/Cargo.toml b/pubkey/Cargo.toml index d8b48e2f..75bc4e5e 100644 --- a/pubkey/Cargo.toml +++ b/pubkey/Cargo.toml @@ -13,9 +13,9 @@ edition = { workspace = true } arbitrary = { workspace = true, features = ["derive"], optional = true } borsh = { workspace = true, optional = true } borsh0-10 = { package = "borsh", version = "0.10.3", optional = true } -bs58 = { workspace = true } bytemuck = { workspace = true, optional = true } bytemuck_derive = { workspace = true, optional = true } +five8 = { workspace = true } five8_const = { workspace = true } num-traits = { workspace = true } rand = { workspace = true, optional = true } diff --git a/pubkey/src/lib.rs b/pubkey/src/lib.rs index b32191d4..0825bd94 100644 --- a/pubkey/src/lib.rs +++ b/pubkey/src/lib.rs @@ -23,8 +23,8 @@ use { core::{ array, convert::{Infallible, TryFrom}, - fmt, mem, - str::{from_utf8, FromStr}, + fmt, + str::{from_utf8_unchecked, FromStr}, }, num_traits::{FromPrimitive, ToPrimitive}, solana_decode_error::DecodeError, @@ -233,18 +233,19 @@ impl FromStr for Pubkey { type Err = ParsePubkeyError; fn from_str(s: &str) -> Result { + use five8::DecodeError; if s.len() > MAX_BASE58_LEN { return Err(ParsePubkeyError::WrongSize); } let mut bytes = [0; PUBKEY_BYTES]; - let decoded_size = bs58::decode(s) - .onto(&mut bytes) - .map_err(|_| ParsePubkeyError::Invalid)?; - if decoded_size != mem::size_of::() { - Err(ParsePubkeyError::WrongSize) - } else { - Ok(Pubkey(bytes)) - } + five8::decode_32(s, &mut bytes).map_err(|e| match e { + DecodeError::InvalidChar(_) => ParsePubkeyError::Invalid, + DecodeError::TooLong + | DecodeError::TooShort + | DecodeError::LargestTermTooHigh + | DecodeError::OutputTooLong => ParsePubkeyError::WrongSize, + })?; + Ok(Pubkey(bytes)) } } @@ -809,13 +810,11 @@ impl AsMut<[u8]> for Pubkey { } } -fn write_as_base58(f: &mut fmt::Formatter, p: &Pubkey) -> fmt::Result { +fn write_as_base58(f: &mut fmt::Formatter, h: &Pubkey) -> fmt::Result { let mut out = [0u8; MAX_BASE58_LEN]; - let out_slice: &mut [u8] = &mut out; - // This will never fail because the only possible error is BufferTooSmall, - // and we will never call it with too small a buffer. - let len = bs58::encode(p.0).onto(out_slice).unwrap(); - let as_str = from_utf8(&out[..len]).unwrap(); + let len = five8::encode_32(&h.0, &mut out) as usize; + // any sequence of base58 chars is valid utf8 + let as_str = unsafe { from_utf8_unchecked(&out[..len]) }; f.write_str(as_str) } @@ -1128,7 +1127,7 @@ pub fn new_rand() -> Pubkey { #[cfg(test)] mod tests { - use {super::*, strum::IntoEnumIterator}; + use {super::*, core::str::from_utf8, strum::IntoEnumIterator}; #[test] fn test_new_unique() { diff --git a/signature/Cargo.toml b/signature/Cargo.toml index 3dc071c8..664b3640 100644 --- a/signature/Cargo.toml +++ b/signature/Cargo.toml @@ -10,8 +10,8 @@ license = { workspace = true } edition = { workspace = true } [dependencies] -bs58 = { workspace = true } ed25519-dalek = { workspace = true, optional = true } +five8 = { workspace = true } rand = { workspace = true, optional = true } serde = { workspace = true, optional = true } serde-big-array = { workspace = true, optional = true } diff --git a/signature/src/lib.rs b/signature/src/lib.rs index 05034e06..c1fbe9dc 100644 --- a/signature/src/lib.rs +++ b/signature/src/lib.rs @@ -6,7 +6,7 @@ use core::convert::TryInto; use core::{ fmt, - str::{from_utf8, FromStr}, + str::{from_utf8_unchecked, FromStr}, }; #[cfg(feature = "std")] extern crate std; @@ -69,13 +69,11 @@ impl AsRef<[u8]> for Signature { } } -fn write_as_base58(f: &mut fmt::Formatter, s: &Signature) -> fmt::Result { +fn write_as_base58(f: &mut fmt::Formatter, h: &Signature) -> fmt::Result { let mut out = [0u8; MAX_BASE58_SIGNATURE_LEN]; - let out_slice: &mut [u8] = &mut out; - // This will never fail because the only possible error is BufferTooSmall, - // and we will never call it with too small a buffer. - let len = bs58::encode(s.0).onto(out_slice).unwrap(); - let as_str = from_utf8(&out[..len]).unwrap(); + let len = five8::encode_64(&h.0, &mut out) as usize; + // any sequence of base58 chars is valid utf8 + let as_str = unsafe { from_utf8_unchecked(&out[..len]) }; f.write_str(as_str) } @@ -147,18 +145,19 @@ impl FromStr for Signature { type Err = ParseSignatureError; fn from_str(s: &str) -> Result { + use five8::DecodeError; if s.len() > MAX_BASE58_SIGNATURE_LEN { return Err(ParseSignatureError::WrongSize); } let mut bytes = [0; SIGNATURE_BYTES]; - let decoded_size = bs58::decode(s) - .onto(&mut bytes) - .map_err(|_| ParseSignatureError::Invalid)?; - if decoded_size != SIGNATURE_BYTES { - Err(ParseSignatureError::WrongSize) - } else { - Ok(bytes.into()) - } + five8::decode_64(s, &mut bytes).map_err(|e| match e { + DecodeError::InvalidChar(_) => ParseSignatureError::Invalid, + DecodeError::TooLong + | DecodeError::TooShort + | DecodeError::LargestTermTooHigh + | DecodeError::OutputTooLong => ParseSignatureError::WrongSize, + })?; + Ok(Self::from(bytes)) } } From 389d3e09167263823376e4f7874e0881f94d1fb1 Mon Sep 17 00:00:00 2001 From: Kevin Heavey Date: Wed, 12 Feb 2025 23:01:45 +0000 Subject: [PATCH 2/4] cosmetic fixes to internal variable names --- pubkey/src/lib.rs | 4 ++-- signature/src/lib.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pubkey/src/lib.rs b/pubkey/src/lib.rs index 0825bd94..70f2ca14 100644 --- a/pubkey/src/lib.rs +++ b/pubkey/src/lib.rs @@ -810,9 +810,9 @@ impl AsMut<[u8]> for Pubkey { } } -fn write_as_base58(f: &mut fmt::Formatter, h: &Pubkey) -> fmt::Result { +fn write_as_base58(f: &mut fmt::Formatter, p: &Pubkey) -> fmt::Result { let mut out = [0u8; MAX_BASE58_LEN]; - let len = five8::encode_32(&h.0, &mut out) as usize; + let len = five8::encode_32(&p.0, &mut out) as usize; // any sequence of base58 chars is valid utf8 let as_str = unsafe { from_utf8_unchecked(&out[..len]) }; f.write_str(as_str) diff --git a/signature/src/lib.rs b/signature/src/lib.rs index c1fbe9dc..33b85028 100644 --- a/signature/src/lib.rs +++ b/signature/src/lib.rs @@ -69,9 +69,9 @@ impl AsRef<[u8]> for Signature { } } -fn write_as_base58(f: &mut fmt::Formatter, h: &Signature) -> fmt::Result { +fn write_as_base58(f: &mut fmt::Formatter, s: &Signature) -> fmt::Result { let mut out = [0u8; MAX_BASE58_SIGNATURE_LEN]; - let len = five8::encode_64(&h.0, &mut out) as usize; + let len = five8::encode_64(&s.0, &mut out) as usize; // any sequence of base58 chars is valid utf8 let as_str = unsafe { from_utf8_unchecked(&out[..len]) }; f.write_str(as_str) From 103c9fa6763eb84f10b02b5f85eaf64f09260ce9 Mon Sep 17 00:00:00 2001 From: Kevin Heavey Date: Wed, 12 Feb 2025 23:33:49 +0000 Subject: [PATCH 3/4] sort deps --- keypair/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/keypair/Cargo.toml b/keypair/Cargo.toml index 4be09b40..bf8f4bee 100644 --- a/keypair/Cargo.toml +++ b/keypair/Cargo.toml @@ -10,9 +10,9 @@ license = { workspace = true } edition = { workspace = true } [dependencies] -five8 = { workspace = true } ed25519-dalek = { workspace = true } ed25519-dalek-bip32 = { workspace = true, optional = true } +five8 = { workspace = true } rand0-7 = { workspace = true } solana-derivation-path = { workspace = true, optional = true } solana-pubkey = { workspace = true } From c22ae88db5b5cf9815270cf850be58b90dce02cb Mon Sep 17 00:00:00 2001 From: Kevin Heavey Date: Mon, 3 Mar 2025 12:04:49 +0000 Subject: [PATCH 4/4] use String::from_utf8_unchecked --- keypair/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/keypair/src/lib.rs b/keypair/src/lib.rs index 9dd6e0d1..e457a01d 100644 --- a/keypair/src/lib.rs +++ b/keypair/src/lib.rs @@ -78,7 +78,7 @@ impl Keypair { pub fn to_base58_string(&self) -> String { let mut out = [0u8; five8::BASE58_ENCODED_64_MAX_LEN]; let len = five8::encode_64(&self.0.to_bytes(), &mut out); - out[..len as usize].iter().map(|c| *c as char).collect() + unsafe { String::from_utf8_unchecked(out[..len as usize].to_vec()) } } /// Gets this `Keypair`'s SecretKey