diff --git a/Cargo.lock b/Cargo.lock index 0d2aa017d30..cf9bbc1d26a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3353,6 +3353,7 @@ dependencies = [ "derive_more", "displaydoc", "fixnum", + "hex", "iroha_ffi", "iroha_macro", "iroha_primitives_derive", diff --git a/crypto/src/lib.rs b/crypto/src/lib.rs index 5593ad24348..23107e922db 100755 --- a/crypto/src/lib.rs +++ b/crypto/src/lib.rs @@ -28,6 +28,7 @@ use error::{Error, NoSuchAlgorithm}; use getset::{CopyGetters, Getters}; pub use hash::*; use iroha_macro::ffi_impl_opaque; +use iroha_primitives::const_bytes::ConstBytes; use iroha_schema::IntoSchema; pub use merkle::MerkleTree; #[cfg(not(feature = "ffi_import"))] @@ -138,7 +139,7 @@ impl TryFrom for UrsaKeyGenOption { match algorithm { Algorithm::Ed25519 | Algorithm::Secp256k1 => { - Ok(Self::FromSecretKey(UrsaPrivateKey(key.payload))) + Ok(Self::FromSecretKey(UrsaPrivateKey(key.payload.into_vec()))) } _ => Err(Self::Error {}), } @@ -265,11 +266,11 @@ impl KeyPair { Ok(Self { public_key: PublicKey { digest_function: configuration.algorithm, - payload: core::mem::take(&mut public_key.0), + payload: ConstBytes::new(core::mem::take(&mut public_key.0)), }, private_key: PrivateKey { digest_function: configuration.algorithm, - payload: core::mem::take(&mut private_key.0), + payload: ConstBytes::new(core::mem::take(&mut private_key.0)), }, }) } @@ -314,7 +315,7 @@ ffi::ffi_item! { #[getset(get_copy = "pub")] digest_function: Algorithm, /// Key payload - payload: Vec, + payload: ConstBytes, } } @@ -323,14 +324,14 @@ impl PublicKey { /// Key payload // TODO: Derive with getset once FFI impl is fixed pub fn payload(&self) -> &[u8] { - &self.payload + self.payload.as_ref() } #[cfg(feature = "std")] fn try_from_private(private_key: PrivateKey) -> Result { let digest_function = private_key.digest_function(); let key_gen_option = Some(UrsaKeyGenOption::FromSecretKey(UrsaPrivateKey( - private_key.payload, + private_key.payload.into_vec(), ))); let (mut public_key, _) = match digest_function { @@ -342,7 +343,7 @@ impl PublicKey { Ok(PublicKey { digest_function: private_key.digest_function, - payload: core::mem::take(&mut public_key.0), + payload: ConstBytes::new(core::mem::take(&mut public_key.0)), }) } } @@ -398,7 +399,7 @@ ffi::ffi_item! { digest_function: Algorithm, /// Key payload #[serde(with = "hex::serde")] - payload: Vec, + payload: ConstBytes, } } @@ -413,7 +414,7 @@ impl PrivateKey { /// Key payload // TODO: Derive with getset once FFI impl is fixed pub fn payload(&self) -> &[u8] { - &self.payload + self.payload.as_ref() } } @@ -429,7 +430,8 @@ impl PrivateKey { ) -> Result { Ok(Self { digest_function, - payload: hex_decode(payload)?, + payload: ConstBytes::from_hex(payload) + .map_err(|err| Error::Parse(err.to_string()))?, }) } @@ -442,6 +444,7 @@ impl PrivateKey { #[cfg(feature = "std")] pub fn from_hex(digest_function: Algorithm, payload: &[u8]) -> Result { let payload = hex_decode(payload)?; + let payload = ConstBytes::new(payload); let private_key_candidate = Self { digest_function, @@ -770,10 +773,12 @@ mod tests { "{}", PublicKey { digest_function: Algorithm::Ed25519, - payload: hex_decode( - "1509A611AD6D97B01D871E58ED00C8FD7C3917B6CA61A8C2833A19E000AAC2E4" + payload: ConstBytes::new( + hex_decode( + "1509A611AD6D97B01D871E58ED00C8FD7C3917B6CA61A8C2833A19E000AAC2E4" + ) + .expect("Failed to decode public key.") ) - .expect("Failed to decode public key.") } ), "ed01201509A611AD6D97B01D871E58ED00C8FD7C3917B6CA61A8C2833A19E000AAC2E4" @@ -783,10 +788,12 @@ mod tests { "{}", PublicKey { digest_function: Algorithm::Secp256k1, - payload: hex_decode( - "0312273E8810581E58948D3FB8F9E8AD53AAA21492EBB8703915BBB565A21B7FCC" + payload: ConstBytes::new( + hex_decode( + "0312273E8810581E58948D3FB8F9E8AD53AAA21492EBB8703915BBB565A21B7FCC" + ) + .expect("Failed to decode public key.") ) - .expect("Failed to decode public key.") } ), "e701210312273E8810581E58948D3FB8F9E8AD53AAA21492EBB8703915BBB565A21B7FCC" @@ -796,10 +803,9 @@ mod tests { "{}", PublicKey { digest_function: Algorithm::BlsNormal, - payload: hex_decode( + payload: ConstBytes::from_hex( "04175B1E79B15E8A2D5893BF7F8933CA7D0863105D8BAC3D6F976CB043378A0E4B885C57ED14EB85FC2FABC639ADC7DE7F0020C70C57ACC38DEE374AF2C04A6F61C11DE8DF9034B12D849C7EB90099B0881267D0E1507D4365D838D7DCC31511E7" - ) - .expect("Failed to decode public key.") + ).expect("Failed to decode public key.") } ), "ea016104175B1E79B15E8A2D5893BF7F8933CA7D0863105D8BAC3D6F976CB043378A0E4B885C57ED14EB85FC2FABC639ADC7DE7F0020C70C57ACC38DEE374AF2C04A6F61C11DE8DF9034B12D849C7EB90099B0881267D0E1507D4365D838D7DCC31511E7" @@ -809,7 +815,7 @@ mod tests { "{}", PublicKey { digest_function: Algorithm::BlsSmall, - payload: hex_decode( + payload: ConstBytes::from_hex( "040CB3231F601E7245A6EC9A647B450936F707CA7DC347ED258586C1924941D8BC38576473A8BA3BB2C37E3E121130AB67103498A96D0D27003E3AD960493DA79209CF024E2AA2AE961300976AEEE599A31A5E1B683EAA1BCFFC47B09757D20F21123C594CF0EE0BAF5E1BDD272346B7DC98A8F12C481A6B28174076A352DA8EAE881B90911013369D7FA960716A5ABC5314307463FA2285A5BF2A5B5C6220D68C2D34101A91DBFC531C5B9BBFB2245CCC0C50051F79FC6714D16907B1FC40E0C0" ) .expect("Failed to decode public key.") @@ -839,14 +845,14 @@ mod tests { TestJson { public_key: PublicKey { digest_function: Algorithm::Ed25519, - payload: hex_decode( + payload: ConstBytes::from_hex( "1509A611AD6D97B01D871E58ED00C8FD7C3917B6CA61A8C2833A19E000AAC2E4" ) .expect("Failed to decode public key.") }, private_key: PrivateKey { digest_function: Algorithm::Ed25519, - payload: hex_decode("3A7991AF1ABB77F3FD27CC148404A6AE4439D095A63591B77C788D53F708A02A1509A611AD6D97B01D871E58ED00C8FD7C3917B6CA61A8C2833A19E000AAC2E4") + payload: ConstBytes::from_hex("3A7991AF1ABB77F3FD27CC148404A6AE4439D095A63591B77C788D53F708A02A1509A611AD6D97B01D871E58ED00C8FD7C3917B6CA61A8C2833A19E000AAC2E4") .expect("Failed to decode private key"), } } @@ -867,14 +873,14 @@ mod tests { TestJson { public_key: PublicKey { digest_function: Algorithm::Secp256k1, - payload: hex_decode( + payload: ConstBytes::from_hex( "0312273E8810581E58948D3FB8F9E8AD53AAA21492EBB8703915BBB565A21B7FCC" ) .expect("Failed to decode public key.") }, private_key: PrivateKey { digest_function: Algorithm::Secp256k1, - payload: hex_decode("4DF4FCA10762D4B529FE40A2188A60CA4469D2C50A825B5F33ADC2CB78C69445") + payload: ConstBytes::from_hex("4DF4FCA10762D4B529FE40A2188A60CA4469D2C50A825B5F33ADC2CB78C69445") .expect("Failed to decode private key"), } } @@ -895,14 +901,14 @@ mod tests { TestJson { public_key: PublicKey { digest_function: Algorithm::BlsNormal, - payload: hex_decode( + payload: ConstBytes::from_hex( "04175B1E79B15E8A2D5893BF7F8933CA7D0863105D8BAC3D6F976CB043378A0E4B885C57ED14EB85FC2FABC639ADC7DE7F0020C70C57ACC38DEE374AF2C04A6F61C11DE8DF9034B12D849C7EB90099B0881267D0E1507D4365D838D7DCC31511E7" ) .expect("Failed to decode public key.") }, private_key: PrivateKey { digest_function: Algorithm::BlsNormal, - payload: hex_decode("000000000000000000000000000000002F57460183837EFBAC6AA6AB3B8DBB7CFFCFC59E9448B7860A206D37D470CBA3") + payload: ConstBytes::from_hex("000000000000000000000000000000002F57460183837EFBAC6AA6AB3B8DBB7CFFCFC59E9448B7860A206D37D470CBA3") .expect("Failed to decode private key"), } } @@ -918,15 +924,16 @@ mod tests { TestJson { public_key: PublicKey { digest_function: Algorithm::BlsSmall, - payload: hex_decode( - "040CB3231F601E7245A6EC9A647B450936F707CA7DC347ED258586C1924941D8BC38576473A8BA3BB2C37E3E121130AB67103498A96D0D27003E3AD960493DA79209CF024E2AA2AE961300976AEEE599A31A5E1B683EAA1BCFFC47B09757D20F21123C594CF0EE0BAF5E1BDD272346B7DC98A8F12C481A6B28174076A352DA8EAE881B90911013369D7FA960716A5ABC5314307463FA2285A5BF2A5B5C6220D68C2D34101A91DBFC531C5B9BBFB2245CCC0C50051F79FC6714D16907B1FC40E0C0" - ) - .expect("Failed to decode public key.") + payload: ConstBytes::from_hex( + "040CB3231F601E7245A6EC9A647B450936F707CA7DC347ED258586C1924941D8BC38576473A8BA3BB2C37E3E121130AB67103498A96D0D27003E3AD960493DA79209CF024E2AA2AE961300976AEEE599A31A5E1B683EAA1BCFFC47B09757D20F21123C594CF0EE0BAF5E1BDD272346B7DC98A8F12C481A6B28174076A352DA8EAE881B90911013369D7FA960716A5ABC5314307463FA2285A5BF2A5B5C6220D68C2D34101A91DBFC531C5B9BBFB2245CCC0C50051F79FC6714D16907B1FC40E0C0" + ) + .expect("Failed to decode public key.") }, private_key: PrivateKey { digest_function: Algorithm::BlsSmall, - payload: hex_decode("0000000000000000000000000000000060F3C1AC9ADDBBED8DB83BC1B2EF22139FB049EECB723A557A41CA1A4B1FED63") - .expect("Failed to decode private key"), + payload: ConstBytes::from_hex( + "0000000000000000000000000000000060F3C1AC9ADDBBED8DB83BC1B2EF22139FB049EECB723A557A41CA1A4B1FED63") + .expect("Failed to decode private key"), } } ) diff --git a/crypto/src/multihash.rs b/crypto/src/multihash.rs index a37f957bd64..893cd146edc 100644 --- a/crypto/src/multihash.rs +++ b/crypto/src/multihash.rs @@ -9,6 +9,7 @@ use alloc::{ }; use derive_more::Display; +use iroha_primitives::const_bytes::ConstBytes; use crate::{varint, Algorithm, NoSuchAlgorithm, PublicKey}; @@ -90,7 +91,7 @@ pub struct Multihash { /// digest pub digest_function: DigestFunction, /// hash payload - pub payload: Vec, + pub payload: ConstBytes, } impl TryFrom> for Multihash { @@ -127,6 +128,7 @@ impl TryFrom> for Multihash { "Digest size not equal to actual length", ))); } + let payload = ConstBytes::new(payload); Ok(Self { digest_function, @@ -148,7 +150,7 @@ impl TryFrom<&Multihash> for Vec { bytes.push(multihash.payload.len().try_into().map_err(|_e| { MultihashConvertError::new(String::from("Digest size can't fit into u8")) })?); - bytes.extend_from_slice(&multihash.payload); + bytes.extend_from_slice(multihash.payload.as_ref()); Ok(bytes) } @@ -222,8 +224,10 @@ mod tests { fn multihash_to_bytes() { let multihash = &Multihash { digest_function: DigestFunction::Ed25519Pub, - payload: hex_decode("1509A611AD6D97B01D871E58ED00C8FD7C3917B6CA61A8C2833A19E000AAC2E4") - .expect("Failed to decode hex."), + payload: ConstBytes::new( + hex_decode("1509A611AD6D97B01D871E58ED00C8FD7C3917B6CA61A8C2833A19E000AAC2E4") + .expect("Failed to decode hex."), + ), }; let bytes: Vec = multihash.try_into().expect("Failed to serialize multihash"); assert_eq!( @@ -237,8 +241,10 @@ mod tests { fn multihash_from_bytes() { let multihash = Multihash { digest_function: DigestFunction::Ed25519Pub, - payload: hex_decode("1509A611AD6D97B01D871E58ED00C8FD7C3917B6CA61A8C2833A19E000AAC2E4") - .expect("Failed to decode hex."), + payload: ConstBytes::new( + hex_decode("1509A611AD6D97B01D871E58ED00C8FD7C3917B6CA61A8C2833A19E000AAC2E4") + .expect("Failed to decode hex."), + ), }; let bytes = hex_decode("ed01201509A611AD6D97B01D871E58ED00C8FD7C3917B6CA61A8C2833A19E000AAC2E4") diff --git a/crypto/src/signature.rs b/crypto/src/signature.rs index e66c641bcef..62751a89957 100644 --- a/crypto/src/signature.rs +++ b/crypto/src/signature.rs @@ -8,6 +8,7 @@ use std::collections::btree_set; use derive_more::{Deref, DerefMut}; use iroha_macro::ffi_impl_opaque; +use iroha_primitives::const_bytes::ConstBytes; use iroha_schema::{IntoSchema, TypeId}; use parity_scale_codec::{Decode, Encode, Input}; #[cfg(not(feature = "ffi_import"))] @@ -34,7 +35,7 @@ ffi::ffi_item! { #[cfg_attr(not(feature="ffi_import"), derive(derive_more::DebugCustom, Hash, Decode, Encode, Deserialize, Serialize, IntoSchema))] #[cfg_attr(not(feature="ffi_import"), debug( fmt = "{{ pub_key: {public_key}, payload: {} }}", - "hex::encode_upper(payload.as_slice())" + "hex::encode_upper(payload)" ))] pub struct Signature { /// Public key that is used for verification. Payload is verified by algorithm @@ -42,7 +43,7 @@ ffi::ffi_item! { #[getset(get = "pub")] public_key: PublicKey, /// Signature payload - payload: Vec, + payload: ConstBytes, } } @@ -50,7 +51,7 @@ ffi::ffi_item! { impl Signature { /// Key payload pub fn payload(&self) -> &[u8] { - &self.payload + self.payload.as_ref() } /// Creates new [`Signature`] by signing payload via [`KeyPair::private_key`]. @@ -62,7 +63,7 @@ impl Signature { let (public_key, private_key) = key_pair.into(); let algorithm: crate::Algorithm = private_key.digest_function(); - let private_key = UrsaPrivateKey(private_key.payload); + let private_key = UrsaPrivateKey(private_key.payload.into_vec()); let signature = match algorithm { crate::Algorithm::Ed25519 => Ed25519Sha512::new().sign(payload, &private_key), @@ -70,10 +71,9 @@ impl Signature { crate::Algorithm::BlsSmall => BlsSmall::new().sign(payload, &private_key), crate::Algorithm::BlsNormal => BlsNormal::new().sign(payload, &private_key), }?; - Ok(Self { public_key, - payload: signature, + payload: ConstBytes::new(signature), }) } @@ -111,7 +111,7 @@ impl Signature { /// Get the encrypted payload of this signature. pub fn signature_payload(&self) -> &[u8] { - &self.payload + self.payload.as_ref() } } @@ -124,7 +124,7 @@ impl From for (PublicKey, Vec) { payload: signature, }: Signature, ) -> Self { - (public_key, signature) + (public_key, signature.into_vec()) } } diff --git a/primitives/Cargo.toml b/primitives/Cargo.toml index 43ef5bbabfb..9e7e4f8f84d 100644 --- a/primitives/Cargo.toml +++ b/primitives/Cargo.toml @@ -38,6 +38,7 @@ smallvec = { version = "1.10.0", default-features = false, features = ["serde", smallstr = { version = "0.3.0", default-features = false, features = ["serde", "union"] } thiserror = { workspace = true, optional = true } displaydoc = { workspace = true } +hex = { workspace = true, features = ["alloc"] } [dev-dependencies] diff --git a/primitives/src/const_bytes.rs b/primitives/src/const_bytes.rs new file mode 100644 index 00000000000..062d5f3b3e3 --- /dev/null +++ b/primitives/src/const_bytes.rs @@ -0,0 +1,92 @@ +//! An implementation of compact container for constant bytes. +#[cfg(not(feature = "std"))] +use alloc::{boxed::Box, format, string::String, vec::Vec}; +use core::ops::Deref; +#[cfg(feature = "std")] +use std::{boxed::Box, vec::Vec}; + +use iroha_schema::IntoSchema; +use parity_scale_codec::{WrapperTypeDecode, WrapperTypeEncode}; +use serde::{Deserialize, Serialize}; + +/// Stores bytes that are not supposed to change during the runtime of the program in a compact way +/// +/// This is a more efficient than `Vec` because it does not have to store the capacity field +/// +/// It does not do reference-counting, so cloning is not cheap +#[derive( + Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default, Serialize, Deserialize, IntoSchema, +)] +#[schema(transparent = "Vec")] +pub struct ConstBytes(Box<[u8]>); + +impl ConstBytes { + /// Create a new `ConstBytes` from something convertible into a `Box<[u8]>`. + /// + /// Using `Vec` here would take ownership of the data without needing to copy it (if length is the same as capacity). + pub fn new(bytes: impl Into>) -> Self { + Self(bytes.into()) + } + + /// Construct a new `ConstBytes` by parsing the given hex string. + /// + /// # Errors + /// + /// This function returns an error if the passed string is not a valid hex-encoded byte sequence. + /// (that is, it contains invalid characters or has an odd length). + pub fn from_hex + ?Sized>(payload: &T) -> Result { + Ok(Self::new(hex::decode(payload)?)) + } + + /// Converts the `ImmutableBytes` into a `Vec`, reusing the heap allocation. + pub fn into_vec(self) -> Vec { + self.0.into_vec() + } +} + +impl AsRef<[u8]> for ConstBytes { + fn as_ref(&self) -> &[u8] { + self.0.as_ref() + } +} + +impl Deref for ConstBytes { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl From> for ConstBytes { + fn from(value: Vec) -> Self { + Self::new(value) + } +} + +impl WrapperTypeEncode for ConstBytes {} +impl WrapperTypeDecode for ConstBytes { + type Wrapped = Vec; +} + +#[cfg(test)] +mod tests { + use parity_scale_codec::{Decode, Encode}; + + use super::ConstBytes; + + #[test] + fn encoded_repr_is_same_as_vec() { + let bytes = vec![1u8, 2, 3, 4, 5]; + let encoded = ConstBytes::new(bytes.clone()); + assert_eq!(bytes.encode(), encoded.encode()); + } + + #[test] + fn encode_decode_round_trip() { + let bytes = vec![1u8, 2, 3, 4, 5]; + let encoded = ConstBytes::new(bytes.clone()); + let decoded = ConstBytes::decode(&mut encoded.encode().as_slice()).unwrap(); + assert_eq!(bytes, decoded.into_vec()); + } +} diff --git a/primitives/src/lib.rs b/primitives/src/lib.rs index 5f4c3e0a324..3bc139facf3 100644 --- a/primitives/src/lib.rs +++ b/primitives/src/lib.rs @@ -12,6 +12,7 @@ extern crate alloc; pub mod addr; +pub mod const_bytes; #[cfg(not(feature = "ffi_import"))] pub mod conststr; pub mod fixed;