keyfork-mnemonic-util: add MnemonicBase::from_nonstandard_bytes

This commit is contained in:
Ryan Heywood 2024-04-18 23:39:29 -04:00
parent 5d2309e301
commit 6a265ad203
Signed by: ryan
GPG Key ID: 8E401478A3FBEF72
3 changed files with 83 additions and 20 deletions

View File

@ -249,7 +249,10 @@ pub trait Format {
let our_key = EphemeralSecret::random(); let our_key = EphemeralSecret::random();
let our_pubkey_mnemonic = Mnemonic::from_bytes(PublicKey::from(&our_key).as_bytes())?; let our_pubkey_mnemonic = Mnemonic::from_bytes(PublicKey::from(&our_key).as_bytes())?;
let shared_secret = our_key.diffie_hellman(&PublicKey::from(their_pubkey)); 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::<Sha256>::new(None, shared_secret.as_bytes()); let hkdf = Hkdf::<Sha256>::new(None, shared_secret.as_bytes());
let mut shared_key_data = [0u8; 256 / 8]; let mut shared_key_data = [0u8; 256 / 8];
@ -289,12 +292,15 @@ pub trait Format {
// encrypt data // encrypt data
let encrypted_bytes = shared_key.encrypt(nonce, plaintext_bytes.as_slice())?; 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); let payload_mnemonic = Mnemonic::from_nonstandard_bytes(mnemonic_bytes);
// 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());
#[cfg(feature = "qrcode")] #[cfg(feature = "qrcode")]
{ {
@ -515,7 +521,10 @@ pub fn remote_decrypt(w: &mut impl Write) -> Result<(), Box<dyn std::error::Erro
); );
let shared_secret = our_key.diffie_hellman(&PublicKey::from(pubkey)); let shared_secret = our_key.diffie_hellman(&PublicKey::from(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::<Sha256>::new(None, shared_secret.as_bytes()); let hkdf = Hkdf::<Sha256>::new(None, shared_secret.as_bytes());
let mut shared_key_data = [0u8; 256 / 8]; let mut shared_key_data = [0u8; 256 / 8];

View File

@ -8,7 +8,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
input.read_line(&mut line)?; input.read_line(&mut line)?;
let decoded = smex::decode(line.trim())?; let decoded = smex::decode(line.trim())?;
let mnemonic = unsafe { Mnemonic::from_raw_bytes(&decoded) }; let mnemonic = Mnemonic::from_raw_bytes(&decoded) ;
println!("{mnemonic}"); println!("{mnemonic}");

View File

@ -125,6 +125,13 @@ impl Wordlist for English {
} }
} }
struct AssertValidMnemonicSize<const N: usize>;
impl<const N: usize> AssertValidMnemonicSize<N> {
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`]. /// A BIP-0039 mnemonic with reference to a [`Wordlist`].
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct MnemonicBase<W: Wordlist> { pub struct MnemonicBase<W: Wordlist> {
@ -276,7 +283,36 @@ where
return Err(MnemonicGenerationError::InvalidByteLength(bit_count)); 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<const N: usize>(bytes: [u8; N]) -> MnemonicBase<W> {
#[allow(clippy::let_unit_value)]
{
let () = AssertValidMnemonicSize::<N>::OK_CHUNKS;
let () = AssertValidMnemonicSize::<N>::OK_SIZE;
}
Self::from_raw_bytes(&bytes)
} }
/// Generate a [`Mnemonic`] from the provided data and [`Wordlist`]. The data is expected to be /// 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 /// 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. /// 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 /// 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 /// 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 /// # Examples
/// ```rust /// ```rust
@ -315,11 +352,10 @@ where
/// // NOTE: Data is of invalid length, 31 /// // NOTE: Data is of invalid length, 31
/// let data = b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; /// let data = b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
/// let mnemonic = unsafe { Mnemonic::from_raw_bytes(data.as_slice()) }; /// 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<W> { pub fn from_raw_bytes(bytes: &[u8]) -> MnemonicBase<W> {
assert!(bytes.len() % 4 == 0);
assert!(bytes.len() <= 1024);
MnemonicBase { MnemonicBase {
data: bytes.to_vec(), data: bytes.to_vec(),
marker: PhantomData, marker: PhantomData,
@ -520,12 +556,30 @@ mod tests {
} }
#[test] #[test]
fn can_do_up_to_1024_bits() { fn can_do_up_to_8192_bits() {
let entropy = &mut [0u8; 128]; let mut entropy = [0u8; 1024];
let mut random = std::fs::File::open("/dev/urandom").unwrap(); let mut random = std::fs::File::open("/dev/urandom").unwrap();
random.read_exact(&mut entropy[..]).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(); 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[..]);
} }
} }