From 6a265ad203d36f77e7c62887c5f52c62daa3026f Mon Sep 17 00:00:00 2001 From: ryan Date: Thu, 18 Apr 2024 23:39:29 -0400 Subject: [PATCH] keyfork-mnemonic-util: add MnemonicBase::from_nonstandard_bytes --- crates/keyfork-shard/src/lib.rs | 23 ++++-- .../src/bin/keyfork-mnemonic-from-seed.rs | 2 +- crates/util/keyfork-mnemonic-util/src/lib.rs | 78 ++++++++++++++++--- 3 files changed, 83 insertions(+), 20 deletions(-) diff --git a/crates/keyfork-shard/src/lib.rs b/crates/keyfork-shard/src/lib.rs index cec7e9d..51a5efd 100644 --- a/crates/keyfork-shard/src/lib.rs +++ b/crates/keyfork-shard/src/lib.rs @@ -249,7 +249,10 @@ pub trait Format { let our_key = EphemeralSecret::random(); let our_pubkey_mnemonic = Mnemonic::from_bytes(PublicKey::from(&our_key).as_bytes())?; let shared_secret = our_key.diffie_hellman(&PublicKey::from(their_pubkey)); - assert!(shared_secret.was_contributory(), bug!("shared secret might be insecure")); + assert!( + shared_secret.was_contributory(), + bug!("shared secret might be insecure") + ); let hkdf = Hkdf::::new(None, shared_secret.as_bytes()); let mut shared_key_data = [0u8; 256 / 8]; @@ -289,12 +292,15 @@ pub trait Format { // encrypt data let encrypted_bytes = shared_key.encrypt(nonce, plaintext_bytes.as_slice())?; + assert_eq!( + encrypted_bytes.len(), + ENCRYPTED_LENGTH as usize, + bug!("encrypted bytes size != expected len"), + ); + let mut mnemonic_bytes = [0u8; ENCRYPTED_LENGTH as usize]; + mnemonic_bytes.copy_from_slice(&encrypted_bytes); - assert_eq!(encrypted_bytes.len(), ENCRYPTED_LENGTH as usize); - - // safety: size of out_bytes is constant and always % 4 == 0 - let payload_mnemonic = unsafe { Mnemonic::from_raw_bytes(&encrypted_bytes) }; - dbg!(payload_mnemonic.words().len()); + let payload_mnemonic = Mnemonic::from_nonstandard_bytes(mnemonic_bytes); #[cfg(feature = "qrcode")] { @@ -515,7 +521,10 @@ pub fn remote_decrypt(w: &mut impl Write) -> Result<(), Box::new(None, shared_secret.as_bytes()); let mut shared_key_data = [0u8; 256 / 8]; diff --git a/crates/util/keyfork-mnemonic-util/src/bin/keyfork-mnemonic-from-seed.rs b/crates/util/keyfork-mnemonic-util/src/bin/keyfork-mnemonic-from-seed.rs index b273bd6..4a129e9 100644 --- a/crates/util/keyfork-mnemonic-util/src/bin/keyfork-mnemonic-from-seed.rs +++ b/crates/util/keyfork-mnemonic-util/src/bin/keyfork-mnemonic-from-seed.rs @@ -8,7 +8,7 @@ fn main() -> Result<(), Box> { input.read_line(&mut line)?; let decoded = smex::decode(line.trim())?; - let mnemonic = unsafe { Mnemonic::from_raw_bytes(&decoded) }; + let mnemonic = Mnemonic::from_raw_bytes(&decoded) ; println!("{mnemonic}"); diff --git a/crates/util/keyfork-mnemonic-util/src/lib.rs b/crates/util/keyfork-mnemonic-util/src/lib.rs index 5f7afec..ffb8556 100644 --- a/crates/util/keyfork-mnemonic-util/src/lib.rs +++ b/crates/util/keyfork-mnemonic-util/src/lib.rs @@ -125,6 +125,13 @@ impl Wordlist for English { } } +struct AssertValidMnemonicSize; + +impl AssertValidMnemonicSize { + const OK_CHUNKS: () = assert!(N % 4 == 0, "bytes must be a length divisible by 4"); + const OK_SIZE: () = assert!(N <= 1024, "bytes must be less-or-equal 1024"); +} + /// A BIP-0039 mnemonic with reference to a [`Wordlist`]. #[derive(Debug, Clone, PartialEq, Eq)] pub struct MnemonicBase { @@ -276,7 +283,36 @@ where return Err(MnemonicGenerationError::InvalidByteLength(bit_count)); } - Ok(unsafe { Self::from_raw_bytes(bytes) }) + Ok( Self::from_raw_bytes(bytes) ) + } + + /// Generate a [`Mnemonic`] from the provided data and [`Wordlist`]. The data may be of a size + /// of a factor of 4, up to 1024 bytes. + /// + /// ```rust + /// use keyfork_mnemonic_util::Mnemonic; + /// let data = b"hello world!"; + /// let mnemonic = Mnemonic::from_nonstandard_bytes(*data); + /// ``` + /// + /// If an invalid size is requested, the code will fail to compile: + /// + /// ```rust,compile_fail + /// use keyfork_mnemonic_util::Mnemonic; + /// let mnemonic = Mnemonic::from_nonstandard_bytes([0u8; 53]); + /// ``` + /// + /// ```rust,compile_fail + /// use keyfork_mnemonic_util::Mnemonic; + /// let mnemonic = Mnemonic::from_nonstandard_bytes([0u8; 1024 + 4]); + /// ``` + pub fn from_nonstandard_bytes(bytes: [u8; N]) -> MnemonicBase { + #[allow(clippy::let_unit_value)] + { + let () = AssertValidMnemonicSize::::OK_CHUNKS; + let () = AssertValidMnemonicSize::::OK_SIZE; + } + Self::from_raw_bytes(&bytes) } /// Generate a [`Mnemonic`] from the provided data and [`Wordlist`]. The data is expected to be @@ -292,11 +328,12 @@ where /// Create a Mnemonic using an arbitrary length of given data. The length does not need to /// conform to BIP-0039 standards, but should be a multiple of 32 bits or 4 bytes. /// - /// # Safety - /// + /// # Panics /// This function can potentially produce mnemonics that are not BIP-0039 compliant or can't /// properly be encoded as a mnemonic. It is assumed the caller asserts the byte count is `% 4 - /// == 0`. If the assumption is incorrect, code may panic. + /// == 0`. If the assumption is incorrect, code may panic. The + /// [`MnemonicBase::from_nonstandard_bytes`] function may be used to generate entropy if the + /// length of the data is known at compile-time. /// /// # Examples /// ```rust @@ -315,11 +352,10 @@ where /// // NOTE: Data is of invalid length, 31 /// let data = b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; /// let mnemonic = unsafe { Mnemonic::from_raw_bytes(data.as_slice()) }; - /// let mnemonic_text = mnemonic.to_string(); - /// // NOTE: panic happens here - /// let new_mnemonic = Mnemonic::from_str(&mnemonic_text).unwrap(); /// ``` - pub unsafe fn from_raw_bytes(bytes: &[u8]) -> MnemonicBase { + pub fn from_raw_bytes(bytes: &[u8]) -> MnemonicBase { + assert!(bytes.len() % 4 == 0); + assert!(bytes.len() <= 1024); MnemonicBase { data: bytes.to_vec(), marker: PhantomData, @@ -520,12 +556,30 @@ mod tests { } #[test] - fn can_do_up_to_1024_bits() { - let entropy = &mut [0u8; 128]; + fn can_do_up_to_8192_bits() { + let mut entropy = [0u8; 1024]; let mut random = std::fs::File::open("/dev/urandom").unwrap(); random.read_exact(&mut entropy[..]).unwrap(); - let mnemonic = unsafe { Mnemonic::from_raw_bytes(&entropy[..]) }; + let mnemonic = Mnemonic::from_nonstandard_bytes(entropy); let words = mnemonic.words(); - assert!(words.len() == 96); + assert_eq!(words.len(), 768); + } + + #[test] + #[should_panic] + fn fails_over_8192_bits() { + let entropy = &mut [0u8; 1024 + 4]; + let mut random = std::fs::File::open("/dev/urandom").unwrap(); + random.read_exact(&mut entropy[..]).unwrap(); + let _mnemonic = Mnemonic::from_raw_bytes(&entropy[..]); + } + + #[test] + #[should_panic] + fn fails_over_invalid_size() { + let entropy = &mut [0u8; 255]; + let mut random = std::fs::File::open("/dev/urandom").unwrap(); + random.read_exact(&mut entropy[..]).unwrap(); + let _mnemonic = Mnemonic::from_raw_bytes(&entropy[..]); } }