diff --git a/CHANGELOG.md b/CHANGELOG.md index faba2b4ecc2b6c2e32200d46295052fa548b2f60..891cc6dbe8ed0b088eefb221b8cb13ceaf2db726 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] - ReleaseDate +### Fixed + +- keys: ed25519 public key methods (as_ref, try_from, to_bytes_vector) must be consistent with bincode serialization/deserialization + ## [0.13.0] - 2020-03-04 ### Added diff --git a/src/bases/b58.rs b/src/bases/b58.rs index 7e5a2ed12df31a07fef118daed47f196181292ef..a45735b52c11618a6641c90cc065307e7056398c 100644 --- a/src/bases/b58.rs +++ b/src/bases/b58.rs @@ -24,7 +24,7 @@ pub trait ToBase58 { } /// Create an array of 32 bytes from a Base58 string. -pub fn str_base58_to_32bytes(base58_data: &str) -> Result<([u8; 32], usize), BaseConvertionError> { +pub fn str_base58_to_32bytes(base58_data: &str) -> Result<([u8; 32], u8), BaseConvertionError> { let mut source = base58_data; let mut count_leading_1 = 0; while !source.is_empty() && &source[0..1] == "1" { @@ -58,7 +58,7 @@ pub fn str_base58_to_32bytes(base58_data: &str) -> Result<([u8; 32], usize), Bas } /// Create a Base58 string from a slice of bytes. -pub fn bytes_to_str_base58(bytes: &[u8], count_leading_1: usize) -> String { +pub fn bytes_to_str_base58(bytes: &[u8], count_leading_1: u8) -> String { let mut str_base58 = String::new(); let mut remaining_leading_1 = count_leading_1; while remaining_leading_1 > 0 { @@ -72,7 +72,7 @@ pub fn bytes_to_str_base58(bytes: &[u8], count_leading_1: usize) -> String { let bytes = if count_leading_1 == 0 && !bytes.is_empty() && bytes[0] == 0 { &bytes[1..] } else { - &bytes[count_leading_1..] + &bytes[count_leading_1 as usize..] }; str_base58.push_str(&bs58::encode(bytes).into_string()); diff --git a/src/dewif/read.rs b/src/dewif/read.rs index 8f5fdd052e915342cbbb23339a84396db2d3f2f1..8d27c35e831822adac5ab70ae248868a67172c08 100644 --- a/src/dewif/read.rs +++ b/src/dewif/read.rs @@ -16,9 +16,9 @@ //! Read [DEWIF](https://git.duniter.org/nodes/common/doc/blob/dewif/rfc/0013_Duniter_Encrypted_Wallet_Import_Format.md) file content use super::{Currency, ExpectedCurrency}; -use crate::keys::ed25519::{KeyPairFromSeed32Generator, PublicKey, PUBKEY_SIZE_IN_BYTES}; +use crate::keys::ed25519::{KeyPairFromSeed32Generator, PublicKey, PUBKEY_DATAS_SIZE_IN_BYTES}; use crate::keys::{KeyPair, KeyPairEnum}; -use crate::seeds::Seed32; +use crate::seeds::{Seed32, SEED_32_SIZE_IN_BYTES}; use arrayvec::ArrayVec; use byteorder::ByteOrder; use std::convert::{TryFrom, TryInto}; @@ -135,11 +135,12 @@ fn read_dewif_v2(bytes: &mut [u8], passphrase: &str) -> Result<KeyPairsIter, Dew fn bytes_to_checked_keypair(bytes: &[u8]) -> Result<KeyPairEnum, DewifReadError> { // Wrap bytes into Seed32 and PublicKey let seed = Seed32::new( - (&bytes[..PUBKEY_SIZE_IN_BYTES]) + (&bytes[..SEED_32_SIZE_IN_BYTES]) .try_into() .expect("dev error"), ); - let expected_pubkey = PublicKey::try_from(&bytes[PUBKEY_SIZE_IN_BYTES..]).expect("dev error"); + let expected_pubkey = + PublicKey::try_from(&bytes[PUBKEY_DATAS_SIZE_IN_BYTES..]).expect("dev error"); // Get keypair let keypair = KeyPairFromSeed32Generator::generate(seed); diff --git a/src/dewif/write.rs b/src/dewif/write.rs index 2b21f12795ff60874a19ddb7847991f2c2ebdfad..e04db6a8c797ee4e70a47669d19b48fc0f7e3bd3 100644 --- a/src/dewif/write.rs +++ b/src/dewif/write.rs @@ -32,7 +32,7 @@ pub fn write_dewif_v1_content( let currency_code: u32 = currency.into(); unwrap!(bytes.try_extend_from_slice(¤cy_code.to_be_bytes())); unwrap!(bytes.try_extend_from_slice(keypair.seed().as_ref())); - unwrap!(bytes.try_extend_from_slice(keypair.public_key().as_ref())); + unwrap!(bytes.try_extend_from_slice(keypair.public_key().datas.as_ref())); let cipher = crate::aes256::new_cipher(super::gen_aes_seed(passphrase)); crate::aes256::encrypt::encrypt_n_blocks( @@ -56,9 +56,9 @@ pub fn write_dewif_v2_content( let currency_code: u32 = currency.into(); unwrap!(bytes.try_extend_from_slice(¤cy_code.to_be_bytes())); unwrap!(bytes.try_extend_from_slice(keypair1.seed().as_ref())); - unwrap!(bytes.try_extend_from_slice(keypair1.public_key().as_ref())); + unwrap!(bytes.try_extend_from_slice(keypair1.public_key().datas.as_ref())); unwrap!(bytes.try_extend_from_slice(keypair2.seed().as_ref())); - unwrap!(bytes.try_extend_from_slice(keypair2.public_key().as_ref())); + unwrap!(bytes.try_extend_from_slice(keypair2.public_key().datas.as_ref())); let cipher = crate::aes256::new_cipher(super::gen_aes_seed(passphrase)); crate::aes256::encrypt::encrypt_8_blocks(&cipher, &mut bytes[super::UNENCRYPTED_BYTES_LEN..]); diff --git a/src/keys.rs b/src/keys.rs index 61cafcc3b342581bda703406648f27d0d7de2dcb..566399692a8d046c8190c0822ec5f3804612ad1d 100644 --- a/src/keys.rs +++ b/src/keys.rs @@ -510,7 +510,7 @@ mod tests { } #[test] - fn pubkey() { + fn public_key() { let ed25519_pubkey_default = ed25519::PublicKey::default(); let pubkey_default = PubKey::Ed25519(ed25519_pubkey_default); let pubkey = PubKey::Ed25519(unwrap!(ed25519::PublicKey::try_from( @@ -524,14 +524,16 @@ mod tests { ); assert_eq!(pubkey.size_in_bytes(), ed25519::PUBKEY_SIZE_IN_BYTES + 3); - assert_eq!("1111111111111111111111111111111", &format!("{}", pubkey)); + assert_eq!("11111111111111111111111111111111", &format!("{}", pubkey)); assert_eq!(KeysAlgo::Ed25519, pubkey.algo()); assert_eq!(KeysAlgo::Schnorr, PubKey::Schnorr().algo()); - assert_eq!([0u8; 32].to_vec(), pubkey.to_bytes_vector()); + let mut expected_vec = [0u8; 32].to_vec(); + expected_vec.push(32); + assert_eq!(expected_vec, pubkey.to_bytes_vector()); - assert_eq!("1111111111111111111111111111111", &pubkey.to_base58()); + assert_eq!("11111111111111111111111111111111", &pubkey.to_base58()); assert_eq!( Err(SigError::InvalidSig), @@ -723,11 +725,11 @@ mod tests { assert_eq!( Err(PubkeyFromBytesError::InvalidBytesLen { expected: ed25519::PUBKEY_SIZE_IN_BYTES, - found: 33, + found: 34, }), PubKey::from_bytes(&[ 0u8, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, - 23, 24, 25, 26, 27, 28, 29, 30, 31, 31 + 23, 24, 25, 26, 27, 28, 29, 30, 31, 31, 17 ]), ); } diff --git a/src/keys/ed25519.rs b/src/keys/ed25519.rs index f13ee32519c6f01f70dbdfafd9ef382fb3394377..455aa4a24f4c4c2e8b5410d2acca4cb8ca0b3d73 100644 --- a/src/keys/ed25519.rs +++ b/src/keys/ed25519.rs @@ -44,8 +44,9 @@ use std::marker::PhantomData; use unwrap::unwrap; use zeroize::Zeroize; -/// Maximal size of a public key in bytes -pub const PUBKEY_SIZE_IN_BYTES: usize = 32; +/// Size of a public key in bytes +pub const PUBKEY_SIZE_IN_BYTES: usize = 33; +pub(crate) const PUBKEY_DATAS_SIZE_IN_BYTES: usize = 32; /// constf a signature in bytes pub const SIG_SIZE_IN_BYTES: usize = 64; @@ -160,8 +161,8 @@ impl Eq for Signature {} #[cfg_attr(feature = "ser", derive(Deserialize, Serialize))] #[derive(Copy, Clone, PartialEq, Eq, Hash)] pub struct PublicKey { - datas: [u8; 32], - count_leading_zero: usize, + pub(crate) datas: [u8; 32], + count_leading_zero: u8, } impl Default for PublicKey { @@ -175,7 +176,7 @@ impl Default for PublicKey { impl AsRef<[u8]> for PublicKey { fn as_ref(&self) -> &[u8] { - &self.datas[..] + unsafe { std::slice::from_raw_parts(self.datas.as_ptr(), PUBKEY_SIZE_IN_BYTES) } } } @@ -189,15 +190,24 @@ impl TryFrom<&[u8]> for PublicKey { found: bytes.len(), }) } else { + let (count_leading_zero, datas_bytes) = if bytes.len() <= PUBKEY_DATAS_SIZE_IN_BYTES { + (0, bytes) + } else { + ( + bytes[PUBKEY_DATAS_SIZE_IN_BYTES], + &bytes[..PUBKEY_DATAS_SIZE_IN_BYTES], + ) + }; // Ensure that given bytes represents a valid point on the Edwards form of Curve25519. - let compressed_edwards_y = CompressedEdwardsY::from_slice(bytes); + let compressed_edwards_y = CompressedEdwardsY::from_slice(datas_bytes); if compressed_edwards_y.decompress().is_some() { - let mut u8_array = [0; PUBKEY_SIZE_IN_BYTES]; - u8_array[(PUBKEY_SIZE_IN_BYTES - bytes.len())..].copy_from_slice(bytes); + let mut u8_array = [0; PUBKEY_DATAS_SIZE_IN_BYTES]; + u8_array[(PUBKEY_DATAS_SIZE_IN_BYTES - datas_bytes.len())..] + .copy_from_slice(datas_bytes); Ok(PublicKey { datas: u8_array, - count_leading_zero: 0, + count_leading_zero, }) } else { Err(PubkeyFromBytesError::InvalidBytesContent) @@ -208,7 +218,7 @@ impl TryFrom<&[u8]> for PublicKey { impl ToBase58 for PublicKey { fn to_base58(&self) -> String { - bytes_to_str_base58(self.as_ref(), self.count_leading_zero) + bytes_to_str_base58(self.datas.as_ref(), self.count_leading_zero) } } @@ -217,7 +227,7 @@ impl Display for PublicKey { write!( f, "{}", - bytes_to_str_base58(self.as_ref(), self.count_leading_zero) + bytes_to_str_base58(self.datas.as_ref(), self.count_leading_zero) ) } } @@ -246,7 +256,7 @@ impl super::PublicKey for PublicKey { } fn verify(&self, message: &[u8], signature: &Self::Signature) -> Result<(), SigError> { - Ok(UnparsedPublicKey::new(&ED25519, self.as_ref()) + Ok(UnparsedPublicKey::new(&ED25519, self.datas.as_ref()) .verify(message, &signature.0) .map_err(|_| SigError::InvalidSig)?) } @@ -459,13 +469,6 @@ mod tests { assert!(!seed.eq(&other_seed)); // Test seed parsing - /*assert_eq!( - Seed32::from_base58("DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQd",).unwrap_err(), - BaseConvertionError::InvalidLength { - found: 31, - expected: 32 - } - );*/ assert_eq!( Seed32::from_base58("DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQd<<").unwrap_err(), BaseConvertionError::InvalidCharacter { @@ -476,24 +479,67 @@ mod tests { } #[test] - fn test_pubkey_111_from_base58() { + fn test_pubkey_111_from_base58() -> Result<(), bincode::Error> { let public58 = "11111111111111111111111111111111111111111111"; - let _ = unwrap!(super::PublicKey::from_base58(public58)); + let public_key = unwrap!(super::PublicKey::from_base58(public58)); + assert_eq!( + &[ + 0u8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 44 + ][..], + public_key.as_ref(), + ); + assert_eq!( + bincode::serialize(&public_key)?, + public_key.to_bytes_vector(), + ); + Ok(()) } #[test] - fn base58_public_key() { + fn test_pubkey_with_leading_1() -> Result<(), bincode::Error> { + let public58 = "13fn6X3XWVgshHTgS8beZMo9XiyScx6MB6yPsBB5ZBia"; + let public_key = unwrap!(super::PublicKey::from_base58(public58)); + assert_eq!( + bincode::serialize(&public_key)?, + public_key.to_bytes_vector(), + ); + Ok(()) + } + + #[test] + fn test_other_pubkey_with_leading_1() -> Result<(), bincode::Error> { + let public58 = "1V27SH9TiVEDs8TWFPydpRKxhvZari7wjGwQnPxMnkr"; + let public_key = unwrap!(super::PublicKey::from_base58(public58)); + assert_eq!( + bincode::serialize(&public_key)?, + public_key.to_bytes_vector(), + ); + Ok(()) + } + + #[test] + fn base58_public_key() -> Result<(), bincode::Error> { let public58 = "DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV"; let public_key = unwrap!(super::PublicKey::from_base58(public58)); // Test base58 encoding/decoding (loop for every bytes) assert_eq!(public_key.to_base58(), public58); let public_raw = unwrap!(b58::str_base58_to_32bytes(public58)); - assert_eq!(public_raw.0.to_vec(), public_key.to_bytes_vector()); - for (key, raw) in public_key.as_ref().iter().zip(public_raw.0.iter()) { + assert_eq!( + public_raw.0.to_vec(), + &public_key.to_bytes_vector()[..PUBKEY_DATAS_SIZE_IN_BYTES] + ); + for (key, raw) in public_key.datas.as_ref().iter().zip(public_raw.0.iter()) { assert_eq!(key, raw); } + // Test binary encoding/decoding + assert_eq!( + bincode::serialize(&public_key)?, + public_key.to_bytes_vector(), + ); + // Test pubkey debug assert_eq!( "PublicKey { DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV }".to_owned(), @@ -516,6 +562,7 @@ mod tests { offset: 42 } ); + Ok(()) } #[test] diff --git a/src/keys/x25519.rs b/src/keys/x25519.rs index 00d4d2244ff378c26a116977f7f360389f9e2dd6..7136317f6a7a6a211ad03110399763dfdcc4f4d0 100644 --- a/src/keys/x25519.rs +++ b/src/keys/x25519.rs @@ -31,7 +31,8 @@ pub(crate) struct X25519PublicKey(MontgomeryPoint); impl From<&PublicKey> for X25519PublicKey { fn from(ed25519_public_key: &PublicKey) -> Self { - let compressed_edwards_y = CompressedEdwardsY::from_slice(ed25519_public_key.as_ref()); + let compressed_edwards_y = + CompressedEdwardsY::from_slice(ed25519_public_key.datas.as_ref()); let edwards_point = compressed_edwards_y .decompress() .expect("dev error: invalid ed25519 public key"); diff --git a/src/private_message.rs b/src/private_message.rs index 6161df9429a8937a4677697d8385c1268c5d5a75..c1e32cccd2ca3af6e2e03792c44dae77d62a8646 100644 --- a/src/private_message.rs +++ b/src/private_message.rs @@ -255,7 +255,7 @@ where // and derive symmetric_key and nonce from shared secret let (symmetric_key, nonce) = generate_symmetric_key_and_nonce( algorithm, - ephemeral_keypair.public_key().as_ref(), + ephemeral_keypair.public_key().datas.as_ref(), ephemeral_keypair.seed(), &receiver_public_key, )?; @@ -287,7 +287,7 @@ where .try_extend_from_slice(tag.as_ref()) .expect("too long tag"); clear_footer - .try_extend_from_slice(ephemeral_keypair.public_key().as_ref()) + .try_extend_from_slice(ephemeral_keypair.public_key().datas.as_ref()) .expect("too long ephemeral_public_key"); message.extend(clear_footer.into_iter()); diff --git a/src/private_message/authentication.rs b/src/private_message/authentication.rs index 32714ea242e876cb5b68ec8311e0e26220e62d64..0a7b503f27fab0de75eed4489c554895a7a63cf1 100644 --- a/src/private_message/authentication.rs +++ b/src/private_message/authentication.rs @@ -75,7 +75,7 @@ pub(crate) fn write_anthentication_datas( ) -> impl AsRef<[u8]> + IntoIterator<Item = u8> { let mut authent_datas = arrayvec::ArrayVec::<[u8; 128]>::new(); authent_datas - .try_extend_from_slice(sender_public_key.as_ref()) + .try_extend_from_slice(sender_public_key.datas.as_ref()) .expect("too long sender public key"); authent_datas .try_extend_from_slice(authent_proof.as_ref()) diff --git a/src/seeds.rs b/src/seeds.rs index c5f55630e156ee6e00ce86e8008f3a9d736a906a..c20aa8360ab81857093841e49891a76b5fab5710 100644 --- a/src/seeds.rs +++ b/src/seeds.rs @@ -24,11 +24,14 @@ use serde::{Deserialize, Serialize}; use std::fmt::{self, Debug, Display, Formatter}; use zeroize::Zeroize; +/// Seed32 size in bytes +pub const SEED_32_SIZE_IN_BYTES: usize = 32; + /// Store a 32 bytes seed used to generate keys. #[cfg_attr(feature = "ser", derive(Deserialize, Serialize))] #[derive(Clone, Default, PartialEq, Eq, Hash, Zeroize)] #[zeroize(drop)] -pub struct Seed32([u8; 32]); +pub struct Seed32([u8; SEED_32_SIZE_IN_BYTES]); impl AsRef<[u8]> for Seed32 { fn as_ref(&self) -> &[u8] { @@ -57,7 +60,7 @@ impl Display for Seed32 { impl Seed32 { #[inline] /// Create new seed - pub fn new(seed_bytes: [u8; 32]) -> Seed32 { + pub fn new(seed_bytes: [u8; SEED_32_SIZE_IN_BYTES]) -> Seed32 { Seed32(seed_bytes) } #[inline]