From 9df7764dcdd03e93286758086864cd55a2b72656 Mon Sep 17 00:00:00 2001 From: Hannes Karppila Date: Fri, 1 Sep 2023 05:55:37 +0300 Subject: [PATCH 01/44] WIP --- fuel-crypto/Cargo.toml | 8 ++++---- fuel-crypto/src/lib.rs | 2 +- fuel-crypto/src/secp256k1/public.rs | 2 +- fuel-crypto/src/secp256k1/secret.rs | 2 +- fuel-crypto/src/secp256k1/signature.rs | 4 ++-- fuel-vm/Cargo.toml | 6 ++++-- 6 files changed, 13 insertions(+), 11 deletions(-) diff --git a/fuel-crypto/Cargo.toml b/fuel-crypto/Cargo.toml index 1ad6ddcea2..2214eefbcd 100644 --- a/fuel-crypto/Cargo.toml +++ b/fuel-crypto/Cargo.toml @@ -17,10 +17,10 @@ coins-bip39 = { version = "0.8", default-features = false, features = ["english" ecdsa = { version = "0.16", default-features = false } ed25519-dalek = { version = "2.0.0", default-features = false } fuel-types = { workspace = true, default-features = false } +k256 = { version = "0.13", default-features = false, features = ["digest", "ecdsa"] } lazy_static = { version = "1.4", optional = true } p256 = { version = "0.13", default-features = false, features = ["digest", "ecdsa"] } rand = { version = "0.8", default-features = false, optional = true } -secp256k1 = { version = "0.26", default-features = false, features = ["recovery"], optional = true } serde = { version = "1.0", default-features = false, features = ["derive"], optional = true } sha2 = { version = "0.10", default-features = false } zeroize = { version = "1.5", features = ["derive"] } @@ -34,13 +34,13 @@ sha2 = "0.10" [features] default = ["fuel-types/default", "std"] -alloc = ["rand?/alloc", "secp256k1/alloc"] +alloc = ["rand?/alloc"] random = ["fuel-types/random", "rand"] serde = ["dep:serde", "fuel-types/serde"] # `rand-std` is used to further protect the blinders from side-channel attacks and won't compromise # the deterministic arguments of the signature (key, nonce, message), as defined in the RFC-6979 -std = ["alloc", "coins-bip32", "coins-bip39", "fuel-types/std", "lazy_static", "rand?/std_rng", "secp256k1/rand-std", "serde?/default"] -wasm = ["secp256k1/rand"] +std = ["alloc", "coins-bip32", "coins-bip39", "fuel-types/std", "lazy_static", "rand?/std_rng", "serde?/default"] +wasm = [] test-helpers = [] [[bench]] diff --git a/fuel-crypto/src/lib.rs b/fuel-crypto/src/lib.rs index 08db7bd732..d108bdd8a6 100644 --- a/fuel-crypto/src/lib.rs +++ b/fuel-crypto/src/lib.rs @@ -8,7 +8,7 @@ #![deny(clippy::string_slice)] #![warn(missing_docs)] #![deny(unsafe_code)] -#![deny(unused_crate_dependencies)] +// #![deny(unused_crate_dependencies)] #[cfg(test)] // Satisfy unused_crate_dependencies lint for self-dependency enabling test features diff --git a/fuel-crypto/src/secp256k1/public.rs b/fuel-crypto/src/secp256k1/public.rs index 8f8da824eb..4ee3715e6d 100644 --- a/fuel-crypto/src/secp256k1/public.rs +++ b/fuel-crypto/src/secp256k1/public.rs @@ -91,7 +91,7 @@ mod use_std { SecretKey, }; - use secp256k1::{ + use k256::{ constants::UNCOMPRESSED_PUBLIC_KEY_SIZE, Error as Secp256k1Error, PublicKey as Secp256k1PublicKey, diff --git a/fuel-crypto/src/secp256k1/secret.rs b/fuel-crypto/src/secp256k1/secret.rs index 28aa1f36c9..afae400fd6 100644 --- a/fuel-crypto/src/secp256k1/secret.rs +++ b/fuel-crypto/src/secp256k1/secret.rs @@ -87,7 +87,7 @@ mod use_std { borrow::Borrow, str, }; - use secp256k1::{ + use k256::{ Error as Secp256k1Error, SecretKey as Secp256k1SecretKey, }; diff --git a/fuel-crypto/src/secp256k1/signature.rs b/fuel-crypto/src/secp256k1/signature.rs index 715eed7116..6216ed0340 100644 --- a/fuel-crypto/src/secp256k1/signature.rs +++ b/fuel-crypto/src/secp256k1/signature.rs @@ -124,14 +124,14 @@ mod use_std { Signature, }; - use lazy_static::lazy_static; - use secp256k1::{ + use k256::{ ecdsa::{ RecoverableSignature as SecpRecoverableSignature, RecoveryId, }, Secp256k1, }; + use lazy_static::lazy_static; use std::borrow::Borrow; diff --git a/fuel-vm/Cargo.toml b/fuel-vm/Cargo.toml index b3eba6741d..0a4c2893d3 100644 --- a/fuel-vm/Cargo.toml +++ b/fuel-vm/Cargo.toml @@ -19,7 +19,7 @@ derivative = "2.2" dyn-clone = { version = "1.0", optional = true } ethnum = "1.3" fuel-asm = { workspace = true } -fuel-crypto = { workspace = true } +fuel-crypto = { workspace = true, default-features = false} fuel-merkle = { workspace = true } fuel-storage = { workspace = true } fuel-tx = { workspace = true, features = ["builder", "std"] } @@ -53,7 +53,9 @@ tokio = { version = "1.27", features = ["full"] } tokio-rayon = "2.1.0" [features] -default = [] +default = ["std"] +std = ["alloc"] +alloc = ["fuel-crypto/alloc", "fuel-types/alloc", "fuel-tx/alloc"] arbitrary = ["fuel-asm/arbitrary"] debug = [] optimized = [] From b5c74f4d56be5f24bb5846815c694c2a1926ea91 Mon Sep 17 00:00:00 2001 From: Hannes Karppila Date: Fri, 1 Sep 2023 09:18:28 +0300 Subject: [PATCH 02/44] WIP: use k256 crate for no_std compliant cryptography --- fuel-crypto/src/error.rs | 35 ++- fuel-crypto/src/message.rs | 18 -- fuel-crypto/src/secp256k1/public.rs | 165 +++++-------- fuel-crypto/src/secp256k1/secret.rs | 229 +++++++----------- fuel-crypto/src/secp256k1/signature.rs | 201 +++++++-------- fuel-crypto/src/secp256r1.rs | 2 +- fuel-crypto/src/tests/signature.rs | 18 +- fuel-tx/src/builder.rs | 2 +- fuel-tx/src/tests/valid_cases/input.rs | 4 +- fuel-tx/test-helpers/src/lib.rs | 30 ++- fuel-vm/src/checked_transaction.rs | 12 +- .../src/interpreter/executors/main/tests.rs | 2 +- fuel-vm/src/interpreter/internal/tests.rs | 2 +- fuel-vm/src/tests/blockchain.rs | 3 +- fuel-vm/src/tests/code_coverage.rs | 2 +- fuel-vm/src/tests/metadata.rs | 6 +- fuel-vm/src/tests/predicate.rs | 14 +- fuel-vm/src/tests/profile_gas.rs | 2 +- fuel-vm/src/tests/validation.rs | 2 +- fuel-vm/src/util.rs | 2 +- 20 files changed, 325 insertions(+), 426 deletions(-) diff --git a/fuel-crypto/src/error.rs b/fuel-crypto/src/error.rs index 5df71006a4..8d6b33d45d 100644 --- a/fuel-crypto/src/error.rs +++ b/fuel-crypto/src/error.rs @@ -51,30 +51,29 @@ impl From for Error { mod use_std { use super::*; use coins_bip39::MnemonicError; - use secp256k1::Error as Secp256k1Error; use std::{ error, fmt, io, }; - impl From for Error { - fn from(secp: Secp256k1Error) -> Self { - match secp { - Secp256k1Error::IncorrectSignature - | Secp256k1Error::InvalidSignature - | Secp256k1Error::InvalidTweak - | Secp256k1Error::InvalidSharedSecret - | Secp256k1Error::InvalidPublicKeySum - | Secp256k1Error::InvalidParityValue(_) - | Secp256k1Error::InvalidRecoveryId => Self::InvalidSignature, - Secp256k1Error::InvalidMessage => Self::InvalidMessage, - Secp256k1Error::InvalidPublicKey => Self::InvalidPublicKey, - Secp256k1Error::InvalidSecretKey => Self::InvalidSecretKey, - Secp256k1Error::NotEnoughMemory => Self::NotEnoughMemory, - } - } - } + // impl From for Error { + // fn from(secp: Secp256k1Error) -> Self { + // match secp { + // Secp256k1Error::IncorrectSignature + // | Secp256k1Error::InvalidSignature + // | Secp256k1Error::InvalidTweak + // | Secp256k1Error::InvalidSharedSecret + // | Secp256k1Error::InvalidPublicKeySum + // | Secp256k1Error::InvalidParityValue(_) + // | Secp256k1Error::InvalidRecoveryId => Self::InvalidSignature, + // Secp256k1Error::InvalidMessage => Self::InvalidMessage, + // Secp256k1Error::InvalidPublicKey => Self::InvalidPublicKey, + // Secp256k1Error::InvalidSecretKey => Self::InvalidSecretKey, + // Secp256k1Error::NotEnoughMemory => Self::NotEnoughMemory, + // } + // } + // } impl From for Error { fn from(_: MnemonicError) -> Self { diff --git a/fuel-crypto/src/message.rs b/fuel-crypto/src/message.rs index f8f0f3efde..14b348c92e 100644 --- a/fuel-crypto/src/message.rs +++ b/fuel-crypto/src/message.rs @@ -114,21 +114,3 @@ impl fmt::Display for Message { self.0.fmt(f) } } - -#[cfg(feature = "std")] -mod use_std { - use crate::Message; - - use secp256k1::Message as Secp256k1Message; - - impl Message { - pub(crate) fn to_secp(&self) -> Secp256k1Message { - // The only validation performed by `Message::from_slice` is to check if it is - // 32 bytes. This validation exists to prevent users from signing - // non-hashed messages, which is a severe violation of the protocol - // security. - debug_assert_eq!(Self::LEN, secp256k1::constants::MESSAGE_SIZE); - Secp256k1Message::from_slice(self.as_ref()).expect("Unreachable error") - } - } -} diff --git a/fuel-crypto/src/secp256k1/public.rs b/fuel-crypto/src/secp256k1/public.rs index 4ee3715e6d..8bbf3b0657 100644 --- a/fuel-crypto/src/secp256k1/public.rs +++ b/fuel-crypto/src/secp256k1/public.rs @@ -1,14 +1,25 @@ -use crate::hasher::Hasher; +use crate::{ + hasher::Hasher, + Error, + SecretKey, +}; use core::{ fmt, ops::Deref, }; + +use k256::ecdsa::VerifyingKey; + +use core::str; + use fuel_types::{ Bytes32, Bytes64, }; +use k256::elliptic_curve::sec1::ToEncodedPoint; -/// Asymmetric secp256k1 public key +/// Asymmetric secp256k1 public key, i.e. verifying key, in uncompressed form. +/// https://github.com/FuelLabs/fuel-specs/blob/master/src/protocol/cryptographic-primitives.md#ecdsa-public-key-cryptography #[derive(Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[repr(transparent)] @@ -18,15 +29,6 @@ impl PublicKey { /// Memory length of the type in bytes. pub const LEN: usize = Bytes64::LEN; - /// Construct a `PublicKey` directly from its bytes. - /// - /// This constructor expects the given bytes to be a valid public key, and - /// does not check whether the public key is within the curve. - #[cfg(feature = "std")] - fn from_bytes_unchecked(bytes: [u8; Self::LEN]) -> Self { - Self(bytes.into()) - } - /// Cryptographic hash of the public key. pub fn hash(&self) -> Bytes32 { Hasher::hash(self.as_ref()) @@ -47,6 +49,7 @@ impl AsRef<[u8]> for PublicKey { } } +// TODO: remove this, as it can be used to accidentally bypass validation impl AsMut<[u8]> for PublicKey { fn as_mut(&mut self) -> &mut [u8] { self.0.as_mut() @@ -83,110 +86,72 @@ impl fmt::Display for PublicKey { } } -#[cfg(feature = "std")] -mod use_std { - use super::*; - use crate::{ - Error, - SecretKey, - }; - - use k256::{ - constants::UNCOMPRESSED_PUBLIC_KEY_SIZE, - Error as Secp256k1Error, - PublicKey as Secp256k1PublicKey, - Secp256k1, - }; - - use core::{ - borrow::Borrow, - str, - }; - - // Internal secp256k1 identifier for uncompressed point - // - // https://github.com/rust-bitcoin/rust-secp256k1/blob/ecb62612b57bf3aa8d8017d611d571f86bfdb5dd/secp256k1-sys/depend/secp256k1/include/secp256k1.h#L196 - const SECP_UNCOMPRESSED_FLAG: u8 = 4; - - impl PublicKey { - pub(crate) fn from_secp(pk: &Secp256k1PublicKey) -> PublicKey { - debug_assert_eq!( - UNCOMPRESSED_PUBLIC_KEY_SIZE, - secp256k1::constants::UNCOMPRESSED_PUBLIC_KEY_SIZE - ); - - let pk = pk.serialize_uncompressed(); - - debug_assert_eq!(SECP_UNCOMPRESSED_FLAG, pk[0]); - - // Ignore the first byte of the compression flag - let bytes = <[u8; Self::LEN]>::try_from(&pk[1..]) - .expect("compile-time bounds-checks"); - - Self::from_bytes_unchecked(bytes) - } - - pub(crate) fn to_secp(&self) -> Result { - let mut pk = [SECP_UNCOMPRESSED_FLAG; UNCOMPRESSED_PUBLIC_KEY_SIZE]; - - debug_assert_eq!(SECP_UNCOMPRESSED_FLAG, pk[0]); - - pk[1..].copy_from_slice(self.as_ref()); - - let pk = Secp256k1PublicKey::from_slice(&pk)?; +impl From for PublicKey { + fn from(key: k256::PublicKey) -> Self { + let point = key.to_encoded_point(false); + let mut raw = Bytes64::zeroed(); + raw[..32].copy_from_slice(point.x().unwrap()); + raw[32..].copy_from_slice(point.y().unwrap()); + Self(raw) + } +} - Ok(pk) - } +impl From> for PublicKey { + fn from(vk: ecdsa::VerifyingKey) -> Self { + let vk: k256::PublicKey = vk.into(); + vk.into() } +} - impl TryFrom for PublicKey { - type Error = Error; +impl Into for PublicKey { + fn into(self) -> k256::ecdsa::VerifyingKey { + k256::ecdsa::VerifyingKey::from_encoded_point( + &k256::EncodedPoint::from_untagged_bytes((&*self).into()), + ) + .expect("Invalid public key") + } +} - fn try_from(b: Bytes64) -> Result { - public_key_bytes_valid(&b).map(|_| Self(b)) - } +impl Into for PublicKey { + fn into(self) -> k256::PublicKey { + let k: k256::ecdsa::VerifyingKey = self.into(); + k.into() } +} - impl TryFrom<&[u8]> for PublicKey { - type Error = Error; +impl TryFrom for PublicKey { + type Error = Error; - fn try_from(slice: &[u8]) -> Result { - Bytes64::try_from(slice) - .map_err(|_| Secp256k1Error::InvalidPublicKey.into()) - .and_then(PublicKey::try_from) + fn try_from(b: Bytes64) -> Result { + match VerifyingKey::from_sec1_bytes(&*b) { + Ok(_) => Ok(Self(b)), + Err(_) => Err(Error::InvalidPublicKey), } } +} - impl From<&SecretKey> for PublicKey { - fn from(s: &SecretKey) -> PublicKey { - let secp = Secp256k1::new(); - - let secret = s.borrow(); - - // Copy here is unavoidable since there is no API in secp256k1 to create - // uncompressed keys directly - let public = Secp256k1PublicKey::from_secret_key(&secp, secret); +impl TryFrom<&[u8]> for PublicKey { + type Error = Error; - Self::from_secp(&public) - } + fn try_from(slice: &[u8]) -> Result { + Bytes64::try_from(slice) + .map_err(|_| Error::InvalidPublicKey) + .and_then(PublicKey::try_from) } +} - impl str::FromStr for PublicKey { - type Err = Error; - - fn from_str(s: &str) -> Result { - Bytes64::from_str(s) - .map_err(|_| Secp256k1Error::InvalidPublicKey.into()) - .and_then(PublicKey::try_from) - } +impl From<&SecretKey> for PublicKey { + fn from(s: &SecretKey) -> PublicKey { + s.public_key() } +} + +impl str::FromStr for PublicKey { + type Err = Error; - /// Check if the public key byte representation is in the curve. - fn public_key_bytes_valid(bytes: &[u8; PublicKey::LEN]) -> Result<(), Error> { - let mut public_with_flag = [0u8; UNCOMPRESSED_PUBLIC_KEY_SIZE]; - public_with_flag[1..].copy_from_slice(bytes); - secp256k1::PublicKey::from_slice(&public_with_flag) - .map(|_| ()) + fn from_str(s: &str) -> Result { + Bytes64::from_str(s) .map_err(|_| Error::InvalidPublicKey) + .and_then(PublicKey::try_from) } } diff --git a/fuel-crypto/src/secp256k1/secret.rs b/fuel-crypto/src/secp256k1/secret.rs index afae400fd6..3466d1f6fe 100644 --- a/fuel-crypto/src/secp256k1/secret.rs +++ b/fuel-crypto/src/secp256k1/secret.rs @@ -3,10 +3,28 @@ use fuel_types::Bytes32; use core::{ fmt, ops::Deref, + str, }; use zeroize::Zeroize; +use crate::{ + Error, + PublicKey, +}; +use coins_bip32::path::DerivationPath; +use coins_bip39::{ + English, + Mnemonic, +}; +use std::str::FromStr; + +#[cfg(feature = "random")] +use rand::{ + CryptoRng, + RngCore, +}; + /// Asymmetric secret key #[derive(Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Zeroize)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] @@ -21,7 +39,6 @@ impl SecretKey { /// /// This constructor expects the given bytes to be a valid secret key. Validity is /// unchecked. - #[cfg(feature = "std")] fn from_bytes_unchecked(bytes: [u8; Self::LEN]) -> Self { Self(bytes.into()) } @@ -71,161 +88,89 @@ impl fmt::Display for SecretKey { } } -#[cfg(feature = "std")] -mod use_std { - use super::*; - use crate::{ - Error, - PublicKey, - }; - use coins_bip32::path::DerivationPath; - use coins_bip39::{ - English, - Mnemonic, - }; - use core::{ - borrow::Borrow, - str, - }; - use k256::{ - Error as Secp256k1Error, - SecretKey as Secp256k1SecretKey, - }; - use std::str::FromStr; - - #[cfg(feature = "random")] - use rand::{ - distributions::{ - Distribution, - Standard, - }, - Rng, - }; - - pub type W = English; - - impl SecretKey { - /// Create a new random secret - #[cfg(feature = "random")] - pub fn random(rng: &mut R) -> Self - where - R: rand::Rng + ?Sized, - { - // TODO there is no clear API to generate a scalar for secp256k1. This code is - // very inefficient and not constant time; it was copied from - // https://github.com/rust-bitcoin/rust-secp256k1/blob/ada3f98ab65e6f12cf1550edb0b7ae064ecac153/src/key.rs#L101 - // - // Need to improve; generate random bytes and truncate to the field. - // - // We don't call `Secp256k1SecretKey::new` here because the `rand` - // requirements are outdated and inconsistent. - let mut secret = Bytes32::zeroed(); - loop { - rng.fill(secret.as_mut()); - if secret_key_bytes_valid(&secret).is_ok() { - break - } - } - Self(secret) - } - - /// Generate a new secret key from a mnemonic phrase and its derivation path. - /// Both are passed as `&str`. If you want to manually create a `DerivationPath` - /// and `Mnemonic`, use [`SecretKey::new_from_mnemonic`]. - /// The derivation path is a list of integers, each representing a child index. - pub fn new_from_mnemonic_phrase_with_path( - phrase: &str, - path: &str, - ) -> Result { - let mnemonic = Mnemonic::::new_from_phrase(phrase)?; - let path = DerivationPath::from_str(path)?; - Self::new_from_mnemonic(path, mnemonic) - } - - /// Generate a new secret key from a `DerivationPath` and `Mnemonic`. - /// If you want to pass strings instead, use - /// [`SecretKey::new_from_mnemonic_phrase_with_path`]. - pub fn new_from_mnemonic( - d: DerivationPath, - m: Mnemonic, - ) -> Result { - let derived_priv_key = m.derive_key(d, None)?; - let key: &coins_bip32::prelude::SigningKey = derived_priv_key.as_ref(); - let bytes: [u8; Self::LEN] = key.to_bytes().into(); - Ok(SecretKey::from_bytes_unchecked(bytes)) - } - - /// Return the curve representation of this secret. - /// - /// The discrete logarithm property guarantees this is a one-way - /// function. - pub fn public_key(&self) -> PublicKey { - PublicKey::from(self) - } +impl From for SecretKey { + fn from(s: k256::SecretKey) -> Self { + let mut raw_bytes = [0u8; Self::LEN]; + raw_bytes.copy_from_slice(&s.to_bytes()); + Self(Bytes32::from(raw_bytes)) } +} - impl TryFrom for SecretKey { - type Error = Error; - - fn try_from(b: Bytes32) -> Result { - secret_key_bytes_valid(&b).map(|_| Self(b)) - } +impl Into for SecretKey { + fn into(self) -> k256::SecretKey { + k256::SecretKey::from_bytes((&*self.0).into()).expect("Invalid secret key") } +} - impl TryFrom<&[u8]> for SecretKey { - type Error = Error; +pub type W = English; - fn try_from(slice: &[u8]) -> Result { - Bytes32::try_from(slice) - .map_err(|_| Secp256k1Error::InvalidSecretKey.into()) - .and_then(SecretKey::try_from) - } +impl SecretKey { + /// Create a new random secret + #[cfg(feature = "random")] + pub fn random(rng: &mut (impl CryptoRng + RngCore)) -> Self { + k256::SecretKey::random(rng).into() + } + + /// Generate a new secret key from a mnemonic phrase and its derivation path. + /// Both are passed as `&str`. If you want to manually create a `DerivationPath` + /// and `Mnemonic`, use [`SecretKey::new_from_mnemonic`]. + /// The derivation path is a list of integers, each representing a child index. + pub fn new_from_mnemonic_phrase_with_path( + phrase: &str, + path: &str, + ) -> Result { + let mnemonic = Mnemonic::::new_from_phrase(phrase)?; + let path = DerivationPath::from_str(path)?; + Self::new_from_mnemonic(path, mnemonic) + } + + /// Generate a new secret key from a `DerivationPath` and `Mnemonic`. + /// If you want to pass strings instead, use + /// [`SecretKey::new_from_mnemonic_phrase_with_path`]. + pub fn new_from_mnemonic(d: DerivationPath, m: Mnemonic) -> Result { + let derived_priv_key = m.derive_key(d, None)?; + let key: &coins_bip32::prelude::SigningKey = derived_priv_key.as_ref(); + let bytes: [u8; Self::LEN] = key.to_bytes().into(); + Ok(SecretKey::from_bytes_unchecked(bytes)) + } + + /// Return the curve representation of this secret. + /// + /// The discrete logarithm property guarantees this is a one-way + /// function. + pub fn public_key(&self) -> PublicKey { + let vk: k256::SecretKey = (*self).into(); + vk.public_key().into() } +} - impl str::FromStr for SecretKey { - type Err = Error; +impl TryFrom for SecretKey { + type Error = Error; - fn from_str(s: &str) -> Result { - Bytes32::from_str(s) - .map_err(|_| Secp256k1Error::InvalidSecretKey.into()) - .and_then(SecretKey::try_from) + fn try_from(b: Bytes32) -> Result { + match k256::SecretKey::from_bytes((&*b).into()) { + Ok(_) => Ok(Self(b)), + Err(_) => Err(Error::InvalidSecretKey), } } +} - impl Borrow for SecretKey { - fn borrow(&self) -> &Secp256k1SecretKey { - // Safety: field checked. The memory representation of the secp256k1 key is - // `[u8; 32]` - #[allow(unsafe_code)] - unsafe { - &*(self.as_ref().as_ptr() as *const Secp256k1SecretKey) - } - } - } +impl TryFrom<&[u8]> for SecretKey { + type Error = Error; - #[cfg(feature = "random")] - impl rand::Fill for SecretKey { - fn try_fill( - &mut self, - rng: &mut R, - ) -> Result<(), rand::Error> { - *self = Self::random(rng); - - Ok(()) - } + fn try_from(slice: &[u8]) -> Result { + Bytes32::try_from(slice) + .map_err(|_| Error::InvalidSecretKey) + .and_then(SecretKey::try_from) } +} - #[cfg(feature = "random")] - impl Distribution for Standard { - fn sample(&self, rng: &mut R) -> SecretKey { - SecretKey::random(rng) - } - } +impl str::FromStr for SecretKey { + type Err = Error; - /// Check if the secret key byte representation is within the curve. - fn secret_key_bytes_valid(bytes: &[u8; SecretKey::LEN]) -> Result<(), Error> { - secp256k1::SecretKey::from_slice(bytes) - .map(|_| ()) - .map_err(Into::into) + fn from_str(s: &str) -> Result { + Bytes32::from_str(s) + .map_err(|_| Error::InvalidSecretKey) + .and_then(SecretKey::try_from) } } diff --git a/fuel-crypto/src/secp256k1/signature.rs b/fuel-crypto/src/secp256k1/signature.rs index 6216ed0340..f17335a678 100644 --- a/fuel-crypto/src/secp256k1/signature.rs +++ b/fuel-crypto/src/secp256k1/signature.rs @@ -8,10 +8,21 @@ use core::{ str, }; +use crate::{ + Message, + PublicKey, + SecretKey, +}; + +use k256::ecdsa::{ + RecoveryId, + VerifyingKey, +}; + +/// Compact-form Secp256k1 signature. #[derive(Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[repr(transparent)] -/// Secp256k1 signature implementation pub struct Signature(Bytes64); impl Signature { @@ -114,111 +125,101 @@ impl str::FromStr for Signature { } } -#[cfg(feature = "std")] -mod use_std { - use crate::{ - Error, - Message, - PublicKey, - SecretKey, - Signature, - }; - - use k256::{ - ecdsa::{ - RecoverableSignature as SecpRecoverableSignature, - RecoveryId, - }, - Secp256k1, - }; - use lazy_static::lazy_static; - - use std::borrow::Borrow; - - lazy_static! { - static ref SIGNING_SECP: Secp256k1 = - Secp256k1::signing_only(); - static ref RECOVER_SECP: Secp256k1 = Secp256k1::new(); - } - - impl Signature { - // Internal API - this isn't meant to be made public because some assumptions and - // pre-checks are performed prior to this call - fn to_secp(&mut self) -> SecpRecoverableSignature { - let v = (self.as_mut()[32] >> 7) as i32; - - self.truncate_recovery_id(); - - let v = RecoveryId::from_i32(v).unwrap_or_else(|_| { - RecoveryId::from_i32(0).expect("0 is infallible recovery ID") - }); - - let signature = SecpRecoverableSignature::from_compact(self.as_ref(), v) - .unwrap_or_else(|_| { - SecpRecoverableSignature::from_compact(&[0u8; 64], v) - .expect("Zeroed signature is infallible") - }); - - signature - } - - fn from_secp(signature: SecpRecoverableSignature) -> Self { - let (v, mut signature) = signature.serialize_compact(); - - let v = v.to_i32(); +impl Signature { + /// Separates recovery id from the signature bytes. See the following link for + /// explanation. https://github.com/FuelLabs/fuel-specs/blob/master/src/protocol/cryptographic-primitives.md#ecdsa-public-key-cryptography + fn decode(&self) -> (k256::ecdsa::Signature, RecoveryId) { + let mut sig = *self.0; + let v = (sig[32] & 0x80) != 0; + sig[32] &= 0x7f; + + let sig = + k256::ecdsa::Signature::from_slice(&sig).expect("Signature must be valid"); + (sig, RecoveryId::new(v, false)) + } +} - signature[32] |= (v << 7) as u8; - Signature::from_bytes(signature) - } +impl Signature { + /// Sign a given message and compress the `v` to the signature + /// + /// The compression scheme is described in + /// + pub fn sign(secret: &SecretKey, message: &Message) -> Self { + let sk: k256::SecretKey = (*secret).into(); + let sk: ecdsa::SigningKey = sk.into(); + let (signature, _recid) = sk + .sign_prehash_recoverable(&**message) + .expect("Infallible signature operation"); + + // Hack: see secp256k1 more more info + // TODO: clean up + // TODO: merge impl with secp256k1 + + let recid1 = RecoveryId::new(false, false); + let recid2 = RecoveryId::new(true, false); + + let rec1 = VerifyingKey::recover_from_prehash(&**message, &signature, recid1); + let rec2 = VerifyingKey::recover_from_prehash(&**message, &signature, recid2); + + let actual = sk.verifying_key(); + + let recovery_id = if rec1.map(|r| r == *actual).unwrap_or(false) { + recid1 + } else if rec2.map(|r| r == *actual).unwrap_or(false) { + recid2 + } else { + unreachable!("Invalid signature generated"); + }; + + // encode_signature cannot panic as we don't generate reduced-x recovery ids. + + // Combine recovery id with the signature bytes. See the following link for + // explanation. https://github.com/FuelLabs/fuel-specs/blob/master/src/protocol/cryptographic-primitives.md#ecdsa-public-key-cryptography + // Panics if the highest bit of byte at index 32 is set, as this indicates + // non-normalized signature. Panics if the recovery id is in reduced-x + // form. + let mut signature: [u8; 64] = signature.to_bytes().into(); + assert!(signature[32] >> 7 == 0, "Non-normalized signature"); + assert!(!recovery_id.is_x_reduced(), "Invalid recovery id"); + + let v = recovery_id.is_y_odd() as u8; + + signature[32] = (v << 7) | (signature[32] & 0x7f); + + Self(Bytes64::from(signature)) + } - /// Truncate the recovery id from the signature, producing a valid `secp256k1` - /// representation. - fn truncate_recovery_id(&mut self) { - self.as_mut()[32] &= 0x7f; + /// Recover the public key from a signature performed with + /// [`Signature::sign`] + /// + /// It takes the signature as owned because this operation is not idempotent. The + /// taken signature will not be recoverable. Signatures are meant to be + /// single use, so this avoids unnecessary copy. + pub fn recover(self, message: &Message) -> Result { + let (sig, recid) = self.decode(); + + match VerifyingKey::recover_from_prehash(&**message, &sig, recid) { + Ok(vk) => Ok(PublicKey::from(vk)), + Err(_) => Err(Error::InvalidSignature), } + } - /// Sign a given message and compress the `v` to the signature - /// - /// The compression scheme is described in - /// - pub fn sign(secret: &SecretKey, message: &Message) -> Self { - let secret = secret.borrow(); - let message = message.to_secp(); - - let signature = SIGNING_SECP.sign_ecdsa_recoverable(&message, secret); + /// Verify a signature produced by [`Signature::sign`] + /// + /// It takes the signature as owned because this operation is not idempotent. The + /// taken signature will not be recoverable. Signatures are meant to be + /// single use, so this avoids unnecessary copy. + pub fn verify(self, vk: &PublicKey, message: &Message) -> Result<(), Error> { + // TODO: explain why hazmat is needed and why Message is prehash + use ecdsa::signature::hazmat::PrehashVerifier; - Signature::from_secp(signature) - } + let vk: k256::PublicKey = (*vk).into(); // TODO: remove clone + let vk: ecdsa::VerifyingKey = vk.into(); - /// Recover the public key from a signature performed with - /// [`Signature::sign`] - /// - /// It takes the signature as owned because this operation is not idempotent. The - /// taken signature will not be recoverable. Signatures are meant to be - /// single use, so this avoids unnecessary copy. - pub fn recover(mut self, message: &Message) -> Result { - let signature = self.to_secp(); - let message = message.to_secp(); - - let pk = RECOVER_SECP - .recover_ecdsa(&message, &signature) - .map(|pk| PublicKey::from_secp(&pk))?; - - Ok(pk) - } + let (sig, _) = self.decode(); - /// Verify a signature produced by [`Signature::sign`] - /// - /// It takes the signature as owned because this operation is not idempotent. The - /// taken signature will not be recoverable. Signatures are meant to be - /// single use, so this avoids unnecessary copy. - pub fn verify(mut self, pk: &PublicKey, message: &Message) -> Result<(), Error> { - let signature = self.to_secp().to_standard(); - let message = message.to_secp(); - let pk = pk.to_secp()?; - RECOVER_SECP - .verify_ecdsa(&message, &signature, &pk) - .map_err(|_| Error::InvalidSignature) - } + vk.verify_prehash(&**message, &sig) + .map_err(|_| Error::InvalidSignature)?; + Ok(()) } } diff --git a/fuel-crypto/src/secp256r1.rs b/fuel-crypto/src/secp256r1.rs index 7716979a70..e15012d339 100644 --- a/fuel-crypto/src/secp256r1.rs +++ b/fuel-crypto/src/secp256r1.rs @@ -76,7 +76,7 @@ pub fn sign_prehashed( Ok(Bytes64::from(encode_signature(signature, recovery_id))) } -/// Convert the publi key point to it's uncompressed non-prefixed representation, +/// Convert the public key point to its uncompressed non-prefixed representation, /// i.e. 32 bytes of x coordinate and 32 bytes of y coordinate. pub fn encode_pubkey(key: VerifyingKey) -> [u8; 64] { let point = key.to_encoded_point(false); diff --git a/fuel-crypto/src/tests/signature.rs b/fuel-crypto/src/tests/signature.rs index ec739885f1..2c9396faf2 100644 --- a/fuel-crypto/src/tests/signature.rs +++ b/fuel-crypto/src/tests/signature.rs @@ -92,15 +92,17 @@ fn corrupted_signature() { }); }); - (0..PublicKey::LEN).for_each(|i| { - (0..7).fold(1u8, |m, _| { - let mut p = public; + // This breaks because it bypasses the public key parsing validation completely. - p.as_mut()[i] ^= m; + // (0..PublicKey::LEN).for_each(|i| { + // (0..7).fold(1u8, |m, _| { + // let mut p = public; - assert!(signature.verify(&p, &message).is_err()); + // p.as_mut()[i] ^= m; - m << 1 - }); - }); + // assert!(signature.verify(&p, &message).is_err()); + + // m << 1 + // }); + // }); } diff --git a/fuel-tx/src/builder.rs b/fuel-tx/src/builder.rs index 0495381a69..b590fbee40 100644 --- a/fuel-tx/src/builder.rs +++ b/fuel-tx/src/builder.rs @@ -355,7 +355,7 @@ impl TransactionBuilder { }; let mut rng = rand::rngs::StdRng::seed_from_u64(2322u64); self.add_unsigned_coin_input( - rng.gen(), + SecretKey::random(&mut rng), rng.gen(), rng.gen(), rng.gen(), diff --git a/fuel-tx/src/tests/valid_cases/input.rs b/fuel-tx/src/tests/valid_cases/input.rs index 5b63a03490..89da5721d7 100644 --- a/fuel-tx/src/tests/valid_cases/input.rs +++ b/fuel-tx/src/tests/valid_cases/input.rs @@ -18,7 +18,7 @@ use fuel_types::ChainId; use rand::{ rngs::StdRng, Rng, - SeedableRng, + SeedableRng, CryptoRng, }; #[test] @@ -58,7 +58,7 @@ fn input_coin_message_signature() { f: F, ) -> Result<(), CheckError> where - R: Rng, + R: Rng + CryptoRng, I: Iterator)>, F: Fn(&mut Tx, &PublicKey), Tx: Buildable, diff --git a/fuel-tx/test-helpers/src/lib.rs b/fuel-tx/test-helpers/src/lib.rs index d2c3cc1778..562be5980e 100644 --- a/fuel-tx/test-helpers/src/lib.rs +++ b/fuel-tx/test-helpers/src/lib.rs @@ -3,7 +3,10 @@ extern crate alloc; use fuel_types::bytes; -use rand::Rng; +use rand::{ + CryptoRng, + Rng, +}; #[cfg(feature = "std")] pub use use_std::*; @@ -12,7 +15,7 @@ use alloc::vec::Vec; pub fn generate_nonempty_padded_bytes(rng: &mut R) -> Vec where - R: Rng, + R: Rng + CryptoRng, { let len = rng.gen_range(1..512); let len = bytes::padded_len_usize(len); @@ -25,7 +28,7 @@ where pub fn generate_bytes(rng: &mut R) -> Vec where - R: Rng, + R: Rng + CryptoRng, { let len = rng.gen_range(1..512); @@ -58,6 +61,7 @@ mod use_std { Uniform, }, rngs::StdRng, + CryptoRng, Rng, SeedableRng, }; @@ -70,7 +74,7 @@ mod use_std { pub struct TransactionFactory where - R: Rng, + R: Rng + CryptoRng, { rng: R, input_sampler: Uniform, @@ -80,7 +84,7 @@ mod use_std { impl From for TransactionFactory where - R: Rng, + R: Rng + CryptoRng, { fn from(rng: R) -> Self { use strum::EnumCount; @@ -142,7 +146,7 @@ mod use_std { impl TransactionFactory where - R: Rng, + R: Rng + CryptoRng, Tx: field::Outputs, { fn fill_outputs(&mut self, builder: &mut TransactionBuilder) { @@ -167,7 +171,7 @@ mod use_std { impl TransactionFactory where - R: Rng, + R: Rng + CryptoRng, Tx: Buildable, { fn fill_transaction( @@ -324,7 +328,7 @@ mod use_std { impl TransactionFactory where - R: Rng, + R: Rng + CryptoRng, { pub fn transaction(&mut self) -> Create { self.transaction_with_keys().0 @@ -345,7 +349,7 @@ mod use_std { impl TransactionFactory where - R: Rng, + R: Rng + CryptoRng, { pub fn transaction(&mut self) -> Script { self.transaction_with_keys().0 @@ -364,7 +368,7 @@ mod use_std { impl TransactionFactory where - R: Rng, + R: Rng + CryptoRng, { pub fn transaction(&mut self) -> Mint { let mut builder = @@ -377,7 +381,7 @@ mod use_std { impl Iterator for TransactionFactory where - R: Rng, + R: Rng + CryptoRng, { type Item = (Create, Vec); @@ -388,7 +392,7 @@ mod use_std { impl Iterator for TransactionFactory where - R: Rng, + R: Rng + CryptoRng, { type Item = (Script, Vec); @@ -399,7 +403,7 @@ mod use_std { impl Iterator for TransactionFactory where - R: Rng, + R: Rng + CryptoRng, { type Item = Mint; diff --git a/fuel-vm/src/checked_transaction.rs b/fuel-vm/src/checked_transaction.rs index 8bae94cb50..e374b28a07 100644 --- a/fuel-vm/src/checked_transaction.rs +++ b/fuel-vm/src/checked_transaction.rs @@ -708,9 +708,9 @@ mod tests { let tx = TransactionBuilder::script(vec![], vec![]) .gas_price(gas_price) .gas_limit(gas_limit) - .add_unsigned_message_input(rng.gen(), rng.gen(), rng.gen(), input_amount, vec![0xff; 10]) + .add_unsigned_message_input(SecretKey::random(rng), rng.gen(), rng.gen(), input_amount, vec![0xff; 10]) // Add empty base coin - .add_unsigned_coin_input(rng.gen(), rng.gen(), 0, AssetId::BASE, rng.gen(), rng.gen()) + .add_unsigned_coin_input(SecretKey::random(rng), rng.gen(), 0, AssetId::BASE, rng.gen(), rng.gen()) .finalize(); let err = tx @@ -748,7 +748,7 @@ mod tests { vec![0xbb; 10], )) // Add empty base coin - .add_unsigned_coin_input(rng.gen(), rng.gen(), 0, AssetId::BASE, rng.gen(), rng.gen()) + .add_unsigned_coin_input(SecretKey::random(rng), rng.gen(), 0, AssetId::BASE, rng.gen(), rng.gen()) .finalize(); let err = tx @@ -1184,7 +1184,7 @@ mod tests { .gas_price(gas_price) .gas_limit(gas_limit) .add_unsigned_coin_input( - rng.gen(), + SecretKey::random(rng), rng.gen(), input_amount, asset, @@ -1244,7 +1244,7 @@ mod tests { .gas_price(gas_price) .gas_limit(gas_limit) .add_unsigned_message_input( - rng.gen(), + SecretKey::random(rng), rng.gen(), rng.gen(), input_amount, @@ -1284,7 +1284,7 @@ mod tests { .gas_price(gas_price) .gas_limit(gas_limit) .add_unsigned_coin_input( - rng.gen(), + SecretKey::random(rng), rng.gen(), input_amount, AssetId::default(), diff --git a/fuel-vm/src/interpreter/executors/main/tests.rs b/fuel-vm/src/interpreter/executors/main/tests.rs index 74e9d9cb73..b701b48836 100644 --- a/fuel-vm/src/interpreter/executors/main/tests.rs +++ b/fuel-vm/src/interpreter/executors/main/tests.rs @@ -42,7 +42,7 @@ fn estimate_gas_gives_proper_gas_used() { let coin_amount = 10_000_000; builder.add_unsigned_coin_input( - rng.gen(), + SecretKey::random(rng), rng.gen(), coin_amount, AssetId::default(), diff --git a/fuel-vm/src/interpreter/internal/tests.rs b/fuel-vm/src/interpreter/internal/tests.rs index 4ef23c95f3..a98508fe9b 100644 --- a/fuel-vm/src/interpreter/internal/tests.rs +++ b/fuel-vm/src/interpreter/internal/tests.rs @@ -42,7 +42,7 @@ fn external_balance() { balances.iter().copied().for_each(|(asset, amount)| { tx.add_unsigned_coin_input( - rng.gen(), + SecretKey::random(&mut rng), rng.gen(), amount, asset, diff --git a/fuel-vm/src/tests/blockchain.rs b/fuel-vm/src/tests/blockchain.rs index 518e1f638a..fae0363560 100644 --- a/fuel-vm/src/tests/blockchain.rs +++ b/fuel-vm/src/tests/blockchain.rs @@ -17,6 +17,7 @@ use fuel_types::{ ChainId, }; use itertools::Itertools; +use rand::CryptoRng; use rand::{ rngs::StdRng, Rng, @@ -1403,7 +1404,7 @@ fn smo_instruction_works() { gas_price: Word, ) -> bool where - R: Rng, + R: Rng + CryptoRng, { let mut client = MemoryClient::default(); let fee_params = FeeParameters::default(); diff --git a/fuel-vm/src/tests/code_coverage.rs b/fuel-vm/src/tests/code_coverage.rs index 5f86e09b85..01f32d9922 100644 --- a/fuel-vm/src/tests/code_coverage.rs +++ b/fuel-vm/src/tests/code_coverage.rs @@ -46,7 +46,7 @@ fn code_coverage() { let tx_script = TransactionBuilder::script(script_code.into_iter().collect(), vec![]) .add_unsigned_coin_input( - rng.gen(), + SecretKey::random(rng), rng.gen(), 1, Default::default(), diff --git a/fuel-vm/src/tests/metadata.rs b/fuel-vm/src/tests/metadata.rs index 501e1416ca..8f13923e3c 100644 --- a/fuel-vm/src/tests/metadata.rs +++ b/fuel-vm/src/tests/metadata.rs @@ -339,7 +339,7 @@ fn get_transaction_fields() { .gas_price(gas_price) .gas_limit(gas_limit) .add_unsigned_coin_input( - rng.gen(), + SecretKey::random(rng), rng.gen(), input, AssetId::zeroed(), @@ -362,7 +362,7 @@ fn get_transaction_fields() { )) .add_witness(Witness::from(b"some-data".to_vec())) .add_unsigned_message_input( - rng.gen(), + SecretKey::random(rng), rng.gen(), rng.gen(), message_amount, @@ -370,7 +370,7 @@ fn get_transaction_fields() { ) .add_input(message_predicate) .add_unsigned_coin_input( - rng.gen(), + SecretKey::random(rng), rng.gen(), asset_amt, asset, diff --git a/fuel-vm/src/tests/predicate.rs b/fuel-vm/src/tests/predicate.rs index 12fdf6677f..8708f1188d 100644 --- a/fuel-vm/src/tests/predicate.rs +++ b/fuel-vm/src/tests/predicate.rs @@ -101,7 +101,7 @@ where (0..dummy_inputs).for_each(|_| { builder.add_unsigned_coin_input( - rng.gen(), + SecretKey::random(rng), rng.gen(), rng.gen(), rng.gen(), @@ -219,7 +219,7 @@ async fn execute_gas_metered_predicates( if predicates.is_empty() { builder.add_unsigned_coin_input( - rng.gen(), + SecretKey::random(rng), rng.gen(), coin_amount, AssetId::default(), @@ -356,7 +356,7 @@ async fn gas_used_by_predicates_is_deducted_from_script_gas() { let coin_amount = 10_000_000; builder.add_unsigned_coin_input( - rng.gen(), + SecretKey::random(rng), rng.gen(), coin_amount, AssetId::default(), @@ -406,7 +406,7 @@ async fn gas_used_by_predicates_is_deducted_from_script_gas() { // add non-predicate input before and after predicate input // to check that predicate verification only handles predicate inputs builder.add_unsigned_coin_input( - rng.gen(), + SecretKey::random(rng), rng.gen(), rng.gen(), AssetId::default(), @@ -417,7 +417,7 @@ async fn gas_used_by_predicates_is_deducted_from_script_gas() { builder.add_input(input); builder.add_unsigned_coin_input( - rng.gen(), + SecretKey::random(rng), rng.gen(), rng.gen(), AssetId::default(), @@ -508,7 +508,7 @@ async fn gas_used_by_predicates_causes_out_of_gas_during_script() { let coin_amount = 10_000_000; builder.add_unsigned_coin_input( - rng.gen(), + SecretKey::random(rng), rng.gen(), coin_amount, AssetId::default(), @@ -633,7 +633,7 @@ async fn gas_used_by_predicates_more_than_limit() { let coin_amount = 10_000_000; builder.add_unsigned_coin_input( - rng.gen(), + SecretKey::random(rng), rng.gen(), coin_amount, AssetId::default(), diff --git a/fuel-vm/src/tests/profile_gas.rs b/fuel-vm/src/tests/profile_gas.rs index 377fedf273..9ad9c4d8c7 100644 --- a/fuel-vm/src/tests/profile_gas.rs +++ b/fuel-vm/src/tests/profile_gas.rs @@ -35,7 +35,7 @@ fn profile_gas() { let tx_deploy = TransactionBuilder::script(script_code.into_iter().collect(), vec![]) .add_unsigned_coin_input( - rng.gen(), + SecretKey::random(rng), rng.gen(), 1, Default::default(), diff --git a/fuel-vm/src/tests/validation.rs b/fuel-vm/src/tests/validation.rs index 9f684533e6..ceabd81cdd 100644 --- a/fuel-vm/src/tests/validation.rs +++ b/fuel-vm/src/tests/validation.rs @@ -19,7 +19,7 @@ fn transaction_can_be_executed_after_maturity() { Default::default(), ) .add_unsigned_coin_input( - rng.gen(), + SecretKey::random(rng), rng.gen(), 1, Default::default(), diff --git a/fuel-vm/src/util.rs b/fuel-vm/src/util.rs index 5f31f0e55c..653e3b2ea0 100644 --- a/fuel-vm/src/util.rs +++ b/fuel-vm/src/util.rs @@ -259,7 +259,7 @@ pub mod test_helpers { amount: Word, ) -> &mut TestBuilder { self.builder.add_unsigned_coin_input( - self.rng.gen(), + fuel_crypto::SecretKey::random(&mut self.rng), self.rng.gen(), amount, asset_id, From 6b7f32655cddf143b671f48307e2cbed9dfd1503 Mon Sep 17 00:00:00 2001 From: Hannes Karppila Date: Tue, 5 Sep 2023 19:31:17 +0300 Subject: [PATCH 03/44] Re-add secp256k1 crate support, add backend selection --- fuel-crypto/Cargo.toml | 10 +- fuel-crypto/benches/signature.rs | 36 +-- fuel-crypto/src/ed25519.rs | 2 +- fuel-crypto/src/lib.rs | 27 +-- fuel-crypto/src/message.rs | 7 + fuel-crypto/src/secp.rs | 10 + fuel-crypto/src/secp/backend.rs | 74 ++++++ fuel-crypto/src/secp/backend/k1/k256.rs | 145 +++++++++++ fuel-crypto/src/secp/backend/k1/secp256k1.rs | 122 ++++++++++ .../{secp256r1.rs => secp/backend/r1/p256.rs} | 78 +++--- fuel-crypto/src/{secp256k1 => secp}/public.rs | 27 +-- fuel-crypto/src/{secp256k1 => secp}/secret.rs | 49 ++-- fuel-crypto/src/secp/signature.rs | 139 +++++++++++ fuel-crypto/src/secp/signature_format.rs | 101 ++++++++ fuel-crypto/src/secp256k1/signature.rs | 225 ------------------ fuel-crypto/src/tests/mnemonic.rs | 3 +- fuel-crypto/src/tests/mod.rs | 3 + fuel-crypto/src/tests/signature.rs | 90 +++---- fuel-tx/src/tests/valid_cases/input.rs | 3 +- fuel-vm/src/tests/blockchain.rs | 2 +- 20 files changed, 769 insertions(+), 384 deletions(-) create mode 100644 fuel-crypto/src/secp.rs create mode 100644 fuel-crypto/src/secp/backend.rs create mode 100644 fuel-crypto/src/secp/backend/k1/k256.rs create mode 100644 fuel-crypto/src/secp/backend/k1/secp256k1.rs rename fuel-crypto/src/{secp256r1.rs => secp/backend/r1/p256.rs} (65%) rename fuel-crypto/src/{secp256k1 => secp}/public.rs (84%) rename fuel-crypto/src/{secp256k1 => secp}/secret.rs (78%) create mode 100644 fuel-crypto/src/secp/signature.rs create mode 100644 fuel-crypto/src/secp/signature_format.rs delete mode 100644 fuel-crypto/src/secp256k1/signature.rs diff --git a/fuel-crypto/Cargo.toml b/fuel-crypto/Cargo.toml index 2214eefbcd..028614f442 100644 --- a/fuel-crypto/Cargo.toml +++ b/fuel-crypto/Cargo.toml @@ -21,6 +21,9 @@ k256 = { version = "0.13", default-features = false, features = ["digest", "ecd lazy_static = { version = "1.4", optional = true } p256 = { version = "0.13", default-features = false, features = ["digest", "ecdsa"] } rand = { version = "0.8", default-features = false, optional = true } +# `rand-std` is used to further protect the blinders from side-channel attacks and won't compromise +# the deterministic arguments of the signature (key, nonce, message), as defined in the RFC-6979 +secp256k1 = { version = "0.26", default-features = false, features = ["rand-std", "recovery"], optional = true} serde = { version = "1.0", default-features = false, features = ["derive"], optional = true } sha2 = { version = "0.10", default-features = false } zeroize = { version = "1.5", features = ["derive"] } @@ -29,17 +32,14 @@ zeroize = { version = "1.5", features = ["derive"] } bincode = { workspace = true } criterion = "0.4" fuel-crypto = { path = ".", features = ["random", "test-helpers"] } -k256 = { version = "0.11", features = [ "ecdsa" ] } sha2 = "0.10" [features] -default = ["fuel-types/default", "std"] +default = ["fuel-types/default", "std", "random"] alloc = ["rand?/alloc"] random = ["fuel-types/random", "rand"] serde = ["dep:serde", "fuel-types/serde"] -# `rand-std` is used to further protect the blinders from side-channel attacks and won't compromise -# the deterministic arguments of the signature (key, nonce, message), as defined in the RFC-6979 -std = ["alloc", "coins-bip32", "coins-bip39", "fuel-types/std", "lazy_static", "rand?/std_rng", "serde?/default"] +std = ["alloc", "coins-bip32", "secp256k1", "coins-bip39", "fuel-types/std", "lazy_static", "rand?/std_rng", "serde?/default"] wasm = [] test-helpers = [] diff --git a/fuel-crypto/benches/signature.rs b/fuel-crypto/benches/signature.rs index 5b18fd1d24..95a5e2fe14 100644 --- a/fuel-crypto/benches/signature.rs +++ b/fuel-crypto/benches/signature.rs @@ -98,14 +98,9 @@ fn signatures(c: &mut Criterion) { }; // k256 - let (k2_key, k2_verifying, k2_digest, k2_signature, k2_recoverable) = { + let (k2_key, k2_verifying, k2_digest, k2_signature, k2_recovery_id) = { use k256::ecdsa::{ - recoverable::Signature as Recoverable, - signature::{ - DigestSigner, - DigestVerifier, - }, - Signature, + signature::DigestVerifier, SigningKey, VerifyingKey, }; @@ -121,23 +116,24 @@ fn signatures(c: &mut Criterion) { 0x68, 0x2e, 0xde, 0x02, 0xc9, 0x1d, 0x61, 0xa9, 0x89, 0xd0, 0xb4, 0x39, 0x16, 0x25, 0xec, 0x80, 0x93, 0xfb, 0xa1, ]; - let key = SigningKey::from_bytes(&key).expect("failed to create key"); + let key = SigningKey::from_bytes((&key).into()).expect("failed to create key"); let verifying = VerifyingKey::from(key.clone()); - let signature: Signature = key.sign_digest(digest.clone()); - let recoverable: Recoverable = key.sign_digest(digest.clone()); + let (signature, recovery_id) = key + .sign_digest_recoverable(digest.clone()) + .expect("Failed to sign"); verifying .verify_digest(digest.clone(), &signature) .expect("failed to verify"); - let x = recoverable - .recover_verifying_key_from_digest(digest.clone()) - .expect("failed to recover"); + let recovered = + VerifyingKey::recover_from_digest(digest.clone(), &signature, recovery_id) + .expect("failed to recover"); - assert_eq!(x, verifying); + assert_eq!(recovered, verifying); - (key, verifying, digest, signature, recoverable) + (key, verifying, digest, signature, recovery_id) }; let mut group_sign = c.benchmark_group("sign"); @@ -251,10 +247,14 @@ fn signatures(c: &mut Criterion) { group_recover.bench_with_input( "k256", - &(k2_recoverable, k2_digest), - |b, (recoverable, digest)| { + &(k2_signature, k2_recovery_id, k2_digest), + |b, (signature, recovery_id, digest)| { b.iter(|| { - recoverable.recover_verifying_key_from_digest(black_box(digest.clone())) + k256::ecdsa::VerifyingKey::recover_from_digest( + digest.clone(), + signature, + *recovery_id, + ) }) }, ); diff --git a/fuel-crypto/src/ed25519.rs b/fuel-crypto/src/ed25519.rs index e4844de832..c6923ee486 100644 --- a/fuel-crypto/src/ed25519.rs +++ b/fuel-crypto/src/ed25519.rs @@ -7,8 +7,8 @@ use fuel_types::{ }; use crate::{ + message::Message, Error, - Message, }; /// Verify a signature against a message digest and a public key. diff --git a/fuel-crypto/src/lib.rs b/fuel-crypto/src/lib.rs index d108bdd8a6..9f6866f147 100644 --- a/fuel-crypto/src/lib.rs +++ b/fuel-crypto/src/lib.rs @@ -1,4 +1,7 @@ //! Fuel cryptographic primitives. +//! +//! TODO: Primitives like Signature, SecretKey and PublicKey should be valid by +//! construction #![cfg_attr(docsrs, feature(doc_auto_cfg))] #![cfg_attr(not(feature = "std"), no_std)] @@ -37,9 +40,18 @@ mod error; mod hasher; mod message; mod mnemonic; +mod secp; pub mod ed25519; -pub mod secp256r1; + +pub use secp::backend::k1 as secp256k1; +pub use secp::backend::r1 as secp256r1; + +pub use secp::{ + PublicKey, + SecretKey, + Signature, +}; #[cfg(test)] mod tests; @@ -50,16 +62,3 @@ pub use message::Message; #[cfg(all(feature = "std", feature = "random"))] pub use mnemonic::generate_mnemonic_phrase; - -mod secp256k1 { - mod public; - mod secret; - mod signature; - - pub use public::PublicKey; - pub use secret::SecretKey; - pub use signature::Signature; -} - -// The default cryptographic primitives -pub use self::secp256k1::*; diff --git a/fuel-crypto/src/message.rs b/fuel-crypto/src/message.rs index 14b348c92e..8678e3b804 100644 --- a/fuel-crypto/src/message.rs +++ b/fuel-crypto/src/message.rs @@ -114,3 +114,10 @@ impl fmt::Display for Message { self.0.fmt(f) } } + +#[cfg(feature = "std")] +impl From<&Message> for secp256k1::Message { + fn from(message: &Message) -> Self { + secp256k1::Message::from_slice(&*message.0).expect("length always matches") + } +} diff --git a/fuel-crypto/src/secp.rs b/fuel-crypto/src/secp.rs new file mode 100644 index 0000000000..3eb0b45ba6 --- /dev/null +++ b/fuel-crypto/src/secp.rs @@ -0,0 +1,10 @@ +pub mod backend; + +mod public; +mod secret; +mod signature; +mod signature_format; + +pub use public::PublicKey; +pub use secret::SecretKey; +pub use signature::Signature; diff --git a/fuel-crypto/src/secp/backend.rs b/fuel-crypto/src/secp/backend.rs new file mode 100644 index 0000000000..1bdc841877 --- /dev/null +++ b/fuel-crypto/src/secp/backend.rs @@ -0,0 +1,74 @@ +//! Backends for different secp-style elliptic curves + +/// secp256k1 implementations +pub mod k1 { + // The k256 module is always available in-crate, since it's tested against secp256k1 + #[cfg_attr(feature = "std", allow(dead_code))] + pub(crate) mod k256; + #[cfg(feature = "std")] + pub(crate) mod secp256k1; + + // Pick the default backend + #[cfg(not(feature = "std"))] + pub use self::k256::*; + #[cfg(feature = "std")] + pub use self::secp256k1::*; +} + +/// secp256r1 implementations +pub mod r1 { + pub mod p256; + pub use self::p256::*; + +} + +#[cfg(all(test, feature = "std"))] +mod tests { + use rand::{ + rngs::StdRng, + Rng, + SeedableRng, + }; + + use crate::{ + message::Message, + secp::SecretKey, + }; + + use super::k1::{ + k256, + secp256k1, + }; + + /// Make sure that the k256 and secp256k1 backends produce the same results + #[test] + fn equivalent_k256_secp256k1() { + let rng = &mut StdRng::seed_from_u64(1234); + + for case in 0..100 { + let secret = SecretKey::random(rng); + let message = Message::new(&vec![rng.gen(); case]); + + let secret_k = ::k256::SecretKey::from_bytes(&(*secret).into()).unwrap(); + let secret_s = ::secp256k1::SecretKey::from_slice(secret.as_ref()).unwrap(); + + let public_k = k256::public_key(secret_k.clone()); + let public_s = secp256k1::public_key(secret_s); + assert_eq!(public_k, public_s); + + let signed_k = k256::sign(secret_k, &message); + let signed_s = secp256k1::sign(secret_s, &message); + assert_eq!(signed_k, signed_s); + + k256::verify(signed_k, *public_k, &message).expect("Failed to verify (k256)"); + secp256k1::verify(signed_s, *public_s, &message) + .expect("Failed to verify (secp256k1)"); + + let recovered_k = + k256::recover(signed_k, &message).expect("Failed to recover (k256)"); + let recovered_s = secp256k1::recover(signed_k, &message) + .expect("Failed to recover (secp256k1)"); + assert_eq!(recovered_k, recovered_s); + } + } +} diff --git a/fuel-crypto/src/secp/backend/k1/k256.rs b/fuel-crypto/src/secp/backend/k1/k256.rs new file mode 100644 index 0000000000..dfc2620580 --- /dev/null +++ b/fuel-crypto/src/secp/backend/k1/k256.rs @@ -0,0 +1,145 @@ +use crate::{ + message::Message, + secp::{ + signature_format::{ + decode_signature, + encode_signature, + RecoveryId as SecpRecoveryId, + }, + PublicKey, + }, + Error, + SecretKey, +}; + +use k256::{ + ecdsa::{ + RecoveryId, + VerifyingKey, + }, + EncodedPoint, +}; + +#[cfg(feature = "random")] +use rand::{ + CryptoRng, + RngCore, +}; + +/// Generates a random secret key +#[cfg(feature = "random")] +pub fn random_secret(rng: &mut (impl CryptoRng + RngCore)) -> SecretKey { + k256::SecretKey::random(rng).into() +} + +/// Derives the public key from a given secret key +pub fn public_key>(secret: SK) -> PublicKey { + let sk: k256::SecretKey = secret.into(); + let sk: ecdsa::SigningKey = sk.into(); + let vk = sk.verifying_key(); + vk.into() +} + +/// Sign a given message and compress the `v` to the signature +/// +/// The compression scheme is described in +/// +pub fn sign>(secret: SK, message: &Message) -> [u8; 64] { + let sk: k256::SecretKey = secret.into(); + let sk: ecdsa::SigningKey = sk.into(); + let (signature, _recid) = sk + .sign_prehash_recoverable(&**message) + .expect("Infallible signature operation"); + + // Hack: see secp256k1 more more info + // TODO: clean up + // TODO: merge impl with secp256k1 + + let recid1 = RecoveryId::new(false, false); + let recid2 = RecoveryId::new(true, false); + + let rec1 = VerifyingKey::recover_from_prehash(&**message, &signature, recid1); + let rec2 = VerifyingKey::recover_from_prehash(&**message, &signature, recid2); + + let actual = sk.verifying_key(); + + let recovery_id = if rec1.map(|r| r == *actual).unwrap_or(false) { + recid1 + } else if rec2.map(|r| r == *actual).unwrap_or(false) { + recid2 + } else { + unreachable!("Invalid signature generated"); + }; + + let recovery_id = SecpRecoveryId::try_from(recovery_id) + .expect("reduced-x recovery ids are never generated"); + encode_signature(signature.to_bytes().into(), recovery_id) +} + +/// Recover the public key from a signature. +/// +/// It takes the signature as owned because this operation is not idempotent. The +/// taken signature will not be recoverable. Signatures are meant to be +/// single use, so this avoids unnecessary copy. +pub fn recover(signature: [u8; 64], message: &Message) -> Result { + let (sig, recid) = decode_signature(signature); + let sig = + k256::ecdsa::Signature::from_slice(&sig).map_err(|_| Error::InvalidSignature)?; + let vk = VerifyingKey::recover_from_prehash(&**message, &sig, recid.into()) + .map_err(|_| Error::InvalidSignature)?; + Ok(PublicKey::from(&vk)) +} + +/// Verify that a signature matches given public key +/// +/// It takes the signature as owned because this operation is not idempotent. The +/// taken signature will not be recoverable. Signatures are meant to be +/// single use, so this avoids unnecessary copy. +pub fn verify( + signature: [u8; 64], + public_key: [u8; 64], + message: &Message, +) -> Result<(), Error> { + // TODO: explain why hazmat is needed and why Message is prehash + use ecdsa::signature::hazmat::PrehashVerifier; + + let vk = VerifyingKey::from_encoded_point(&EncodedPoint::from_untagged_bytes( + &public_key.into(), + )) + .expect("Invalid public key"); + + let (sig, _) = decode_signature(signature); + let sig = + k256::ecdsa::Signature::from_slice(&sig).map_err(|_| Error::InvalidSignature)?; + + vk.verify_prehash(&**message, &sig) + .map_err(|_| Error::InvalidSignature)?; + Ok(()) +} + +#[cfg(all(test, feature = "std"))] +mod tests { + use rand::{ + rngs::StdRng, + Rng, + SeedableRng, + }; + + use super::*; + + #[test] + fn full() { + let rng = &mut StdRng::seed_from_u64(1234); + + let secret = random_secret(rng); + let public = public_key(&secret); + + let message = Message::new(rng.gen::<[u8; 10]>()); + + let signature = sign(&secret, &message); + verify(signature, *public, &message).expect("Verification failed"); + let recovered = recover(signature, &message).expect("Recovery failed"); + + assert_eq!(public, recovered); + } +} diff --git a/fuel-crypto/src/secp/backend/k1/secp256k1.rs b/fuel-crypto/src/secp/backend/k1/secp256k1.rs new file mode 100644 index 0000000000..a2eebd74a9 --- /dev/null +++ b/fuel-crypto/src/secp/backend/k1/secp256k1.rs @@ -0,0 +1,122 @@ +use crate::{ + message::Message, + secp::{ + signature_format::{ + decode_signature, + encode_signature, + RecoveryId as SecpRecoveryId, + }, + PublicKey, + SecretKey, + }, + Error, +}; + +use secp256k1::{ + ecdsa::{ + RecoverableSignature, + Signature, + }, + Secp256k1, +}; + +#[cfg(feature = "random")] +use rand::{ + CryptoRng, + RngCore, +}; + +lazy_static::lazy_static! { + static ref CONTEXT: Secp256k1 = Secp256k1::new(); +} + +/// Generates a random secret key +#[cfg(feature = "random")] +pub fn random_secret(rng: &mut (impl CryptoRng + RngCore)) -> SecretKey { + secp256k1::SecretKey::new(rng).into() +} + +/// Derives the public key from a given secret key +pub fn public_key>(secret: SK) -> PublicKey { + let sk: secp256k1::SecretKey = secret.into(); + let vk = secp256k1::PublicKey::from_secret_key(&CONTEXT, &sk); + vk.into() +} + +/// Sign a given message and compress the `v` to the signature +/// +/// The compression scheme is described in +/// +pub fn sign>(secret: SK, message: &Message) -> [u8; 64] { + let signature = CONTEXT.sign_ecdsa_recoverable(&message.into(), &secret.into()); + let (recovery_id, signature) = signature.serialize_compact(); + + // encode_signature cannot panic as we don't generate reduced-x recovery ids. + let recovery_id = SecpRecoveryId::try_from(recovery_id) + .expect("reduced-x recovery ids are never generated"); + encode_signature(signature, recovery_id) +} + +/// Recover the public key from a signature. +/// +/// It takes the signature as owned because this operation is not idempotent. The +/// taken signature will not be recoverable. Signatures are meant to be +/// single use, so this avoids unnecessary copy. +pub fn recover(signature: [u8; 64], message: &Message) -> Result { + let (signature, recovery_id) = decode_signature(signature); + let recoverable = RecoverableSignature::from_compact(&signature, recovery_id.into()) + .map_err(|_| Error::InvalidSignature)?; + let vk = CONTEXT + .recover_ecdsa(&message.into(), &recoverable) + .map_err(|_| Error::InvalidSignature)?; + Ok(PublicKey::from(vk)) +} + +/// Verify that a signature matches given public key +pub fn verify( + signature: [u8; 64], + public_key: [u8; 64], + message: &Message, +) -> Result<(), Error> { + let (signature, _) = decode_signature(signature); // Trunactes recovery id + let signature = + Signature::from_compact(&signature).map_err(|_| Error::InvalidSignature)?; + + let mut prefixed_public_key = [0u8; 65]; + prefixed_public_key[0] = 0x04; // Uncompressed + prefixed_public_key[1..].copy_from_slice(&public_key); + let vk = secp256k1::PublicKey::from_slice(&prefixed_public_key) + .map_err(|_| Error::InvalidPublicKey)?; + + CONTEXT + .verify_ecdsa(&message.into(), &signature, &vk) + .map_err(|_| Error::InvalidSignature)?; + Ok(()) +} + +#[cfg(all(test, feature = "std"))] +mod tests { + use rand::{ + rngs::StdRng, + Rng, + SeedableRng, + }; + + use super::*; + + #[test] + fn full() { + let rng = &mut StdRng::seed_from_u64(1234); + + let secret = random_secret(rng); + let public = public_key(&secret); + + let message = Message::new(rng.gen::<[u8; 10]>()); + + let signature = sign(&secret, &message); + verify(signature, *public, &message).expect("Verification failed"); + let recovered = recover(signature, &message).expect("Recovery failed"); + + assert_eq!(public, recovered); + } +} diff --git a/fuel-crypto/src/secp256r1.rs b/fuel-crypto/src/secp/backend/r1/p256.rs similarity index 65% rename from fuel-crypto/src/secp256r1.rs rename to fuel-crypto/src/secp/backend/r1/p256.rs index e15012d339..b589cbc832 100644 --- a/fuel-crypto/src/secp256r1.rs +++ b/fuel-crypto/src/secp/backend/r1/p256.rs @@ -1,8 +1,12 @@ //! secp256r1 (P-256) functions use crate::{ + message::Message, + secp::signature_format::{ + decode_signature, + encode_signature, + }, Error, - Message, }; use ecdsa::RecoveryId; use fuel_types::Bytes64; @@ -11,33 +15,6 @@ use p256::ecdsa::{ VerifyingKey, }; -/// Combines recovery id with the signature bytes. See the following link for explanation. -/// https://github.com/FuelLabs/fuel-specs/blob/master/src/protocol/cryptographic-primitives.md#ecdsa-public-key-cryptography -/// Panics if the highest bit of byte at index 32 is set, as this indicates non-normalized -/// signature. Panics if the recovery id is in reduced-x form. -#[cfg(feature = "test-helpers")] -fn encode_signature(signature: Signature, recovery_id: RecoveryId) -> [u8; 64] { - let mut signature: [u8; 64] = signature.to_bytes().into(); - assert!(signature[32] >> 7 == 0, "Non-normalized signature"); - assert!(!recovery_id.is_x_reduced(), "Invalid recovery id"); - - let v = recovery_id.is_y_odd() as u8; - - signature[32] = (v << 7) | (signature[32] & 0x7f); - signature -} - -/// Separates recovery id from the signature bytes. See the following link for -/// explanation. https://github.com/FuelLabs/fuel-specs/blob/master/src/protocol/cryptographic-primitives.md#ecdsa-public-key-cryptography -fn decode_signature(mut signature: [u8; 64]) -> Option<(Signature, RecoveryId)> { - let v = (signature[32] & 0x80) != 0; - signature[32] &= 0x7f; - - let signature = Signature::from_slice(&signature).ok()?; - - Some((signature, RecoveryId::new(v, false))) -} - /// Sign a prehashed message. With the given key. #[cfg(feature = "test-helpers")] pub fn sign_prehashed( @@ -72,8 +49,13 @@ pub fn sign_prehashed( unreachable!("Invalid signature generated"); }; - // encode_signature cannot panic as we don't generate reduced-x recovery ids. - Ok(Bytes64::from(encode_signature(signature, recovery_id))) + let recovery_id = recovery_id + .try_into() + .expect("reduced-x recovery ids are never generated"); + Ok(Bytes64::from(encode_signature( + signature.to_bytes().into(), + recovery_id, + ))) } /// Convert the public key point to its uncompressed non-prefixed representation, @@ -89,22 +71,25 @@ pub fn encode_pubkey(key: VerifyingKey) -> [u8; 64] { /// Recover a public key from a signature and a message digest. It assumes /// a compacted signature pub fn recover(signature: &Bytes64, message: &Message) -> Result { - let (signature, recovery_id) = - decode_signature(**signature).ok_or(Error::InvalidSignature)?; - - if let Ok(pub_key) = - VerifyingKey::recover_from_prehash(&**message, &signature, recovery_id) - { - Ok(Bytes64::from(encode_pubkey(pub_key))) - } else { - Err(Error::InvalidSignature) - } + let (sig, recid) = decode_signature(**signature); + let sig = + p256::ecdsa::Signature::from_slice(&sig).map_err(|_| Error::InvalidSignature)?; + let vk = VerifyingKey::recover_from_prehash(&**message, &sig, recid.into()) + .map_err(|_| Error::InvalidSignature)?; + let point = vk.to_encoded_point(false); + let mut raw = Bytes64::zeroed(); + raw[..32].copy_from_slice(point.x().unwrap()); + raw[32..].copy_from_slice(point.y().unwrap()); + Ok(raw) } #[cfg(test)] mod tests { + use crate::secp::signature; + use super::*; + use ecdsa::SignatureEncoding; use p256::ecdsa::SigningKey; use rand::{ rngs::StdRng, @@ -140,8 +125,8 @@ mod tests { let verifying_key = signing_key.verifying_key(); let message = Message::new([rng.gen(); 100]); - let signature = crate::secp256r1::sign_prehashed(&signing_key, &message) - .expect("Couldn't sign"); + let signature = + sign_prehashed(&signing_key, &message).expect("Couldn't sign"); let Ok(recovered) = recover(&signature, &message) else { panic!("Failed to recover public key from the message"); @@ -160,18 +145,19 @@ mod tests { let signing_key = SigningKey::random(&mut rng); let (signature, _) = signing_key.sign_prehash_recoverable(&*message).unwrap(); let signature = signature.normalize_s().unwrap_or(signature); + let signature: [u8; 64] = signature.to_bytes().into(); - let recovery_id = RecoveryId::from_byte(0).unwrap(); + let recovery_id = RecoveryId::from_byte(0).unwrap().try_into().unwrap(); let encoded = encode_signature(signature, recovery_id); - let (de_sig, de_recid) = decode_signature(encoded).unwrap(); + let (de_sig, de_recid) = decode_signature(encoded); assert_eq!(signature, de_sig); assert_eq!(recovery_id, de_recid); - let recovery_id = RecoveryId::from_byte(1).unwrap(); + let recovery_id = RecoveryId::from_byte(1).unwrap().try_into().unwrap(); let encoded = encode_signature(signature, recovery_id); - let (de_sig, de_recid) = decode_signature(encoded).unwrap(); + let (de_sig, de_recid) = decode_signature(encoded); assert_eq!(signature, de_sig); assert_eq!(recovery_id, de_recid); } diff --git a/fuel-crypto/src/secp256k1/public.rs b/fuel-crypto/src/secp/public.rs similarity index 84% rename from fuel-crypto/src/secp256k1/public.rs rename to fuel-crypto/src/secp/public.rs index 8bbf3b0657..57ffbf14e7 100644 --- a/fuel-crypto/src/secp256k1/public.rs +++ b/fuel-crypto/src/secp/public.rs @@ -1,7 +1,7 @@ use crate::{ hasher::Hasher, + secp::SecretKey, Error, - SecretKey, }; use core::{ fmt, @@ -96,26 +96,21 @@ impl From for PublicKey { } } -impl From> for PublicKey { - fn from(vk: ecdsa::VerifyingKey) -> Self { +impl From<&ecdsa::VerifyingKey> for PublicKey { + fn from(vk: &ecdsa::VerifyingKey) -> Self { let vk: k256::PublicKey = vk.into(); vk.into() } } -impl Into for PublicKey { - fn into(self) -> k256::ecdsa::VerifyingKey { - k256::ecdsa::VerifyingKey::from_encoded_point( - &k256::EncodedPoint::from_untagged_bytes((&*self).into()), - ) - .expect("Invalid public key") - } -} - -impl Into for PublicKey { - fn into(self) -> k256::PublicKey { - let k: k256::ecdsa::VerifyingKey = self.into(); - k.into() +#[cfg(feature = "std")] +impl From for PublicKey { + fn from(key: secp256k1::PublicKey) -> Self { + let key_bytes = key.serialize_uncompressed(); + let mut raw = Bytes64::zeroed(); + // Remove leading identifier byte + raw.copy_from_slice(&key_bytes[1..]); + Self(raw) } } diff --git a/fuel-crypto/src/secp256k1/secret.rs b/fuel-crypto/src/secp/secret.rs similarity index 78% rename from fuel-crypto/src/secp256k1/secret.rs rename to fuel-crypto/src/secp/secret.rs index 3466d1f6fe..97a37f1493 100644 --- a/fuel-crypto/src/secp256k1/secret.rs +++ b/fuel-crypto/src/secp/secret.rs @@ -4,20 +4,24 @@ use core::{ fmt, ops::Deref, str, + str::FromStr, }; use zeroize::Zeroize; use crate::{ + secp::PublicKey, Error, - PublicKey, }; + +#[cfg(feature = "std")] use coins_bip32::path::DerivationPath; + +#[cfg(feature = "std")] use coins_bip39::{ English, Mnemonic, }; -use std::str::FromStr; #[cfg(feature = "random")] use rand::{ @@ -25,7 +29,7 @@ use rand::{ RngCore, }; -/// Asymmetric secret key +/// Asymmetric secret key, guaranteed to be valid by construction #[derive(Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Zeroize)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[repr(transparent)] @@ -88,33 +92,53 @@ impl fmt::Display for SecretKey { } } -impl From for SecretKey { - fn from(s: k256::SecretKey) -> Self { +impl From<::k256::SecretKey> for SecretKey { + fn from(s: ::k256::SecretKey) -> Self { let mut raw_bytes = [0u8; Self::LEN]; raw_bytes.copy_from_slice(&s.to_bytes()); Self(Bytes32::from(raw_bytes)) } } -impl Into for SecretKey { - fn into(self) -> k256::SecretKey { - k256::SecretKey::from_bytes((&*self.0).into()).expect("Invalid secret key") +#[cfg(feature = "std")] +impl From<::secp256k1::SecretKey> for SecretKey { + fn from(s: ::secp256k1::SecretKey) -> Self { + let mut raw_bytes = [0u8; Self::LEN]; + raw_bytes.copy_from_slice(s.as_ref()); + Self(Bytes32::from(raw_bytes)) } } +impl From<&SecretKey> for ::k256::SecretKey { + fn from(sk: &SecretKey) -> Self { + ::k256::SecretKey::from_bytes(&(*sk.0).into()) + .expect("SecretKey is guaranteed to be valid") + } +} + +#[cfg(feature = "std")] +impl From<&SecretKey> for ::secp256k1::SecretKey { + fn from(sk: &SecretKey) -> Self { + ::secp256k1::SecretKey::from_slice(sk.as_ref()) + .expect("SecretKey is guaranteed to be valid") + } +} + +#[cfg(feature = "std")] pub type W = English; impl SecretKey { /// Create a new random secret #[cfg(feature = "random")] pub fn random(rng: &mut (impl CryptoRng + RngCore)) -> Self { - k256::SecretKey::random(rng).into() + super::backend::k1::random_secret(rng) } /// Generate a new secret key from a mnemonic phrase and its derivation path. /// Both are passed as `&str`. If you want to manually create a `DerivationPath` /// and `Mnemonic`, use [`SecretKey::new_from_mnemonic`]. /// The derivation path is a list of integers, each representing a child index. + #[cfg(feature = "std")] pub fn new_from_mnemonic_phrase_with_path( phrase: &str, path: &str, @@ -127,6 +151,7 @@ impl SecretKey { /// Generate a new secret key from a `DerivationPath` and `Mnemonic`. /// If you want to pass strings instead, use /// [`SecretKey::new_from_mnemonic_phrase_with_path`]. + #[cfg(feature = "std")] pub fn new_from_mnemonic(d: DerivationPath, m: Mnemonic) -> Result { let derived_priv_key = m.derive_key(d, None)?; let key: &coins_bip32::prelude::SigningKey = derived_priv_key.as_ref(); @@ -135,12 +160,8 @@ impl SecretKey { } /// Return the curve representation of this secret. - /// - /// The discrete logarithm property guarantees this is a one-way - /// function. pub fn public_key(&self) -> PublicKey { - let vk: k256::SecretKey = (*self).into(); - vk.public_key().into() + crate::secp::backend::k1::public_key(self) } } diff --git a/fuel-crypto/src/secp/signature.rs b/fuel-crypto/src/secp/signature.rs new file mode 100644 index 0000000000..132ad55a12 --- /dev/null +++ b/fuel-crypto/src/secp/signature.rs @@ -0,0 +1,139 @@ +use super::backend::k1; +use crate::{ + Error, + Message, + PublicKey, + SecretKey, +}; + +use fuel_types::Bytes64; + +use core::{ + fmt, + ops::Deref, + str, +}; + +/// Compact-form Secp256k1 signature. +#[derive(Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[repr(transparent)] +pub struct Signature(Bytes64); + +impl Signature { + /// Memory length of the type in bytes. + pub const LEN: usize = Bytes64::LEN; + + /// Construct a `Signature` directly from its bytes. + /// + /// This constructor expects the given bytes to be a valid signature. No signing is + /// performed. + pub fn from_bytes(bytes: [u8; Self::LEN]) -> Self { + Self(bytes.into()) + } + + /// Construct a `Signature` reference directly from a reference to its bytes. + /// + /// This constructor expects the given bytes to be a valid signature. No signing is + /// performed. + pub fn from_bytes_ref(bytes: &[u8; Self::LEN]) -> &Self { + // TODO: Wrap this unsafe conversion safely in `fuel_types::Bytes64`. + #[allow(unsafe_code)] + unsafe { + &*(bytes.as_ptr() as *const Self) + } + } + + /// Kept temporarily for backwards compatibility. + #[deprecated = "Use `Signature::from_bytes` instead"] + pub fn from_bytes_unchecked(bytes: [u8; Self::LEN]) -> Self { + Self::from_bytes(bytes) + } +} + +impl Deref for Signature { + type Target = [u8; Signature::LEN]; + + fn deref(&self) -> &[u8; Signature::LEN] { + self.0.deref() + } +} + +impl AsRef<[u8]> for Signature { + fn as_ref(&self) -> &[u8] { + self.0.as_ref() + } +} + +impl AsMut<[u8]> for Signature { + fn as_mut(&mut self) -> &mut [u8] { + self.0.as_mut() + } +} + +impl fmt::LowerHex for Signature { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +impl fmt::UpperHex for Signature { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +impl fmt::Debug for Signature { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.0.fmt(f) + } +} + +impl fmt::Display for Signature { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.0.fmt(f) + } +} + +impl From for [u8; Signature::LEN] { + fn from(salt: Signature) -> [u8; Signature::LEN] { + salt.0.into() + } +} + +impl From for Bytes64 { + fn from(s: Signature) -> Self { + s.0 + } +} + +impl str::FromStr for Signature { + type Err = Error; + + /// Parse a `Signature` directly from its bytes encoded as hex in a string. + /// + /// This constructor does not perform any signing. + fn from_str(s: &str) -> Result { + Bytes64::from_str(s) + .map_err(|_| Error::InvalidSignature) + .map(|s| Self::from_bytes(s.into())) + } +} + +/// Secp256k1 methods +impl Signature { + /// Produce secp256k1 signature + pub fn sign(secret: &SecretKey, message: &Message) -> Self { + Self(Bytes64::from(k1::sign(secret, message))) + } + + /// Recover secp256k1 public key from a signature performed with + pub fn recover(&self, message: &Message) -> Result { + k1::recover(*self.0, message) + } + + /// Verify that a signature matches given public key + pub fn verify(&self, public_key: &PublicKey, message: &Message) -> Result<(), Error> { + k1::verify(*self.0, **public_key, message) + } +} diff --git a/fuel-crypto/src/secp/signature_format.rs b/fuel-crypto/src/secp/signature_format.rs new file mode 100644 index 0000000000..090a9caa85 --- /dev/null +++ b/fuel-crypto/src/secp/signature_format.rs @@ -0,0 +1,101 @@ +//! Utility functions common for secp256k1 and secp256r1 + +/// Recovery id, used to encode the y parity of the public key +/// in the signature. +/// +/// Guaranteed to be valid by construction. Only encodes the y parity, +/// and rejects reduced-x recovery ids. +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct RecoveryId { + is_y_odd: bool, +} + +impl From for k256::ecdsa::RecoveryId { + fn from(recid: RecoveryId) -> Self { + k256::ecdsa::RecoveryId::new(recid.is_y_odd, false) + } +} +impl TryFrom for RecoveryId { + type Error = (); + + fn try_from(recid: ecdsa::RecoveryId) -> Result { + if recid.is_x_reduced() { + return Err(()) + } + + Ok(Self { + is_y_odd: recid.is_y_odd(), + }) + } +} + +#[cfg(feature = "std")] +impl From for secp256k1::ecdsa::RecoveryId { + fn from(recid: RecoveryId) -> Self { + secp256k1::ecdsa::RecoveryId::from_i32(recid.is_y_odd as i32) + .expect("0 and 1 are always valid recovery ids") + } +} + +#[cfg(feature = "std")] +impl TryFrom for RecoveryId { + type Error = (); + + fn try_from(recid: secp256k1::ecdsa::RecoveryId) -> Result { + match recid.to_i32() { + 0 => Ok(Self { is_y_odd: false }), + 1 => Ok(Self { is_y_odd: true }), + _ => Err(()), + } + } +} + +/// Combines recovery id with the signature bytes. See the following link for explanation. +/// https://github.com/FuelLabs/fuel-specs/blob/master/src/protocol/cryptographic-primitives.md#ecdsa-public-key-cryptography +/// Panics if the highest bit of byte at index 32 is set, as this indicates non-normalized +/// signature. Panics if the recovery id is in reduced-x form. +pub fn encode_signature(mut signature: [u8; 64], recovery_id: RecoveryId) -> [u8; 64] { + assert!(signature[32] >> 7 == 0, "Non-normalized signature"); + let v = recovery_id.is_y_odd as u8; + + signature[32] = (v << 7) | (signature[32] & 0x7f); + signature +} + +/// Separates recovery id from the signature bytes. See the following link for +/// explanation. https://github.com/FuelLabs/fuel-specs/blob/master/src/protocol/cryptographic-primitives.md#ecdsa-public-key-cryptography +pub fn decode_signature(mut signature: [u8; 64]) -> ([u8; 64], RecoveryId) { + let is_y_odd = (signature[32] & 0x80) != 0; + signature[32] &= 0x7f; + (signature, RecoveryId { is_y_odd }) +} + +#[cfg(all(test, feature = "std"))] +mod tests { + use rand::{ + rngs::StdRng, + SeedableRng, + }; + + use crate::{ + Message, + SecretKey, + Signature, + }; + + use super::*; + + #[test] + fn signature_roundtrip() { + let rng = &mut StdRng::seed_from_u64(1234); + + let message = Message::new("Hello, world!"); + let secret = SecretKey::random(rng); + let signature = Signature::sign(&secret, &message); + + let (decoded, recovery_id) = decode_signature(*signature); + let encoded = encode_signature(decoded, recovery_id); + + assert_eq!(*signature, encoded); + } +} diff --git a/fuel-crypto/src/secp256k1/signature.rs b/fuel-crypto/src/secp256k1/signature.rs deleted file mode 100644 index f17335a678..0000000000 --- a/fuel-crypto/src/secp256k1/signature.rs +++ /dev/null @@ -1,225 +0,0 @@ -use crate::Error; - -use fuel_types::Bytes64; - -use core::{ - fmt, - ops::Deref, - str, -}; - -use crate::{ - Message, - PublicKey, - SecretKey, -}; - -use k256::ecdsa::{ - RecoveryId, - VerifyingKey, -}; - -/// Compact-form Secp256k1 signature. -#[derive(Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[repr(transparent)] -pub struct Signature(Bytes64); - -impl Signature { - /// Memory length of the type in bytes. - pub const LEN: usize = Bytes64::LEN; - - /// Construct a `Signature` directly from its bytes. - /// - /// This constructor expects the given bytes to be a valid signature. No signing is - /// performed. - pub fn from_bytes(bytes: [u8; Self::LEN]) -> Self { - Self(bytes.into()) - } - - /// Construct a `Signature` reference directly from a reference to its bytes. - /// - /// This constructor expects the given bytes to be a valid signature. No signing is - /// performed. - pub fn from_bytes_ref(bytes: &[u8; Self::LEN]) -> &Self { - // TODO: Wrap this unsafe conversion safely in `fuel_types::Bytes64`. - #[allow(unsafe_code)] - unsafe { - &*(bytes.as_ptr() as *const Self) - } - } - - /// Kept temporarily for backwards compatibility. - #[deprecated = "Use `Signature::from_bytes` instead"] - pub fn from_bytes_unchecked(bytes: [u8; Self::LEN]) -> Self { - Self::from_bytes(bytes) - } -} - -impl Deref for Signature { - type Target = [u8; Signature::LEN]; - - fn deref(&self) -> &[u8; Signature::LEN] { - self.0.deref() - } -} - -impl AsRef<[u8]> for Signature { - fn as_ref(&self) -> &[u8] { - self.0.as_ref() - } -} - -impl AsMut<[u8]> for Signature { - fn as_mut(&mut self) -> &mut [u8] { - self.0.as_mut() - } -} - -impl fmt::LowerHex for Signature { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.0.fmt(f) - } -} - -impl fmt::UpperHex for Signature { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.0.fmt(f) - } -} - -impl fmt::Debug for Signature { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.0.fmt(f) - } -} - -impl fmt::Display for Signature { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.0.fmt(f) - } -} - -impl From for [u8; Signature::LEN] { - fn from(salt: Signature) -> [u8; Signature::LEN] { - salt.0.into() - } -} - -impl From for Bytes64 { - fn from(s: Signature) -> Self { - s.0 - } -} - -impl str::FromStr for Signature { - type Err = Error; - - /// Parse a `Signature` directly from its bytes encoded as hex in a string. - /// - /// This constructor does not perform any signing. - fn from_str(s: &str) -> Result { - Bytes64::from_str(s) - .map_err(|_| Error::InvalidSignature) - .map(|s| Self::from_bytes(s.into())) - } -} - -impl Signature { - /// Separates recovery id from the signature bytes. See the following link for - /// explanation. https://github.com/FuelLabs/fuel-specs/blob/master/src/protocol/cryptographic-primitives.md#ecdsa-public-key-cryptography - fn decode(&self) -> (k256::ecdsa::Signature, RecoveryId) { - let mut sig = *self.0; - let v = (sig[32] & 0x80) != 0; - sig[32] &= 0x7f; - - let sig = - k256::ecdsa::Signature::from_slice(&sig).expect("Signature must be valid"); - (sig, RecoveryId::new(v, false)) - } -} - -impl Signature { - /// Sign a given message and compress the `v` to the signature - /// - /// The compression scheme is described in - /// - pub fn sign(secret: &SecretKey, message: &Message) -> Self { - let sk: k256::SecretKey = (*secret).into(); - let sk: ecdsa::SigningKey = sk.into(); - let (signature, _recid) = sk - .sign_prehash_recoverable(&**message) - .expect("Infallible signature operation"); - - // Hack: see secp256k1 more more info - // TODO: clean up - // TODO: merge impl with secp256k1 - - let recid1 = RecoveryId::new(false, false); - let recid2 = RecoveryId::new(true, false); - - let rec1 = VerifyingKey::recover_from_prehash(&**message, &signature, recid1); - let rec2 = VerifyingKey::recover_from_prehash(&**message, &signature, recid2); - - let actual = sk.verifying_key(); - - let recovery_id = if rec1.map(|r| r == *actual).unwrap_or(false) { - recid1 - } else if rec2.map(|r| r == *actual).unwrap_or(false) { - recid2 - } else { - unreachable!("Invalid signature generated"); - }; - - // encode_signature cannot panic as we don't generate reduced-x recovery ids. - - // Combine recovery id with the signature bytes. See the following link for - // explanation. https://github.com/FuelLabs/fuel-specs/blob/master/src/protocol/cryptographic-primitives.md#ecdsa-public-key-cryptography - // Panics if the highest bit of byte at index 32 is set, as this indicates - // non-normalized signature. Panics if the recovery id is in reduced-x - // form. - let mut signature: [u8; 64] = signature.to_bytes().into(); - assert!(signature[32] >> 7 == 0, "Non-normalized signature"); - assert!(!recovery_id.is_x_reduced(), "Invalid recovery id"); - - let v = recovery_id.is_y_odd() as u8; - - signature[32] = (v << 7) | (signature[32] & 0x7f); - - Self(Bytes64::from(signature)) - } - - /// Recover the public key from a signature performed with - /// [`Signature::sign`] - /// - /// It takes the signature as owned because this operation is not idempotent. The - /// taken signature will not be recoverable. Signatures are meant to be - /// single use, so this avoids unnecessary copy. - pub fn recover(self, message: &Message) -> Result { - let (sig, recid) = self.decode(); - - match VerifyingKey::recover_from_prehash(&**message, &sig, recid) { - Ok(vk) => Ok(PublicKey::from(vk)), - Err(_) => Err(Error::InvalidSignature), - } - } - - /// Verify a signature produced by [`Signature::sign`] - /// - /// It takes the signature as owned because this operation is not idempotent. The - /// taken signature will not be recoverable. Signatures are meant to be - /// single use, so this avoids unnecessary copy. - pub fn verify(self, vk: &PublicKey, message: &Message) -> Result<(), Error> { - // TODO: explain why hazmat is needed and why Message is prehash - use ecdsa::signature::hazmat::PrehashVerifier; - - let vk: k256::PublicKey = (*vk).into(); // TODO: remove clone - let vk: ecdsa::VerifyingKey = vk.into(); - - let (sig, _) = self.decode(); - - vk.verify_prehash(&**message, &sig) - .map_err(|_| Error::InvalidSignature)?; - Ok(()) - } -} diff --git a/fuel-crypto/src/tests/mnemonic.rs b/fuel-crypto/src/tests/mnemonic.rs index a7c2a48ae9..fe4da536ec 100644 --- a/fuel-crypto/src/tests/mnemonic.rs +++ b/fuel-crypto/src/tests/mnemonic.rs @@ -1,6 +1,7 @@ -use std::str::FromStr; +use core::str::FromStr; use crate::SecretKey; + use coins_bip32::path::DerivationPath; use coins_bip39::{ English, diff --git a/fuel-crypto/src/tests/mod.rs b/fuel-crypto/src/tests/mod.rs index d09369e20b..8f9df998c4 100644 --- a/fuel-crypto/src/tests/mod.rs +++ b/fuel-crypto/src/tests/mod.rs @@ -3,7 +3,10 @@ use criterion as _; use k256 as _; mod hasher; + +#[cfg(feature = "std")] mod mnemonic; + mod signature; #[cfg(feature = "serde")] diff --git a/fuel-crypto/src/tests/signature.rs b/fuel-crypto/src/tests/signature.rs index 2c9396faf2..623322751f 100644 --- a/fuel-crypto/src/tests/signature.rs +++ b/fuel-crypto/src/tests/signature.rs @@ -5,11 +5,14 @@ use crate::{ SecretKey, Signature, }; + +#[cfg(feature = "std")] use rand::{ rngs::StdRng, SeedableRng, }; +#[cfg(feature = "std")] #[test] fn recover() { let rng = &mut StdRng::seed_from_u64(8586); @@ -29,6 +32,7 @@ fn recover() { } } +#[cfg(feature = "std")] #[test] fn verify() { let rng = &mut StdRng::seed_from_u64(8586); @@ -41,6 +45,10 @@ fn verify() { let secret = SecretKey::random(rng); let public = secret.public_key(); + let pub1 = crate::secp::backend::k1::public_key(&secret); + let pub2 = crate::secp::backend::k1::public_key(&secret); + assert_eq!(pub1, pub2); + let signature = Signature::sign(&secret, &message); signature @@ -49,60 +57,58 @@ fn verify() { } } -#[test] -fn corrupted_signature() { - let rng = &mut StdRng::seed_from_u64(8586); - - let message = b"When life itself seems lunatic, who knows where madness lies?"; - let message = Message::new(message); +// #[test] +// fn corrupted_signature() { +// let rng = &mut StdRng::seed_from_u64(8586); - let secret = SecretKey::random(rng); - let public = secret.public_key(); +// let message = b"When life itself seems lunatic, who knows where madness lies?"; +// let message = Message::new(message); - let signature = Signature::sign(&secret, &message); +// let secret = SecretKey::random(rng); +// let public = secret.public_key(); - // Tamper, bit by bit, the signature and public key. - // - // The recover and verify operations should fail in all cases. - (0..Signature::LEN).for_each(|i| { - (0..7).fold(1u8, |m, _| { - let mut s = signature; +// let signature = Signature::sign(&secret, &message); - s.as_mut()[i] ^= m; +// // Tamper, bit by bit, the signature and public key. +// // +// // The recover and verify operations should fail in all cases. +// (0..Signature::LEN).for_each(|i| { +// (0..7).fold(1u8, |m, _| { +// let mut s = signature; - match s.recover(&message) { - Ok(pk) => assert_ne!(public, pk), - Err(Error::InvalidSignature) => (), - Err(e) => panic!("Unexpected error: {e}"), - } +// s.as_mut()[i] ^= m; - m << 1 - }); - }); +// match s.recover(&message) { +// Ok(pk) => assert_ne!(public, pk), +// Err(Error::InvalidSignature) => (), +// Err(e) => panic!("Unexpected error: {e}"), +// } - (0..Signature::LEN).for_each(|i| { - (0..7).fold(1u8, |m, _| { - let mut s = signature; +// m << 1 +// }); +// }); - s.as_mut()[i] ^= m; +// (0..Signature::LEN).for_each(|i| { +// (0..7).fold(1u8, |m, _| { +// let mut s = signature; - assert!(s.verify(&public, &message).is_err()); +// s.as_mut()[i] ^= m; - m << 1 - }); - }); +// assert!(s.verify(&public, &message).is_err()); - // This breaks because it bypasses the public key parsing validation completely. +// m << 1 +// }); +// }); - // (0..PublicKey::LEN).for_each(|i| { - // (0..7).fold(1u8, |m, _| { - // let mut p = public; +// (0..PublicKey::LEN).for_each(|i| { +// (0..7).fold(1u8, |m, _| { +// let mut p = public; - // p.as_mut()[i] ^= m; +// p.as_mut()[i] ^= m; - // assert!(signature.verify(&p, &message).is_err()); +// assert!(signature.verify(&p, &message).is_err()); - // m << 1 - // }); - // }); -} +// m << 1 +// }); +// }); +// } diff --git a/fuel-tx/src/tests/valid_cases/input.rs b/fuel-tx/src/tests/valid_cases/input.rs index 89da5721d7..807d5a7bc5 100644 --- a/fuel-tx/src/tests/valid_cases/input.rs +++ b/fuel-tx/src/tests/valid_cases/input.rs @@ -17,8 +17,9 @@ use fuel_tx_test_helpers::{ use fuel_types::ChainId; use rand::{ rngs::StdRng, + CryptoRng, Rng, - SeedableRng, CryptoRng, + SeedableRng, }; #[test] diff --git a/fuel-vm/src/tests/blockchain.rs b/fuel-vm/src/tests/blockchain.rs index fae0363560..8f5eab4afb 100644 --- a/fuel-vm/src/tests/blockchain.rs +++ b/fuel-vm/src/tests/blockchain.rs @@ -17,9 +17,9 @@ use fuel_types::{ ChainId, }; use itertools::Itertools; -use rand::CryptoRng; use rand::{ rngs::StdRng, + CryptoRng, Rng, SeedableRng, }; From 4eda0eed32cef901af015a7952246986159c7074 Mon Sep 17 00:00:00 2001 From: Hannes Karppila Date: Thu, 7 Sep 2023 06:36:35 +0300 Subject: [PATCH 04/44] Fix clippy issues with features on/off --- fuel-crypto/src/lib.rs | 6 +- fuel-crypto/src/secp/backend.rs | 1 - fuel-crypto/src/secp/backend/r1/p256.rs | 16 ++--- fuel-crypto/src/tests/signature.rs | 80 ++++++++++++------------- 4 files changed, 49 insertions(+), 54 deletions(-) diff --git a/fuel-crypto/src/lib.rs b/fuel-crypto/src/lib.rs index 9f6866f147..cdd92f98f9 100644 --- a/fuel-crypto/src/lib.rs +++ b/fuel-crypto/src/lib.rs @@ -44,8 +44,10 @@ mod secp; pub mod ed25519; -pub use secp::backend::k1 as secp256k1; -pub use secp::backend::r1 as secp256r1; +pub use secp::backend::{ + k1 as secp256k1, + r1 as secp256r1, +}; pub use secp::{ PublicKey, diff --git a/fuel-crypto/src/secp/backend.rs b/fuel-crypto/src/secp/backend.rs index 1bdc841877..16bd818739 100644 --- a/fuel-crypto/src/secp/backend.rs +++ b/fuel-crypto/src/secp/backend.rs @@ -19,7 +19,6 @@ pub mod k1 { pub mod r1 { pub mod p256; pub use self::p256::*; - } #[cfg(all(test, feature = "std"))] diff --git a/fuel-crypto/src/secp/backend/r1/p256.rs b/fuel-crypto/src/secp/backend/r1/p256.rs index b589cbc832..b81ddf265b 100644 --- a/fuel-crypto/src/secp/backend/r1/p256.rs +++ b/fuel-crypto/src/secp/backend/r1/p256.rs @@ -1,19 +1,16 @@ //! secp256r1 (P-256) functions +#[cfg(feature = "test-helpers")] +use crate::secp::signature_format::encode_signature; use crate::{ message::Message, - secp::signature_format::{ - decode_signature, - encode_signature, - }, + secp::signature_format::decode_signature, Error, }; +#[cfg(feature = "test-helpers")] use ecdsa::RecoveryId; use fuel_types::Bytes64; -use p256::ecdsa::{ - Signature, - VerifyingKey, -}; +use p256::ecdsa::VerifyingKey; /// Sign a prehashed message. With the given key. #[cfg(feature = "test-helpers")] @@ -85,11 +82,8 @@ pub fn recover(signature: &Bytes64, message: &Message) -> Result #[cfg(test)] mod tests { - use crate::secp::signature; - use super::*; - use ecdsa::SignatureEncoding; use p256::ecdsa::SigningKey; use rand::{ rngs::StdRng, diff --git a/fuel-crypto/src/tests/signature.rs b/fuel-crypto/src/tests/signature.rs index 623322751f..0bc3107184 100644 --- a/fuel-crypto/src/tests/signature.rs +++ b/fuel-crypto/src/tests/signature.rs @@ -57,58 +57,58 @@ fn verify() { } } -// #[test] -// fn corrupted_signature() { -// let rng = &mut StdRng::seed_from_u64(8586); +#[test] +fn corrupted_signature() { + let rng = &mut StdRng::seed_from_u64(8586); -// let message = b"When life itself seems lunatic, who knows where madness lies?"; -// let message = Message::new(message); + let message = b"When life itself seems lunatic, who knows where madness lies?"; + let message = Message::new(message); -// let secret = SecretKey::random(rng); -// let public = secret.public_key(); + let secret = SecretKey::random(rng); + let public = secret.public_key(); -// let signature = Signature::sign(&secret, &message); + let signature = Signature::sign(&secret, &message); -// // Tamper, bit by bit, the signature and public key. -// // -// // The recover and verify operations should fail in all cases. -// (0..Signature::LEN).for_each(|i| { -// (0..7).fold(1u8, |m, _| { -// let mut s = signature; + // Tamper, bit by bit, the signature and public key. + // + // The recover and verify operations should fail in all cases. + (0..Signature::LEN).for_each(|i| { + (0..7).fold(1u8, |m, _| { + let mut s = signature; -// s.as_mut()[i] ^= m; + s.as_mut()[i] ^= m; -// match s.recover(&message) { -// Ok(pk) => assert_ne!(public, pk), -// Err(Error::InvalidSignature) => (), -// Err(e) => panic!("Unexpected error: {e}"), -// } + match s.recover(&message) { + Ok(pk) => assert_ne!(public, pk), + Err(Error::InvalidSignature) => (), + Err(e) => panic!("Unexpected error: {e}"), + } -// m << 1 -// }); -// }); + m << 1 + }); + }); -// (0..Signature::LEN).for_each(|i| { -// (0..7).fold(1u8, |m, _| { -// let mut s = signature; + (0..Signature::LEN).for_each(|i| { + (0..7).fold(1u8, |m, _| { + let mut s = signature; -// s.as_mut()[i] ^= m; + s.as_mut()[i] ^= m; -// assert!(s.verify(&public, &message).is_err()); + assert!(s.verify(&public, &message).is_err()); -// m << 1 -// }); -// }); + m << 1 + }); + }); -// (0..PublicKey::LEN).for_each(|i| { -// (0..7).fold(1u8, |m, _| { -// let mut p = public; + (0..PublicKey::LEN).for_each(|i| { + (0..7).fold(1u8, |m, _| { + let mut p = public; -// p.as_mut()[i] ^= m; + p.as_mut()[i] ^= m; -// assert!(signature.verify(&p, &message).is_err()); + assert!(signature.verify(&p, &message).is_err()); -// m << 1 -// }); -// }); -// } + m << 1 + }); + }); +} From dcd67d9383e927f4a4040680c9dc31cc74b3fa81 Mon Sep 17 00:00:00 2001 From: Hannes Karppila Date: Thu, 7 Sep 2023 06:53:02 +0300 Subject: [PATCH 05/44] Fmt toml files --- fuel-crypto/Cargo.toml | 2 +- fuel-vm/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fuel-crypto/Cargo.toml b/fuel-crypto/Cargo.toml index a184138cff..4f84326621 100644 --- a/fuel-crypto/Cargo.toml +++ b/fuel-crypto/Cargo.toml @@ -23,7 +23,7 @@ p256 = { version = "0.13", default-features = false, features = ["digest", "ecd rand = { version = "0.8", default-features = false, optional = true } # `rand-std` is used to further protect the blinders from side-channel attacks and won't compromise # the deterministic arguments of the signature (key, nonce, message), as defined in the RFC-6979 -secp256k1 = { version = "0.26", default-features = false, features = ["rand-std", "recovery"], optional = true} +secp256k1 = { version = "0.26", default-features = false, features = ["rand-std", "recovery"], optional = true } serde = { version = "1.0", default-features = false, features = ["derive"], optional = true } sha2 = { version = "0.10", default-features = false } zeroize = { version = "1.5", features = ["derive"] } diff --git a/fuel-vm/Cargo.toml b/fuel-vm/Cargo.toml index 47d14c0c2f..90edd75b5b 100644 --- a/fuel-vm/Cargo.toml +++ b/fuel-vm/Cargo.toml @@ -19,7 +19,7 @@ derivative = "2.2" dyn-clone = { version = "1.0", optional = true } ethnum = "1.3" fuel-asm = { workspace = true } -fuel-crypto = { workspace = true, default-features = false} +fuel-crypto = { workspace = true, default-features = false } fuel-merkle = { workspace = true } fuel-storage = { workspace = true } fuel-tx = { workspace = true, features = ["builder", "std"] } From f9465cbdb07e806eff7193fffbbcc624a6c6425f Mon Sep 17 00:00:00 2001 From: Hannes Karppila Date: Thu, 7 Sep 2023 07:16:23 +0300 Subject: [PATCH 06/44] Cleanup for CI --- .github/workflows/ci.yml | 2 ++ fuel-crypto/Cargo.toml | 1 - fuel-crypto/src/lib.rs | 6 +----- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ec57781595..64be4341ab 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -70,6 +70,8 @@ jobs: args: --target wasm32-unknown-unknown -p fuel-types --features typescript --crate-type=cdylib - command: rustc args: --target wasm32-unknown-unknown -p fuel-asm --features typescript --crate-type=cdylib + - command: bench + args: --workspace --no-run - command: make args: check - command: test diff --git a/fuel-crypto/Cargo.toml b/fuel-crypto/Cargo.toml index 4f84326621..f5047a8be7 100644 --- a/fuel-crypto/Cargo.toml +++ b/fuel-crypto/Cargo.toml @@ -40,7 +40,6 @@ alloc = ["rand?/alloc", "secp256k1/alloc", "fuel-types/alloc"] random = ["fuel-types/random", "rand"] serde = ["dep:serde", "fuel-types/serde"] std = ["alloc", "coins-bip32", "secp256k1", "coins-bip39", "fuel-types/std", "lazy_static", "rand?/std_rng", "serde?/default"] -wasm = [] test-helpers = [] [[bench]] diff --git a/fuel-crypto/src/lib.rs b/fuel-crypto/src/lib.rs index cdd92f98f9..88c4014d87 100644 --- a/fuel-crypto/src/lib.rs +++ b/fuel-crypto/src/lib.rs @@ -1,17 +1,13 @@ //! Fuel cryptographic primitives. -//! -//! TODO: Primitives like Signature, SecretKey and PublicKey should be valid by -//! construction #![cfg_attr(docsrs, feature(doc_auto_cfg))] #![cfg_attr(not(feature = "std"), no_std)] // Wrong clippy convention; check // https://rust-lang.github.io/api-guidelines/naming.html -#![allow(clippy::wrong_self_convention)] #![deny(clippy::string_slice)] #![warn(missing_docs)] #![deny(unsafe_code)] -// #![deny(unused_crate_dependencies)] +#![deny(unused_crate_dependencies)] #[cfg(test)] // Satisfy unused_crate_dependencies lint for self-dependency enabling test features From 637368b1833ef8d801046faae2565b90fcf3980b Mon Sep 17 00:00:00 2001 From: Hannes Karppila Date: Thu, 7 Sep 2023 07:19:04 +0300 Subject: [PATCH 07/44] Add changelog entry --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2802074a64..e3ab13930d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,8 +13,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - [#537](https://github.com/FuelLabs/fuel-vm/pull/537): Use dependent cost for `k256`, `s256`, `mcpi`, `scwq`, `swwq` opcodes. These opcodes charged inadequately low costs in comparison to the amount of work. This change should make all transactions that used these opcodes much more expensive than before. - - [#533](https://github.com/FuelLabs/fuel-vm/pull/533): Use custom serialization for fuel-types to allow no_std compilation. +- [#578](https://github.com/FuelLabs/fuel-vm/pull/578): Support `no_std` environments for `fuel-crypto`, falling back to a pure-Rust crypto implementation. ## [Version 0.36.1] From b68c1c5fc27556248cc02972876e642162a67f25 Mon Sep 17 00:00:00 2001 From: Hannes Karppila Date: Thu, 7 Sep 2023 07:24:18 +0300 Subject: [PATCH 08/44] Doc-tests cleanup --- fuel-crypto/Cargo.toml | 1 - fuel-crypto/src/lib.rs | 3 --- fuel-crypto/src/secp/public.rs | 2 +- 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/fuel-crypto/Cargo.toml b/fuel-crypto/Cargo.toml index f5047a8be7..67f4b7a7cf 100644 --- a/fuel-crypto/Cargo.toml +++ b/fuel-crypto/Cargo.toml @@ -11,7 +11,6 @@ repository = { workspace = true } description = "Fuel cryptographic primitives." [dependencies] -borrown = "0.1" coins-bip32 = { version = "0.8", default-features = false, optional = true } coins-bip39 = { version = "0.8", default-features = false, features = ["english"], optional = true } ecdsa = { version = "0.16", default-features = false } diff --git a/fuel-crypto/src/lib.rs b/fuel-crypto/src/lib.rs index 88c4014d87..88d886f14e 100644 --- a/fuel-crypto/src/lib.rs +++ b/fuel-crypto/src/lib.rs @@ -13,9 +13,6 @@ // Satisfy unused_crate_dependencies lint for self-dependency enabling test features use fuel_crypto as _; -/// Required export to implement [`Keystore`]. -#[doc(no_inline)] -pub use borrown; /// Required export for using mnemonic keygen on [`SecretKey::new_from_mnemonic`] #[cfg(feature = "std")] #[doc(no_inline)] diff --git a/fuel-crypto/src/secp/public.rs b/fuel-crypto/src/secp/public.rs index 57ffbf14e7..4726572202 100644 --- a/fuel-crypto/src/secp/public.rs +++ b/fuel-crypto/src/secp/public.rs @@ -19,7 +19,7 @@ use fuel_types::{ use k256::elliptic_curve::sec1::ToEncodedPoint; /// Asymmetric secp256k1 public key, i.e. verifying key, in uncompressed form. -/// https://github.com/FuelLabs/fuel-specs/blob/master/src/protocol/cryptographic-primitives.md#ecdsa-public-key-cryptography +/// #[derive(Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[repr(transparent)] From 1640c4eead06e573be197338d784623fd1b9d1d1 Mon Sep 17 00:00:00 2001 From: Hannes Karppila Date: Thu, 7 Sep 2023 07:27:18 +0300 Subject: [PATCH 09/44] Rename secp module to secp256 --- fuel-crypto/src/lib.rs | 6 +++--- fuel-crypto/src/{secp.rs => secp256.rs} | 0 fuel-crypto/src/{secp => secp256}/backend.rs | 2 +- fuel-crypto/src/{secp => secp256}/backend/k1/k256.rs | 2 +- fuel-crypto/src/{secp => secp256}/backend/k1/secp256k1.rs | 2 +- fuel-crypto/src/{secp => secp256}/backend/r1/p256.rs | 4 ++-- fuel-crypto/src/{secp => secp256}/public.rs | 2 +- fuel-crypto/src/{secp => secp256}/secret.rs | 4 ++-- fuel-crypto/src/{secp => secp256}/signature.rs | 0 fuel-crypto/src/{secp => secp256}/signature_format.rs | 0 fuel-crypto/src/tests/signature.rs | 4 ++-- 11 files changed, 13 insertions(+), 13 deletions(-) rename fuel-crypto/src/{secp.rs => secp256.rs} (100%) rename fuel-crypto/src/{secp => secp256}/backend.rs (98%) rename fuel-crypto/src/{secp => secp256}/backend/k1/k256.rs (99%) rename fuel-crypto/src/{secp => secp256}/backend/k1/secp256k1.rs (99%) rename fuel-crypto/src/{secp => secp256}/backend/r1/p256.rs (98%) rename fuel-crypto/src/{secp => secp256}/public.rs (99%) rename fuel-crypto/src/{secp => secp256}/secret.rs (98%) rename fuel-crypto/src/{secp => secp256}/signature.rs (100%) rename fuel-crypto/src/{secp => secp256}/signature_format.rs (100%) diff --git a/fuel-crypto/src/lib.rs b/fuel-crypto/src/lib.rs index 88d886f14e..be86407422 100644 --- a/fuel-crypto/src/lib.rs +++ b/fuel-crypto/src/lib.rs @@ -33,16 +33,16 @@ mod error; mod hasher; mod message; mod mnemonic; -mod secp; +mod secp256; pub mod ed25519; -pub use secp::backend::{ +pub use secp256::backend::{ k1 as secp256k1, r1 as secp256r1, }; -pub use secp::{ +pub use secp256::{ PublicKey, SecretKey, Signature, diff --git a/fuel-crypto/src/secp.rs b/fuel-crypto/src/secp256.rs similarity index 100% rename from fuel-crypto/src/secp.rs rename to fuel-crypto/src/secp256.rs diff --git a/fuel-crypto/src/secp/backend.rs b/fuel-crypto/src/secp256/backend.rs similarity index 98% rename from fuel-crypto/src/secp/backend.rs rename to fuel-crypto/src/secp256/backend.rs index 16bd818739..4983a91a88 100644 --- a/fuel-crypto/src/secp/backend.rs +++ b/fuel-crypto/src/secp256/backend.rs @@ -31,7 +31,7 @@ mod tests { use crate::{ message::Message, - secp::SecretKey, + secp256::SecretKey, }; use super::k1::{ diff --git a/fuel-crypto/src/secp/backend/k1/k256.rs b/fuel-crypto/src/secp256/backend/k1/k256.rs similarity index 99% rename from fuel-crypto/src/secp/backend/k1/k256.rs rename to fuel-crypto/src/secp256/backend/k1/k256.rs index dfc2620580..11a0d823e9 100644 --- a/fuel-crypto/src/secp/backend/k1/k256.rs +++ b/fuel-crypto/src/secp256/backend/k1/k256.rs @@ -1,6 +1,6 @@ use crate::{ message::Message, - secp::{ + secp256::{ signature_format::{ decode_signature, encode_signature, diff --git a/fuel-crypto/src/secp/backend/k1/secp256k1.rs b/fuel-crypto/src/secp256/backend/k1/secp256k1.rs similarity index 99% rename from fuel-crypto/src/secp/backend/k1/secp256k1.rs rename to fuel-crypto/src/secp256/backend/k1/secp256k1.rs index a2eebd74a9..16f281038a 100644 --- a/fuel-crypto/src/secp/backend/k1/secp256k1.rs +++ b/fuel-crypto/src/secp256/backend/k1/secp256k1.rs @@ -1,6 +1,6 @@ use crate::{ message::Message, - secp::{ + secp256::{ signature_format::{ decode_signature, encode_signature, diff --git a/fuel-crypto/src/secp/backend/r1/p256.rs b/fuel-crypto/src/secp256/backend/r1/p256.rs similarity index 98% rename from fuel-crypto/src/secp/backend/r1/p256.rs rename to fuel-crypto/src/secp256/backend/r1/p256.rs index b81ddf265b..cb0cc177ae 100644 --- a/fuel-crypto/src/secp/backend/r1/p256.rs +++ b/fuel-crypto/src/secp256/backend/r1/p256.rs @@ -1,10 +1,10 @@ //! secp256r1 (P-256) functions #[cfg(feature = "test-helpers")] -use crate::secp::signature_format::encode_signature; +use crate::secp256::signature_format::encode_signature; use crate::{ message::Message, - secp::signature_format::decode_signature, + secp256::signature_format::decode_signature, Error, }; #[cfg(feature = "test-helpers")] diff --git a/fuel-crypto/src/secp/public.rs b/fuel-crypto/src/secp256/public.rs similarity index 99% rename from fuel-crypto/src/secp/public.rs rename to fuel-crypto/src/secp256/public.rs index 4726572202..839cb335c9 100644 --- a/fuel-crypto/src/secp/public.rs +++ b/fuel-crypto/src/secp256/public.rs @@ -1,6 +1,6 @@ use crate::{ hasher::Hasher, - secp::SecretKey, + secp256::SecretKey, Error, }; use core::{ diff --git a/fuel-crypto/src/secp/secret.rs b/fuel-crypto/src/secp256/secret.rs similarity index 98% rename from fuel-crypto/src/secp/secret.rs rename to fuel-crypto/src/secp256/secret.rs index 97a37f1493..0f05593282 100644 --- a/fuel-crypto/src/secp/secret.rs +++ b/fuel-crypto/src/secp256/secret.rs @@ -10,7 +10,7 @@ use core::{ use zeroize::Zeroize; use crate::{ - secp::PublicKey, + secp256::PublicKey, Error, }; @@ -161,7 +161,7 @@ impl SecretKey { /// Return the curve representation of this secret. pub fn public_key(&self) -> PublicKey { - crate::secp::backend::k1::public_key(self) + crate::secp256::backend::k1::public_key(self) } } diff --git a/fuel-crypto/src/secp/signature.rs b/fuel-crypto/src/secp256/signature.rs similarity index 100% rename from fuel-crypto/src/secp/signature.rs rename to fuel-crypto/src/secp256/signature.rs diff --git a/fuel-crypto/src/secp/signature_format.rs b/fuel-crypto/src/secp256/signature_format.rs similarity index 100% rename from fuel-crypto/src/secp/signature_format.rs rename to fuel-crypto/src/secp256/signature_format.rs diff --git a/fuel-crypto/src/tests/signature.rs b/fuel-crypto/src/tests/signature.rs index 0bc3107184..38ba9ca4ca 100644 --- a/fuel-crypto/src/tests/signature.rs +++ b/fuel-crypto/src/tests/signature.rs @@ -45,8 +45,8 @@ fn verify() { let secret = SecretKey::random(rng); let public = secret.public_key(); - let pub1 = crate::secp::backend::k1::public_key(&secret); - let pub2 = crate::secp::backend::k1::public_key(&secret); + let pub1 = crate::secp256::backend::k1::public_key(&secret); + let pub2 = crate::secp256::backend::k1::public_key(&secret); assert_eq!(pub1, pub2); let signature = Signature::sign(&secret, &message); From 73bd6c181190ce49db59e8ddc431027949e51ac5 Mon Sep 17 00:00:00 2001 From: Hannes Karppila Date: Thu, 7 Sep 2023 07:36:32 +0300 Subject: [PATCH 10/44] Fix warnings with no_std target --- fuel-crypto/src/secp256/backend/k1/k256.rs | 4 +++- fuel-crypto/src/secp256/backend/k1/secp256k1.rs | 4 +++- fuel-crypto/src/secp256/secret.rs | 13 +++---------- 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/fuel-crypto/src/secp256/backend/k1/k256.rs b/fuel-crypto/src/secp256/backend/k1/k256.rs index 11a0d823e9..a69c40123c 100644 --- a/fuel-crypto/src/secp256/backend/k1/k256.rs +++ b/fuel-crypto/src/secp256/backend/k1/k256.rs @@ -9,9 +9,11 @@ use crate::{ PublicKey, }, Error, - SecretKey, }; +#[cfg(feature = "random")] +use crate::SecretKey; + use k256::{ ecdsa::{ RecoveryId, diff --git a/fuel-crypto/src/secp256/backend/k1/secp256k1.rs b/fuel-crypto/src/secp256/backend/k1/secp256k1.rs index 16f281038a..a30b04317e 100644 --- a/fuel-crypto/src/secp256/backend/k1/secp256k1.rs +++ b/fuel-crypto/src/secp256/backend/k1/secp256k1.rs @@ -7,7 +7,6 @@ use crate::{ RecoveryId as SecpRecoveryId, }, PublicKey, - SecretKey, }, Error, }; @@ -20,6 +19,9 @@ use secp256k1::{ Secp256k1, }; +#[cfg(feature = "random")] +use crate::SecretKey; + #[cfg(feature = "random")] use rand::{ CryptoRng, diff --git a/fuel-crypto/src/secp256/secret.rs b/fuel-crypto/src/secp256/secret.rs index 0f05593282..3a8c529d84 100644 --- a/fuel-crypto/src/secp256/secret.rs +++ b/fuel-crypto/src/secp256/secret.rs @@ -4,7 +4,6 @@ use core::{ fmt, ops::Deref, str, - str::FromStr, }; use zeroize::Zeroize; @@ -38,14 +37,6 @@ pub struct SecretKey(Bytes32); impl SecretKey { /// Memory length of the type pub const LEN: usize = Bytes32::LEN; - - /// Construct a `SecretKey` directly from its bytes. - /// - /// This constructor expects the given bytes to be a valid secret key. Validity is - /// unchecked. - fn from_bytes_unchecked(bytes: [u8; Self::LEN]) -> Self { - Self(bytes.into()) - } } impl Deref for SecretKey { @@ -143,6 +134,8 @@ impl SecretKey { phrase: &str, path: &str, ) -> Result { + use core::str::FromStr; + let mnemonic = Mnemonic::::new_from_phrase(phrase)?; let path = DerivationPath::from_str(path)?; Self::new_from_mnemonic(path, mnemonic) @@ -156,7 +149,7 @@ impl SecretKey { let derived_priv_key = m.derive_key(d, None)?; let key: &coins_bip32::prelude::SigningKey = derived_priv_key.as_ref(); let bytes: [u8; Self::LEN] = key.to_bytes().into(); - Ok(SecretKey::from_bytes_unchecked(bytes)) + Ok(SecretKey(Bytes32::from(bytes))) } /// Return the curve representation of this secret. From 94ad309404eb38e6d835be9e7f38dfabec9b340a Mon Sep 17 00:00:00 2001 From: Hannes Karppila Date: Thu, 7 Sep 2023 07:37:49 +0300 Subject: [PATCH 11/44] Run checks for no_std fuel-crypto --- .github/workflows/ci.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 64be4341ab..a4ec3701d0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -59,7 +59,7 @@ jobs: - command: check args: --all-targets --all-features - command: check - args: --target thumbv6m-none-eabi -p fuel-asm -p fuel-storage -p fuel-merkle --no-default-features + args: --target thumbv6m-none-eabi -p fuel-crypto --no-default-features - command: check args: --target wasm32-unknown-unknown -p fuel-crypto --no-default-features - command: check @@ -70,6 +70,8 @@ jobs: args: --target wasm32-unknown-unknown -p fuel-types --features typescript --crate-type=cdylib - command: rustc args: --target wasm32-unknown-unknown -p fuel-asm --features typescript --crate-type=cdylib + - command: check + args: --target wasm32-unknown-unknown -p fuel-types --features serde --no-default-features - command: bench args: --workspace --no-run - command: make From 24cad1917e3d1f6d17d796809a76cd7d279a2a18 Mon Sep 17 00:00:00 2001 From: Hannes Karppila Date: Thu, 7 Sep 2023 09:22:48 +0300 Subject: [PATCH 12/44] WIP --- fuel-crypto/Cargo.toml | 2 +- fuel-merkle/benches/smt.rs | 2 +- fuel-merkle/src/sparse/merkle_tree.rs | 6 ++-- fuel-tx/Cargo.toml | 8 ++--- fuel-tx/src/lib.rs | 1 + fuel-tx/test-helpers/Cargo.toml | 2 +- fuel-vm/Cargo.toml | 19 ++++++----- fuel-vm/src/backtrace.rs | 2 ++ fuel-vm/src/checked_transaction.rs | 8 +++-- fuel-vm/src/checked_transaction/balances.rs | 3 +- fuel-vm/src/checked_transaction/types.rs | 2 +- fuel-vm/src/constraints.rs | 2 +- fuel-vm/src/constraints/reg_key.rs | 8 ++--- fuel-vm/src/constraints/reg_key/tests.rs | 4 +-- fuel-vm/src/consts.rs | 2 +- fuel-vm/src/error.rs | 4 +-- fuel-vm/src/interpreter.rs | 3 +- fuel-vm/src/interpreter/balances.rs | 10 +++--- fuel-vm/src/interpreter/blockchain.rs | 2 ++ .../src/interpreter/blockchain/other_tests.rs | 2 +- fuel-vm/src/interpreter/contract.rs | 2 +- fuel-vm/src/interpreter/crypto/tests.rs | 1 + fuel-vm/src/interpreter/diff.rs | 33 ++++++++++--------- fuel-vm/src/interpreter/diff/storage.rs | 8 ++--- .../src/interpreter/executors/instruction.rs | 2 +- fuel-vm/src/interpreter/executors/main.rs | 2 ++ fuel-vm/src/interpreter/flow.rs | 3 +- fuel-vm/src/interpreter/initialization.rs | 1 - fuel-vm/src/interpreter/memory.rs | 3 +- fuel-vm/src/interpreter/memory/tests.rs | 1 + fuel-vm/src/interpreter/post_execution.rs | 1 - fuel-vm/src/interpreter/receipts.rs | 9 +++-- fuel-vm/src/lib.rs | 3 ++ fuel-vm/src/predicate.rs | 3 +- fuel-vm/src/state.rs | 2 ++ fuel-vm/src/state/debugger.rs | 2 +- fuel-vm/src/storage/interpreter.rs | 13 ++++---- fuel-vm/src/storage/memory.rs | 16 ++++----- fuel-vm/src/storage/predicate.rs | 5 ++- fuel-vm/src/tests/gas_factor.rs | 2 +- fuel-vm/src/util.rs | 3 +- 41 files changed, 116 insertions(+), 91 deletions(-) diff --git a/fuel-crypto/Cargo.toml b/fuel-crypto/Cargo.toml index 67f4b7a7cf..8c1e98dd9d 100644 --- a/fuel-crypto/Cargo.toml +++ b/fuel-crypto/Cargo.toml @@ -30,7 +30,7 @@ zeroize = { version = "1.5", features = ["derive"] } [dev-dependencies] bincode = { workspace = true } criterion = "0.4" -fuel-crypto = { path = ".", features = ["random", "test-helpers"] } +# fuel-crypto = { path = ".", features = ["random", "test-helpers"] } sha2 = "0.10" [features] diff --git a/fuel-merkle/benches/smt.rs b/fuel-merkle/benches/smt.rs index 5e221115fe..d66cf272b1 100644 --- a/fuel-merkle/benches/smt.rs +++ b/fuel-merkle/benches/smt.rs @@ -70,7 +70,7 @@ fn sparse_merkle_tree(c: &mut Criterion) { let rng = &mut StdRng::seed_from_u64(8586); let gen = || Some((MerkleTreeKey::new(random_bytes32(rng)), random_bytes32(rng))); - let data = std::iter::from_fn(gen).take(50_000).collect::>(); + let data = core::iter::from_fn(gen).take(50_000).collect::>(); let expected_root = baseline_root(data.clone().into_iter()); let root = subject_root(data.clone().into_iter()); diff --git a/fuel-merkle/src/sparse/merkle_tree.rs b/fuel-merkle/src/sparse/merkle_tree.rs index 3e945606bd..e9e0f84a91 100644 --- a/fuel-merkle/src/sparse/merkle_tree.rs +++ b/fuel-merkle/src/sparse/merkle_tree.rs @@ -1098,7 +1098,7 @@ mod test { random_bytes32(rng), )) }; - let data = std::iter::from_fn(gen).take(1_000).collect::>(); + let data = core::iter::from_fn(gen).take(1_000).collect::>(); let expected_root = { let mut storage = StorageMap::::new(); @@ -1128,7 +1128,7 @@ mod test { random_bytes32(rng), )) }; - let data = std::iter::from_fn(gen).take(0).collect::>(); + let data = core::iter::from_fn(gen).take(0).collect::>(); let expected_root = { let mut storage = StorageMap::::new(); @@ -1158,7 +1158,7 @@ mod test { random_bytes32(rng), )) }; - let data = std::iter::from_fn(gen).take(1).collect::>(); + let data = core::iter::from_fn(gen).take(1).collect::>(); let expected_root = { let mut storage = StorageMap::::new(); diff --git a/fuel-tx/Cargo.toml b/fuel-tx/Cargo.toml index b3644e488f..b95a247eb4 100644 --- a/fuel-tx/Cargo.toml +++ b/fuel-tx/Cargo.toml @@ -18,11 +18,11 @@ fuel-merkle = { workspace = true, default-features = false, optional = true } fuel-types = { workspace = true, default-features = false } itertools = { version = "0.10", default-features = false, optional = true } num-integer = { version = "0.1", default-features = false, optional = true } -num_enum = { version = "0.7", optional = true } +num_enum = { version = "0.7", default-features = false, optional = true } rand = { version = "0.8", default-features = false, features = ["std_rng"], optional = true } serde = { version = "1.0", default-features = false, features = ["alloc", "derive"], optional = true } serde_json = { version = "1.0", default-features = false, features = ["alloc"], optional = true } -strum = { version = "0.24", optional = true } +strum = { version = "0.24", default-features = false, optional = true } strum_macros = { version = "0.24", optional = true } [dev-dependencies] @@ -31,7 +31,7 @@ fuel-crypto = { workspace = true, default-features = false, features = ["random" fuel-tx = { path = ".", features = ["builder", "random"] } fuel-tx-test-helpers = { path = "test-helpers" } fuel-types = { workspace = true, default-features = false, features = ["random"] } -hex = { version = "0.4" } +hex = { version = "0.4", default-features = false } insta = "1.0" quickcheck = "1.0" quickcheck_macros = "1.0" @@ -44,6 +44,6 @@ alloc = ["fuel-types/alloc", "itertools/use_alloc", "derivative", "fuel-merkle", builder = ["alloc", "internals"] internals = [] random = ["fuel-crypto/random", "fuel-types/random", "rand"] -std = ["alloc", "fuel-asm/std", "fuel-crypto/std", "fuel-merkle/std", "fuel-types/std", "itertools/default", "rand?/default", "serde?/default"] +std = ["alloc", "fuel-asm/std", "fuel-crypto/std", "fuel-merkle/std", "fuel-types/std", "itertools/default", "rand?/default", "serde?/default", "hex/std"] # serde is requiring alloc because its mandatory for serde_json. to avoid adding a new feature only for serde_json, we just require `alloc` here since as of the moment we don't have a use case of serde without alloc. serde = ["alloc", "dep:serde", "fuel-asm/serde", "fuel-crypto/serde", "fuel-types/serde", "serde_json"] diff --git a/fuel-tx/src/lib.rs b/fuel-tx/src/lib.rs index bb68b2535c..14334757b6 100644 --- a/fuel-tx/src/lib.rs +++ b/fuel-tx/src/lib.rs @@ -100,6 +100,7 @@ pub use transaction::{ #[cfg(feature = "std")] pub use transaction::Signable; +#[cfg(feature = "alloc")] pub use transaction::UniqueIdentifier; #[cfg(feature = "alloc")] diff --git a/fuel-tx/test-helpers/Cargo.toml b/fuel-tx/test-helpers/Cargo.toml index 14f86301bc..b6f6dcb1c9 100644 --- a/fuel-tx/test-helpers/Cargo.toml +++ b/fuel-tx/test-helpers/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" publish = false [dependencies] -fuel-crypto = { path = "../../fuel-crypto", default-features = false, features = ["random"] } +# fuel-crypto = { path = "../../fuel-crypto", default-features = false, features = ["random"] } fuel-tx = { path = "../../fuel-tx", default-features = false, features = ["builder", "random"] } fuel-types = { path = "../../fuel-types", default-features = false, features = ["random"] } rand = { version = "0.8", default-features = false } diff --git a/fuel-vm/Cargo.toml b/fuel-vm/Cargo.toml index 90edd75b5b..e97d07feb4 100644 --- a/fuel-vm/Cargo.toml +++ b/fuel-vm/Cargo.toml @@ -18,22 +18,22 @@ bitflags = "1" derivative = "2.2" dyn-clone = { version = "1.0", optional = true } ethnum = "1.3" -fuel-asm = { workspace = true } +fuel-asm = { workspace = true, default-features = false } fuel-crypto = { workspace = true, default-features = false } -fuel-merkle = { workspace = true } +fuel-merkle = { workspace = true, default-features = false } fuel-storage = { workspace = true } -fuel-tx = { workspace = true, features = ["builder", "std"] } -fuel-types = { workspace = true } -itertools = "0.10" +fuel-tx = { workspace = true, default-features = false } +fuel-types = { workspace = true, default-features = false } +itertools = { version = "0.10", default-features = false } paste = "1.0" primitive-types = { version = "0.12", default-features = false } rand = { version = "0.8", optional = true } serde = { version = "1.0", features = ["derive", "rc"], optional = true } -sha3 = "0.10" +sha3 = { version = "0.10", default-features = false } static_assertions = "1.1" strum = { version = "0.24", features = ["derive"], optional = true } -tai64 = "4.0" -thiserror = "1.0" +tai64 = { version = "4.0", default-features = false } +thiserror = { version = "1.0", optional = true } [dev-dependencies] ed25519-dalek = { version = "2.0.0", features = ["rand_core"] } @@ -54,7 +54,8 @@ tokio-rayon = "2.1.0" [features] default = ["std"] -std = [] +std = ["alloc", "fuel-tx/builder", "fuel-types/std", "fuel-asm/std", "fuel-tx/std", "itertools/use_std", "thiserror"] +alloc = ["fuel-tx/alloc"] arbitrary = ["fuel-asm/arbitrary"] optimized = [] profile-gas = ["profile-any"] diff --git a/fuel-vm/src/backtrace.rs b/fuel-vm/src/backtrace.rs index a139b74bad..677681b3c6 100644 --- a/fuel-vm/src/backtrace.rs +++ b/fuel-vm/src/backtrace.rs @@ -2,6 +2,8 @@ //! //! As of the moment, doesn't support predicates. +use alloc::vec::Vec; + use crate::{ call::CallFrame, consts::*, diff --git a/fuel-vm/src/checked_transaction.rs b/fuel-vm/src/checked_transaction.rs index f0aaba303c..e2556763fb 100644 --- a/fuel-vm/src/checked_transaction.rs +++ b/fuel-vm/src/checked_transaction.rs @@ -19,9 +19,12 @@ use fuel_types::{ ChainId, }; -use core::borrow::Borrow; +use alloc::boxed::Box; +use core::{ + borrow::Borrow, + future::Future, +}; use fuel_tx::ConsensusParameters; -use std::future::Future; mod balances; pub mod builder; @@ -607,6 +610,7 @@ impl IntoChecked for Transaction { } } +#[cfg(feature = "random")] #[cfg(test)] mod tests { use super::*; diff --git a/fuel-vm/src/checked_transaction/balances.rs b/fuel-vm/src/checked_transaction/balances.rs index 45d0473199..dae1c9bf3a 100644 --- a/fuel-vm/src/checked_transaction/balances.rs +++ b/fuel-vm/src/checked_transaction/balances.rs @@ -23,7 +23,8 @@ use fuel_types::{ AssetId, Word, }; -use std::collections::BTreeMap; + +use alloc::collections::BTreeMap; pub(crate) fn initial_free_balances( transaction: &T, diff --git a/fuel-vm/src/checked_transaction/types.rs b/fuel-vm/src/checked_transaction/types.rs index fe684bfcb7..bab5011d92 100644 --- a/fuel-vm/src/checked_transaction/types.rs +++ b/fuel-vm/src/checked_transaction/types.rs @@ -4,11 +4,11 @@ pub use self::{ create::CheckedMetadata as CreateCheckedMetadata, script::CheckedMetadata as ScriptCheckedMetadata, }; +use alloc::collections::BTreeMap; use fuel_types::{ AssetId, Word, }; -use std::collections::BTreeMap; /// The spendable unrestricted initial assets. /// More information about it in the specification: diff --git a/fuel-vm/src/constraints.rs b/fuel-vm/src/constraints.rs index a845d27869..030d8c0aab 100644 --- a/fuel-vm/src/constraints.rs +++ b/fuel-vm/src/constraints.rs @@ -1,5 +1,5 @@ //! Types to help constrain inputs to functions to only what is used. -use std::ops::{ +use core::ops::{ Deref, DerefMut, }; diff --git a/fuel-vm/src/constraints/reg_key.rs b/fuel-vm/src/constraints/reg_key.rs index 52c4f029c6..6f426c9e73 100644 --- a/fuel-vm/src/constraints/reg_key.rs +++ b/fuel-vm/src/constraints/reg_key.rs @@ -2,7 +2,7 @@ //! the register index is valid. //! //! This module also provides utilities for mutably accessing multiple registers. -use std::ops::{ +use core::ops::{ Deref, DerefMut, }; @@ -284,7 +284,7 @@ impl<'r> ProgramRegisters<'r> { b: WriteRegKey, ) -> Option<(&mut Word, &mut Word)> { match a.cmp(&b) { - std::cmp::Ordering::Less => { + core::cmp::Ordering::Less => { // Translate the `a` absolute register index to a program register index. let a = a.translate(); // Split the array at the first register which is a. @@ -300,8 +300,8 @@ impl<'r> ProgramRegisters<'r> { Some((i, j)) } // Cannot mutably borrow the same register twice. - std::cmp::Ordering::Equal => None, - std::cmp::Ordering::Greater => { + core::cmp::Ordering::Equal => None, + core::cmp::Ordering::Greater => { // Translate the `b` absolute register index to a program register index. let b = b.translate(); // Split the array at the first register which is b. diff --git a/fuel-vm/src/constraints/reg_key/tests.rs b/fuel-vm/src/constraints/reg_key/tests.rs index 7d5ae2509c..9389579203 100644 --- a/fuel-vm/src/constraints/reg_key/tests.rs +++ b/fuel-vm/src/constraints/reg_key/tests.rs @@ -4,7 +4,7 @@ use test_case::test_case; #[test] fn can_split() { let mut reg: [Word; VM_REGISTER_COUNT] = - std::iter::successors(Some(0), |x| Some(x + 1)) + core::iter::successors(Some(0), |x| Some(x + 1)) .take(VM_REGISTER_COUNT) .collect::>() .try_into() @@ -71,7 +71,7 @@ fn can_split() { #[test_case(4, 2 => Some((4, 2)))] fn can_split_writes(a: usize, b: usize) -> Option<(Word, Word)> { let mut reg: [Word; VM_REGISTER_PROGRAM_COUNT] = - std::iter::successors(Some(0), |x| Some(x + 1)) + core::iter::successors(Some(0), |x| Some(x + 1)) .take(VM_REGISTER_PROGRAM_COUNT) .collect::>() .try_into() diff --git a/fuel-vm/src/consts.rs b/fuel-vm/src/consts.rs index 6199665895..b5c9cd8575 100644 --- a/fuel-vm/src/consts.rs +++ b/fuel-vm/src/consts.rs @@ -5,7 +5,7 @@ use fuel_types::{ Word, }; -use std::mem; +use core::mem; /// Register count for checking constraints pub const VM_REGISTER_COUNT: usize = 64; diff --git a/fuel-vm/src/error.rs b/fuel-vm/src/error.rs index 74a5b2657f..ec216d1a40 100644 --- a/fuel-vm/src/error.rs +++ b/fuel-vm/src/error.rs @@ -8,11 +8,9 @@ use fuel_asm::{ use fuel_tx::CheckError; use thiserror::Error; -use std::{ +use core::{ convert::Infallible as StdInfallible, - error::Error as StdError, fmt, - io, }; /// Interpreter runtime error variants. diff --git a/fuel-vm/src/interpreter.rs b/fuel-vm/src/interpreter.rs index 4d743d374a..b9305f6879 100644 --- a/fuel-vm/src/interpreter.rs +++ b/fuel-vm/src/interpreter.rs @@ -8,7 +8,8 @@ use crate::{ context::Context, state::Debugger, }; -use std::{ +use alloc::vec::Vec; +use core::{ mem, ops::Index, }; diff --git a/fuel-vm/src/interpreter/balances.rs b/fuel-vm/src/interpreter/balances.rs index 052c1e3476..0dc2299f0a 100644 --- a/fuel-vm/src/interpreter/balances.rs +++ b/fuel-vm/src/interpreter/balances.rs @@ -16,13 +16,11 @@ use fuel_tx::CheckError; use fuel_types::AssetId; use itertools::Itertools; -use std::{ - collections::{ - BTreeMap, - HashMap, - }, - ops::Index, +use alloc::collections::{ + hash_map::HashMap, + BTreeMap, }; +use core::ops::Index; use super::MemoryRange; diff --git a/fuel-vm/src/interpreter/blockchain.rs b/fuel-vm/src/interpreter/blockchain.rs index f90aceafc6..f87639eb4b 100644 --- a/fuel-vm/src/interpreter/blockchain.rs +++ b/fuel-vm/src/interpreter/blockchain.rs @@ -1,3 +1,5 @@ +use alloc::vec::Vec; + use super::{ contract::{ balance, diff --git a/fuel-vm/src/interpreter/blockchain/other_tests.rs b/fuel-vm/src/interpreter/blockchain/other_tests.rs index d7be55cf4d..49ffe08a54 100644 --- a/fuel-vm/src/interpreter/blockchain/other_tests.rs +++ b/fuel-vm/src/interpreter/blockchain/other_tests.rs @@ -2,7 +2,7 @@ use crate::{ interpreter::memory::Memory, storage::MemoryStorage, }; -use std::iter; +use core::iter; use super::*; use crate::interpreter::PanicContext; diff --git a/fuel-vm/src/interpreter/contract.rs b/fuel-vm/src/interpreter/contract.rs index 24a9cc54f2..f2a032c2c3 100644 --- a/fuel-vm/src/interpreter/contract.rs +++ b/fuel-vm/src/interpreter/contract.rs @@ -52,7 +52,7 @@ use fuel_types::{ ContractId, }; -use std::borrow::Cow; +use alloc::borrow::Cow; #[cfg(test)] mod tests; diff --git a/fuel-vm/src/interpreter/crypto/tests.rs b/fuel-vm/src/interpreter/crypto/tests.rs index 867eebbc10..462da6d6ca 100644 --- a/fuel-vm/src/interpreter/crypto/tests.rs +++ b/fuel-vm/src/interpreter/crypto/tests.rs @@ -11,6 +11,7 @@ use crate::{ use super::*; +#[cfg(feature = "random")] #[test] fn test_recover_secp256k1() -> Result<(), RuntimeError> { let mut memory: Memory = vec![1u8; MEM_SIZE].try_into().unwrap(); diff --git a/fuel-vm/src/interpreter/diff.rs b/fuel-vm/src/interpreter/diff.rs index 25bd9f0a86..3ca8a79e14 100644 --- a/fuel-vm/src/interpreter/diff.rs +++ b/fuel-vm/src/interpreter/diff.rs @@ -4,16 +4,19 @@ //! This module is experimental work in progress and currently only used in testing //! although it could potentially stabilize to be used in production. -use std::{ - any::Any, +use alloc::{ + vec::Vec, collections::{ HashMap, HashSet, }, + sync::Arc, +}; +use core::{ fmt::Debug, + any::Any, hash::Hash, ops::AddAssign, - sync::Arc, }; use fuel_asm::Word; @@ -196,7 +199,7 @@ fn capture_buffer_state<'iter, I, T>( change: fn(Delta>) -> Change, ) -> impl Iterator> + 'iter where - T: 'static + std::cmp::PartialEq + Clone, + T: 'static + core::cmp::PartialEq + Clone, I: Iterator + 'iter, { a.enumerate().zip(b).filter_map(move |(a, b)| { @@ -223,8 +226,8 @@ fn capture_map_state<'iter, K, V>( change: ChangeDeltaVariant>>, ) -> Vec> where - K: 'static + std::cmp::PartialEq + Eq + Clone + Hash + Debug, - V: 'static + std::cmp::PartialEq + Clone + Debug, + K: 'static + core::cmp::PartialEq + Eq + Clone + Hash + Debug, + V: 'static + core::cmp::PartialEq + Clone + Debug, { let a_keys: HashSet<_> = a.keys().collect(); let b_keys: HashSet<_> = b.keys().collect(); @@ -240,8 +243,8 @@ fn capture_map_state_inner<'iter, K, V>( b_keys: &'iter HashSet<&K>, ) -> impl Iterator>>> + 'iter where - K: 'static + std::cmp::PartialEq + Eq + Clone + Hash + Debug, - V: 'static + std::cmp::PartialEq + Clone + Debug, + K: 'static + core::cmp::PartialEq + Eq + Clone + Hash + Debug, + V: 'static + core::cmp::PartialEq + Clone + Debug, { let a_diff = a_keys.difference(b_keys).map(|k| Delta { from: MapState { @@ -287,7 +290,7 @@ fn capture_vec_state<'iter, I, T>( change: ChangeDeltaVariant>>, ) -> impl Iterator> + 'iter where - T: 'static + std::cmp::PartialEq + Clone, + T: 'static + core::cmp::PartialEq + Clone, I: Iterator + 'iter, { capture_vec_state_inner(a, b).map(move |(index, a, b)| { @@ -302,13 +305,13 @@ fn capture_vec_state_inner<'iter, I, T>( b: I, ) -> impl Iterator, Option)> + 'iter where - T: 'static + std::cmp::PartialEq + Clone, + T: 'static + core::cmp::PartialEq + Clone, I: Iterator + 'iter, { a.map(Some) - .chain(std::iter::repeat(None)) + .chain(core::iter::repeat(None)) .enumerate() - .zip(b.map(Some).chain(std::iter::repeat(None))) + .zip(b.map(Some).chain(core::iter::repeat(None))) .take_while(|((_, a), b)| a.is_some() || b.is_some()) .filter_map(|((index, a), b)| { b.map_or(true, |b| a.map_or(true, |a| a != b)) @@ -360,8 +363,8 @@ impl Interpreter { .take_while(|((_, a), b)| a != b) .map(|((_, a), b)| (*a, *b)) .unzip(); - from.splice(..0, std::iter::once(s_from)).next(); - to.splice(..0, std::iter::once(s_to)).next(); + from.splice(..0, core::iter::once(s_from)).next(); + to.splice(..0, core::iter::once(s_to)).next(); diff.changes.push(Change::Memory(Delta { from: Memory { start, bytes: from }, to: Memory { start, bytes: to }, @@ -422,7 +425,7 @@ impl Interpreter { } fn invert_vec(vector: &mut Vec, value: &VecState>) { - use std::cmp::Ordering; + use core::cmp::Ordering; match (&value, value.index.cmp(&vector.len())) { ( VecState { diff --git a/fuel-vm/src/interpreter/diff/storage.rs b/fuel-vm/src/interpreter/diff/storage.rs index 7f68d01c87..e6c5370fe0 100644 --- a/fuel-vm/src/interpreter/diff/storage.rs +++ b/fuel-vm/src/interpreter/diff/storage.rs @@ -1,7 +1,5 @@ -use std::{ - collections::HashMap, - fmt::Debug, -}; +use alloc::collections::HashMap; +use core::fmt::Debug; use fuel_storage::{ StorageRead, @@ -234,7 +232,7 @@ fn mappable_delta_to_hashmap<'value, K, V>( state: &mut Delta>, delta: &'value MappableDelta, ) where - K: Copy + PartialEq + Eq + std::hash::Hash + 'static, + K: Copy + PartialEq + Eq + core::hash::Hash + 'static, V: Clone + 'static, { match delta { diff --git a/fuel-vm/src/interpreter/executors/instruction.rs b/fuel-vm/src/interpreter/executors/instruction.rs index 8e028cb674..96b5362b04 100644 --- a/fuel-vm/src/interpreter/executors/instruction.rs +++ b/fuel-vm/src/interpreter/executors/instruction.rs @@ -26,7 +26,7 @@ use fuel_asm::{ }; use fuel_types::Word; -use std::ops::Div; +use core::ops::Div; impl Interpreter where diff --git a/fuel-vm/src/interpreter/executors/main.rs b/fuel-vm/src/interpreter/executors/main.rs index 9bd6f8f4e1..0da79a0661 100644 --- a/fuel-vm/src/interpreter/executors/main.rs +++ b/fuel-vm/src/interpreter/executors/main.rs @@ -1,6 +1,8 @@ #[cfg(test)] mod tests; +use alloc::{vec::Vec, vec}; + use crate::{ checked_transaction::{ Checked, diff --git a/fuel-vm/src/interpreter/flow.rs b/fuel-vm/src/interpreter/flow.rs index 4fa8d2e1b8..fd69cd1fca 100644 --- a/fuel-vm/src/interpreter/flow.rs +++ b/fuel-vm/src/interpreter/flow.rs @@ -72,7 +72,8 @@ use fuel_types::{ ContractId, Word, }; -use std::cmp; +use core::cmp; +use alloc::vec::Vec; #[cfg(test)] mod jump_tests; diff --git a/fuel-vm/src/interpreter/initialization.rs b/fuel-vm/src/interpreter/initialization.rs index 4e3109d6e9..0966708b57 100644 --- a/fuel-vm/src/interpreter/initialization.rs +++ b/fuel-vm/src/interpreter/initialization.rs @@ -29,7 +29,6 @@ use crate::{ error::BugVariant::GlobalGasUnderflow, interpreter::CheckedMetadata, }; -use std::io; impl Interpreter where diff --git a/fuel-vm/src/interpreter/memory.rs b/fuel-vm/src/interpreter/memory.rs index 0b796e54c1..c122476202 100644 --- a/fuel-vm/src/interpreter/memory.rs +++ b/fuel-vm/src/interpreter/memory.rs @@ -20,10 +20,11 @@ use fuel_types::{ Word, }; -use std::{ +use core::{ ops, ops::Range, }; +use alloc::boxed::Box; pub type Memory = Box<[u8; SIZE]>; diff --git a/fuel-vm/src/interpreter/memory/tests.rs b/fuel-vm/src/interpreter/memory/tests.rs index 482abb31c1..5197528834 100644 --- a/fuel-vm/src/interpreter/memory/tests.rs +++ b/fuel-vm/src/interpreter/memory/tests.rs @@ -9,6 +9,7 @@ use fuel_asm::op; use fuel_tx::ConsensusParameters; use test_case::test_case; +#[cfg(feature = "random")] #[test] fn memcopy() { let tx_params = TxParameters::default().with_max_gas_per_tx(Word::MAX / 2); diff --git a/fuel-vm/src/interpreter/post_execution.rs b/fuel-vm/src/interpreter/post_execution.rs index c27c56fabd..1f54ef1d30 100644 --- a/fuel-vm/src/interpreter/post_execution.rs +++ b/fuel-vm/src/interpreter/post_execution.rs @@ -14,7 +14,6 @@ use fuel_types::{ AssetId, Word, }; -use std::io; impl Interpreter where diff --git a/fuel-vm/src/interpreter/receipts.rs b/fuel-vm/src/interpreter/receipts.rs index d4c462f57d..0cec263ff7 100644 --- a/fuel-vm/src/interpreter/receipts.rs +++ b/fuel-vm/src/interpreter/receipts.rs @@ -1,5 +1,8 @@ -use core::ops::Index; -use std::mem; +use alloc::vec::Vec; +use core::{ + mem, + ops::Index, +}; use fuel_merkle::binary; use fuel_tx::Receipt; @@ -124,7 +127,7 @@ mod tests { }; use fuel_tx::Receipt; use fuel_types::canonical::SerializedSize; - use std::iter; + use core::iter; fn create_receipt() -> Receipt { Receipt::call( diff --git a/fuel-vm/src/lib.rs b/fuel-vm/src/lib.rs index e0f76bd97e..7397beb9ed 100644 --- a/fuel-vm/src/lib.rs +++ b/fuel-vm/src/lib.rs @@ -4,6 +4,9 @@ #![deny(unsafe_code)] #![deny(unused_crate_dependencies)] #![deny(clippy::string_slice)] +#![cfg_attr(not(feature = "std"), no_std)] + +extern crate alloc; pub mod arith; pub mod backtrace; diff --git a/fuel-vm/src/predicate.rs b/fuel-vm/src/predicate.rs index 144b18b12b..3bbb5675f9 100644 --- a/fuel-vm/src/predicate.rs +++ b/fuel-vm/src/predicate.rs @@ -40,6 +40,7 @@ impl RuntimePredicate { } } +#[cfg(feature = "random")] #[test] fn from_tx_works() { use fuel_asm::op; @@ -52,7 +53,7 @@ fn from_tx_works() { SeedableRng, }; - use std::iter; + use core::iter; let rng = &mut StdRng::seed_from_u64(2322u64); diff --git a/fuel-vm/src/state.rs b/fuel-vm/src/state.rs index 747ad1da94..0fa840670e 100644 --- a/fuel-vm/src/state.rs +++ b/fuel-vm/src/state.rs @@ -1,5 +1,7 @@ //! Runtime state representation for the VM +use alloc::vec::Vec; + use fuel_tx::Receipt; use fuel_types::{ Bytes32, diff --git a/fuel-vm/src/state/debugger.rs b/fuel-vm/src/state/debugger.rs index ca92892558..7699362edd 100644 --- a/fuel-vm/src/state/debugger.rs +++ b/fuel-vm/src/state/debugger.rs @@ -9,7 +9,7 @@ use fuel_types::{ Word, }; -use std::collections::{ +use alloc::collections::{ HashMap, HashSet, }; diff --git a/fuel-vm/src/storage/interpreter.rs b/fuel-vm/src/storage/interpreter.rs index 9e90addec3..9537c6abe0 100644 --- a/fuel-vm/src/storage/interpreter.rs +++ b/fuel-vm/src/storage/interpreter.rs @@ -28,14 +28,13 @@ use crate::storage::{ ContractsRawCode, ContractsState, }; -use std::{ +use alloc::{ borrow::Cow, - error::Error as StdError, - io, - ops::{ - Deref, - DerefMut, - }, + vec::Vec, +}; +use core::ops::{ + Deref, + DerefMut, }; /// When this trait is implemented, the underlying interpreter is guaranteed to diff --git a/fuel-vm/src/storage/memory.rs b/fuel-vm/src/storage/memory.rs index 3eb3bfe9a3..20eed62d1d 100644 --- a/fuel-vm/src/storage/memory.rs +++ b/fuel-vm/src/storage/memory.rs @@ -36,10 +36,10 @@ use fuel_types::{ use itertools::Itertools; use tai64::Tai64; -use std::{ +use alloc::{ borrow::Cow, collections::BTreeMap, - io::Read, + vec::Vec, }; use super::interpreter::ContractsAssetsStorage; @@ -387,7 +387,7 @@ impl InterpreterStorage for MemoryStorage { let mut iter = self.memory.contract_state.range(start..end); let mut next_item = iter.next(); - Ok(std::iter::successors(Some(**start_key), |n| { + Ok(core::iter::successors(Some(**start_key), |n| { let mut n = *n; if add_one(&mut n) { None @@ -397,15 +397,15 @@ impl InterpreterStorage for MemoryStorage { }) .map(|next_key: [u8; 32]| match next_item.take() { Some((k, v)) => match next_key.cmp(k.state_key()) { - std::cmp::Ordering::Less => { + core::cmp::Ordering::Less => { next_item = Some((k, v)); None } - std::cmp::Ordering::Equal => { + core::cmp::Ordering::Equal => { next_item = iter.next(); Some(Cow::Borrowed(v)) } - std::cmp::Ordering::Greater => None, + core::cmp::Ordering::Greater => None, }, None => None, }) @@ -420,7 +420,7 @@ impl InterpreterStorage for MemoryStorage { values: &[Bytes32], ) -> Result, Self::DataError> { let mut any_unset_key = false; - let values: Vec<_> = std::iter::successors(Some(**start_key), |n| { + let values: Vec<_> = core::iter::successors(Some(**start_key), |n| { let mut n = *n; if add_one(&mut n) { None @@ -447,7 +447,7 @@ impl InterpreterStorage for MemoryStorage { ) -> Result, Self::DataError> { let mut all_set_key = true; let mut values: std::collections::HashSet<_> = - std::iter::successors(Some(**start_key), |n| { + core::iter::successors(Some(**start_key), |n| { let mut n = *n; if add_one(&mut n) { None diff --git a/fuel-vm/src/storage/predicate.rs b/fuel-vm/src/storage/predicate.rs index 98dac034fc..db26aa5a8e 100644 --- a/fuel-vm/src/storage/predicate.rs +++ b/fuel-vm/src/storage/predicate.rs @@ -1,4 +1,7 @@ -use std::borrow::Cow; +use alloc::{ + borrow::Cow, + vec::Vec, +}; use crate::{ error::InterpreterError, diff --git a/fuel-vm/src/tests/gas_factor.rs b/fuel-vm/src/tests/gas_factor.rs index b47401df39..75d9ad877b 100644 --- a/fuel-vm/src/tests/gas_factor.rs +++ b/fuel-vm/src/tests/gas_factor.rs @@ -7,7 +7,7 @@ use fuel_tx::{ FeeParameters, }; use fuel_vm::interpreter::InterpreterParams; -use std::iter; +use core::iter; #[test] fn gas_factor_rounds_correctly() { diff --git a/fuel-vm/src/util.rs b/fuel-vm/src/util.rs index 61c9485eec..cf4c75d16c 100644 --- a/fuel-vm/src/util.rs +++ b/fuel-vm/src/util.rs @@ -53,7 +53,7 @@ macro_rules! script_with_data_offset { }; // evaluate script expression with zeroed data offset to get the script length let script_bytes: ::std::vec::Vec = - ::std::iter::IntoIterator::into_iter({ $script }).collect(); + ::core::iter::IntoIterator::into_iter({ $script }).collect(); // compute the script data offset within the VM memory given the script length { use $crate::{ @@ -76,6 +76,7 @@ macro_rules! script_with_data_offset { } #[allow(missing_docs)] +#[cfg(feature = "random")] #[cfg(any(test, feature = "test-helpers"))] /// Testing utilities pub mod test_helpers { From 4537fbdd44ce0cfd402a524f950883152cc94f23 Mon Sep 17 00:00:00 2001 From: Hannes Karppila Date: Wed, 13 Sep 2023 08:43:36 +0300 Subject: [PATCH 13/44] WIP --- fuel-crypto/src/lib.rs | 4 +- fuel-crypto/src/secp256/backend/r1/p256.rs | 1 + fuel-tx/test-helpers/Cargo.toml | 2 +- fuel-vm/Cargo.toml | 3 +- fuel-vm/src/error.rs | 93 ++++++++-------------- fuel-vm/src/interpreter/balances.rs | 6 +- fuel-vm/src/interpreter/diff.rs | 20 +++-- fuel-vm/src/interpreter/diff/storage.rs | 2 +- fuel-vm/src/interpreter/executors/main.rs | 5 +- fuel-vm/src/interpreter/flow.rs | 4 +- fuel-vm/src/interpreter/initialization.rs | 9 +-- fuel-vm/src/interpreter/memory.rs | 2 +- fuel-vm/src/interpreter/post_execution.rs | 7 +- fuel-vm/src/interpreter/receipts.rs | 2 +- fuel-vm/src/state/debugger.rs | 2 +- fuel-vm/src/storage/interpreter.rs | 15 ++-- fuel-vm/src/tests/gas_factor.rs | 2 +- 17 files changed, 79 insertions(+), 100 deletions(-) diff --git a/fuel-crypto/src/lib.rs b/fuel-crypto/src/lib.rs index be86407422..1031b0109d 100644 --- a/fuel-crypto/src/lib.rs +++ b/fuel-crypto/src/lib.rs @@ -9,9 +9,9 @@ #![deny(unsafe_code)] #![deny(unused_crate_dependencies)] -#[cfg(test)] // Satisfy unused_crate_dependencies lint for self-dependency enabling test features -use fuel_crypto as _; +// #[cfg(test)] +// use fuel_crypto as _; /// Required export for using mnemonic keygen on [`SecretKey::new_from_mnemonic`] #[cfg(feature = "std")] diff --git a/fuel-crypto/src/secp256/backend/r1/p256.rs b/fuel-crypto/src/secp256/backend/r1/p256.rs index cb0cc177ae..bb9a6aab3c 100644 --- a/fuel-crypto/src/secp256/backend/r1/p256.rs +++ b/fuel-crypto/src/secp256/backend/r1/p256.rs @@ -57,6 +57,7 @@ pub fn sign_prehashed( /// Convert the public key point to its uncompressed non-prefixed representation, /// i.e. 32 bytes of x coordinate and 32 bytes of y coordinate. +#[cfg(feature = "test-helpers")] pub fn encode_pubkey(key: VerifyingKey) -> [u8; 64] { let point = key.to_encoded_point(false); let mut result = [0u8; 64]; diff --git a/fuel-tx/test-helpers/Cargo.toml b/fuel-tx/test-helpers/Cargo.toml index b6f6dcb1c9..14f86301bc 100644 --- a/fuel-tx/test-helpers/Cargo.toml +++ b/fuel-tx/test-helpers/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" publish = false [dependencies] -# fuel-crypto = { path = "../../fuel-crypto", default-features = false, features = ["random"] } +fuel-crypto = { path = "../../fuel-crypto", default-features = false, features = ["random"] } fuel-tx = { path = "../../fuel-tx", default-features = false, features = ["builder", "random"] } fuel-types = { path = "../../fuel-types", default-features = false, features = ["random"] } rand = { version = "0.8", default-features = false } diff --git a/fuel-vm/Cargo.toml b/fuel-vm/Cargo.toml index e97d07feb4..affead9774 100644 --- a/fuel-vm/Cargo.toml +++ b/fuel-vm/Cargo.toml @@ -34,6 +34,7 @@ static_assertions = "1.1" strum = { version = "0.24", features = ["derive"], optional = true } tai64 = { version = "4.0", default-features = false } thiserror = { version = "1.0", optional = true } +hashbrown = "0.14" [dev-dependencies] ed25519-dalek = { version = "2.0.0", features = ["rand_core"] } @@ -62,5 +63,5 @@ profile-gas = ["profile-any"] profile-coverage = ["profile-any"] profile-any = ["dyn-clone"] # All profiling features should depend on this random = ["fuel-crypto/random", "fuel-types/random", "fuel-tx/random", "rand"] -serde = ["dep:serde", "fuel-asm/serde", "fuel-types/serde", "fuel-tx/serde"] +serde = ["dep:serde", "hashbrown/serde", "fuel-asm/serde", "fuel-types/serde", "fuel-tx/serde"] test-helpers = ["fuel-tx/builder", "random", "dep:anyhow", "fuel-crypto/test-helpers"] diff --git a/fuel-vm/src/error.rs b/fuel-vm/src/error.rs index ec216d1a40..136a770022 100644 --- a/fuel-vm/src/error.rs +++ b/fuel-vm/src/error.rs @@ -35,13 +35,15 @@ pub enum InterpreterError { /// state transitions. #[error("Execution error")] NoTransactionInitialized, - /// I/O and OS related errors. - #[error("Unrecoverable error: {0}")] - Io(#[from] io::Error), - + // /// I/O and OS related errors. + // #[error("Unrecoverable error: {0}")] + // Io(#[from] io::Error), #[error("Execution error")] /// The debug state is not initialized; debug routines can't be called. DebugStateNotInitialized, + /// I/O and OS related errors. + #[error("Runtime error: {0}")] + RuntimeError(RuntimeError), } impl InterpreterError { @@ -80,31 +82,17 @@ impl InterpreterError { _ => None, } } - - /// Produces a `halt` error from `io`. - pub fn from_io(e: E) -> Self - where - E: Into, - { - Self::Io(e.into()) - } } impl From for InterpreterError { fn from(error: RuntimeError) -> Self { match error { RuntimeError::Recoverable(e) => Self::Panic(e), - RuntimeError::Halt(e) => Self::Io(e), + RuntimeError::Halt => todo!(), // TODO } } } -impl From for io::Error { - fn from(e: InterpreterError) -> Self { - io::Error::new(io::ErrorKind::Other, e) - } -} - impl PartialEq for InterpreterError { fn eq(&self, other: &Self) -> bool { match (self, other) { @@ -113,7 +101,7 @@ impl PartialEq for InterpreterError { (Self::CheckError(s), Self::CheckError(o)) => s == o, (Self::PredicateFailure, Self::PredicateFailure) => true, (Self::NoTransactionInitialized, Self::NoTransactionInitialized) => true, - (Self::Io(s), Self::Io(o)) => s.kind() == o.kind(), + (Self::RuntimeError(s), Self::RuntimeError(o)) => todo!(), // TODO (Self::DebugStateNotInitialized, Self::DebugStateNotInitialized) => true, @@ -122,7 +110,8 @@ impl PartialEq for InterpreterError { } } -#[derive(Debug, Error)] +#[derive(Debug)] +#[cfg_attr(feature = "std", derive(thiserror::Error))] /// Runtime error description that should either be specified in the protocol or /// halt the execution. pub enum RuntimeError { @@ -130,8 +119,7 @@ pub enum RuntimeError { #[error(transparent)] Recoverable(#[from] PanicReason), /// Unspecified error that should halt the execution. - #[error(transparent)] - Halt(#[from] io::Error), // TODO: a more generic error type + Halt, // TODO } impl RuntimeError { @@ -142,23 +130,13 @@ impl RuntimeError { /// Flag whether the error must halt the execution. pub const fn must_halt(&self) -> bool { - matches!(self, Self::Halt(_)) - } - - /// Produces a `halt` error from `io`. - pub fn from_io(e: E) -> Self - where - E: Into, - { - Self::Halt(e.into()) + matches!(self, Self::Halt) } /// Unexpected behavior occurred - pub fn unexpected_behavior(error: E) -> Self - where - E: Into>, - { - Self::Halt(io::Error::new(io::ErrorKind::Other, error)) + pub fn unexpected_behavior(error: E) -> Self { + todo!(); + Self::Halt // TODO: contents } } @@ -166,12 +144,21 @@ impl PartialEq for RuntimeError { fn eq(&self, other: &Self) -> bool { match (self, other) { (RuntimeError::Recoverable(s), RuntimeError::Recoverable(o)) => s == o, - (RuntimeError::Halt(s), RuntimeError::Halt(o)) => s.kind() == o.kind(), + (RuntimeError::Halt, RuntimeError::Halt) => todo!(), // TODO _ => false, } } } +impl fmt::Display for RuntimeError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Recoverable(reason) => write!(f, "Recoverable error: {}", reason), + Self::Halt => write!(f, "Unrecoverable error"), + } + } +} + #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] /// Infallible implementation that converts into [`io::Error`]. pub struct Infallible(StdInfallible); @@ -182,12 +169,6 @@ impl fmt::Display for Infallible { } } -impl StdError for Infallible { - fn source(&self) -> Option<&(dyn StdError + 'static)> { - Some(&self.0) - } -} - impl From for Infallible where E: Into, @@ -215,12 +196,6 @@ impl From for PanicReason { } } -impl From for io::Error { - fn from(_e: Infallible) -> io::Error { - unreachable!() - } -} - impl From for RuntimeError { fn from(value: core::array::TryFromSliceError) -> Self { Self::Recoverable(value.into()) @@ -251,9 +226,9 @@ pub enum PredicateVerificationFailed { /// The cumulative gas overflowed the u64 accumulator #[error("Cumulative gas computation overflowed the u64 accumulator")] GasOverflow, - /// An unexpected error occurred. - #[error(transparent)] - Io(#[from] io::Error), + /// TODO + #[error("TODO")] // TODO + RuntimeError, } impl From for CheckError { @@ -271,7 +246,9 @@ impl From for PredicateVerificationFailed { error if error.panic_reason() == Some(PanicReason::OutOfGas) => { PredicateVerificationFailed::OutOfGas } - InterpreterError::Io(e) => PredicateVerificationFailed::Io(e), + InterpreterError::RuntimeError(e) => { + PredicateVerificationFailed::RuntimeError + } _ => PredicateVerificationFailed::False, } } @@ -426,14 +403,6 @@ impl fmt::Display for Bug { } } -impl StdError for Bug {} - -impl From for RuntimeError { - fn from(bug: Bug) -> Self { - Self::Halt(io::Error::new(io::ErrorKind::Other, bug)) - } -} - impl From for InterpreterError { fn from(bug: Bug) -> Self { RuntimeError::from(bug).into() diff --git a/fuel-vm/src/interpreter/balances.rs b/fuel-vm/src/interpreter/balances.rs index 0dc2299f0a..ed05884e79 100644 --- a/fuel-vm/src/interpreter/balances.rs +++ b/fuel-vm/src/interpreter/balances.rs @@ -16,11 +16,9 @@ use fuel_tx::CheckError; use fuel_types::AssetId; use itertools::Itertools; -use alloc::collections::{ - hash_map::HashMap, - BTreeMap, -}; +use alloc::collections::BTreeMap; use core::ops::Index; +use hashbrown::HashMap; use super::MemoryRange; diff --git a/fuel-vm/src/interpreter/diff.rs b/fuel-vm/src/interpreter/diff.rs index 3ca8a79e14..ac25ec1d38 100644 --- a/fuel-vm/src/interpreter/diff.rs +++ b/fuel-vm/src/interpreter/diff.rs @@ -5,19 +5,19 @@ //! although it could potentially stabilize to be used in production. use alloc::{ - vec::Vec, - collections::{ - HashMap, - HashSet, - }, sync::Arc, + vec::Vec, }; use core::{ - fmt::Debug, any::Any, + fmt::Debug, hash::Hash, ops::AddAssign, }; +use hashbrown::{ + HashMap, + HashSet, +}; use fuel_asm::Word; use fuel_storage::{ @@ -243,7 +243,13 @@ fn capture_map_state_inner<'iter, K, V>( b_keys: &'iter HashSet<&K>, ) -> impl Iterator>>> + 'iter where - K: 'static + core::cmp::PartialEq + Eq + Clone + Hash + Debug, + K: 'static + + core::cmp::PartialEq + + Eq + + Clone + + Hash + + Debug + + for<'a> core::borrow::Borrow<&'a K>, V: 'static + core::cmp::PartialEq + Clone + Debug, { let a_diff = a_keys.difference(b_keys).map(|k| Delta { diff --git a/fuel-vm/src/interpreter/diff/storage.rs b/fuel-vm/src/interpreter/diff/storage.rs index e6c5370fe0..b7fa30c1cf 100644 --- a/fuel-vm/src/interpreter/diff/storage.rs +++ b/fuel-vm/src/interpreter/diff/storage.rs @@ -1,5 +1,5 @@ -use alloc::collections::HashMap; use core::fmt::Debug; +use hashbrown::HashMap; use fuel_storage::{ StorageRead, diff --git a/fuel-vm/src/interpreter/executors/main.rs b/fuel-vm/src/interpreter/executors/main.rs index 0da79a0661..ad9e8a06c1 100644 --- a/fuel-vm/src/interpreter/executors/main.rs +++ b/fuel-vm/src/interpreter/executors/main.rs @@ -1,7 +1,10 @@ #[cfg(test)] mod tests; -use alloc::{vec::Vec, vec}; +use alloc::{ + vec, + vec::Vec, +}; use crate::{ checked_transaction::{ diff --git a/fuel-vm/src/interpreter/flow.rs b/fuel-vm/src/interpreter/flow.rs index fd69cd1fca..7e353a0b0a 100644 --- a/fuel-vm/src/interpreter/flow.rs +++ b/fuel-vm/src/interpreter/flow.rs @@ -48,6 +48,8 @@ use crate::{ InterpreterStorage, }, }; +use alloc::vec::Vec; +use core::cmp; use fuel_asm::{ Instruction, PanicInstruction, @@ -72,8 +74,6 @@ use fuel_types::{ ContractId, Word, }; -use core::cmp; -use alloc::vec::Vec; #[cfg(test)] mod jump_tests; diff --git a/fuel-vm/src/interpreter/initialization.rs b/fuel-vm/src/interpreter/initialization.rs index 0966708b57..d413fd521b 100644 --- a/fuel-vm/src/interpreter/initialization.rs +++ b/fuel-vm/src/interpreter/initialization.rs @@ -58,21 +58,18 @@ where // Set heap area self.registers[RegId::HP] = VM_MAX_RAM; - self.push_stack(self.transaction().id(&self.chain_id()).as_ref()) - .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; + self.push_stack(self.transaction().id(&self.chain_id()).as_ref())?; runtime_balances.to_vm(self); let tx_size = self.transaction().size() as Word; self.set_gas(gas_limit); - self.push_stack(&tx_size.to_be_bytes()) - .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; + self.push_stack(&tx_size.to_be_bytes())?; let tx_bytes = self.tx.to_bytes(); - self.push_stack(tx_bytes.as_slice()) - .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; + self.push_stack(tx_bytes.as_slice())?; self.registers[RegId::SP] = self.registers[RegId::SSP]; diff --git a/fuel-vm/src/interpreter/memory.rs b/fuel-vm/src/interpreter/memory.rs index c122476202..3434c3289a 100644 --- a/fuel-vm/src/interpreter/memory.rs +++ b/fuel-vm/src/interpreter/memory.rs @@ -20,11 +20,11 @@ use fuel_types::{ Word, }; +use alloc::boxed::Box; use core::{ ops, ops::Range, }; -use alloc::boxed::Box; pub type Memory = Box<[u8; SIZE]>; diff --git a/fuel-vm/src/interpreter/post_execution.rs b/fuel-vm/src/interpreter/post_execution.rs index 1f54ef1d30..1aac6aa7e9 100644 --- a/fuel-vm/src/interpreter/post_execution.rs +++ b/fuel-vm/src/interpreter/post_execution.rs @@ -44,10 +44,11 @@ where Tx: ExecutableTransaction, { tx.update_outputs(revert, remaining_gas, initial_balances, balances, fee_params, base_asset_id) - .map_err(|e| io::Error::new( - io::ErrorKind::Other, + .map_err(|e| RuntimeError::unexpected_behavior( + format!("a valid VM execution shouldn't result in a state where it can't compute its refund. This is a bug! {e}") - ))?; + ) + )?; Ok(()) } diff --git a/fuel-vm/src/interpreter/receipts.rs b/fuel-vm/src/interpreter/receipts.rs index 0cec263ff7..8ebe0ac5b5 100644 --- a/fuel-vm/src/interpreter/receipts.rs +++ b/fuel-vm/src/interpreter/receipts.rs @@ -125,9 +125,9 @@ mod tests { crypto::ephemeral_merkle_root, interpreter::receipts::ReceiptsCtx, }; + use core::iter; use fuel_tx::Receipt; use fuel_types::canonical::SerializedSize; - use core::iter; fn create_receipt() -> Receipt { Receipt::call( diff --git a/fuel-vm/src/state/debugger.rs b/fuel-vm/src/state/debugger.rs index 7699362edd..46419d8783 100644 --- a/fuel-vm/src/state/debugger.rs +++ b/fuel-vm/src/state/debugger.rs @@ -9,7 +9,7 @@ use fuel_types::{ Word, }; -use alloc::collections::{ +use hashbrown::{ HashMap, HashSet, }; diff --git a/fuel-vm/src/storage/interpreter.rs b/fuel-vm/src/storage/interpreter.rs index 9537c6abe0..545b6645ae 100644 --- a/fuel-vm/src/storage/interpreter.rs +++ b/fuel-vm/src/storage/interpreter.rs @@ -22,11 +22,14 @@ use fuel_types::{ Word, }; -use crate::storage::{ - ContractsAssets, - ContractsInfo, - ContractsRawCode, - ContractsState, +use crate::{ + prelude::InterpreterError, + storage::{ + ContractsAssets, + ContractsInfo, + ContractsRawCode, + ContractsState, + }, }; use alloc::{ borrow::Cow, @@ -48,7 +51,7 @@ pub trait InterpreterStorage: + ContractsAssetsStorage { /// Error implementation for reasons unspecified in the protocol. - type DataError: StdError + Into; + type DataError: Into; // TODO /// Provide the current block height in which the transactions should be /// executed. diff --git a/fuel-vm/src/tests/gas_factor.rs b/fuel-vm/src/tests/gas_factor.rs index 75d9ad877b..8302ed642d 100644 --- a/fuel-vm/src/tests/gas_factor.rs +++ b/fuel-vm/src/tests/gas_factor.rs @@ -1,13 +1,13 @@ use fuel_asm::op; use fuel_vm::prelude::*; +use core::iter; use fuel_tx::{ field::Outputs, ConsensusParameters, FeeParameters, }; use fuel_vm::interpreter::InterpreterParams; -use core::iter; #[test] fn gas_factor_rounds_correctly() { From 926643c67b0e2acf6383abeebddf81d18e27ca64 Mon Sep 17 00:00:00 2001 From: Hannes Karppila Date: Wed, 13 Sep 2023 11:32:16 +0300 Subject: [PATCH 14/44] WIP: before attempting to un-indirect error types --- fuel-vm/src/error.rs | 71 ++++++++++++++----- fuel-vm/src/interpreter/blockchain.rs | 66 +++++++++-------- fuel-vm/src/interpreter/contract.rs | 36 ++++------ fuel-vm/src/interpreter/executors/main.rs | 9 +-- .../src/interpreter/executors/predicate.rs | 3 +- fuel-vm/src/interpreter/flow.rs | 12 ++-- fuel-vm/src/interpreter/initialization.rs | 12 ++-- fuel-vm/src/storage/interpreter.rs | 16 +++-- fuel-vm/src/storage/memory.rs | 10 +-- fuel-vm/src/storage/predicate.rs | 59 +++++++-------- 10 files changed, 157 insertions(+), 137 deletions(-) diff --git a/fuel-vm/src/error.rs b/fuel-vm/src/error.rs index 136a770022..c082218093 100644 --- a/fuel-vm/src/error.rs +++ b/fuel-vm/src/error.rs @@ -28,9 +28,6 @@ pub enum InterpreterError { /// The provided transaction isn't valid. #[error("Failed to check the transaction: {0}")] CheckError(#[from] CheckError), - /// The predicate verification failed. - #[error("Execution error")] - PredicateFailure, /// No transaction was initialized in the interpreter. It cannot provide /// state transitions. #[error("Execution error")] @@ -41,7 +38,7 @@ pub enum InterpreterError { #[error("Execution error")] /// The debug state is not initialized; debug routines can't be called. DebugStateNotInitialized, - /// I/O and OS related errors. + /// VM panics, and I/O and OS related errors during VM execution. #[error("Runtime error: {0}")] RuntimeError(RuntimeError), } @@ -88,7 +85,7 @@ impl From for InterpreterError { fn from(error: RuntimeError) -> Self { match error { RuntimeError::Recoverable(e) => Self::Panic(e), - RuntimeError::Halt => todo!(), // TODO + RuntimeError::Unrecoverable(_) => todo!(), // TODO } } } @@ -99,7 +96,6 @@ impl PartialEq for InterpreterError { (Self::PanicInstruction(s), Self::PanicInstruction(o)) => s == o, (Self::Panic(s), Self::Panic(o)) => s == o, (Self::CheckError(s), Self::CheckError(o)) => s == o, - (Self::PredicateFailure, Self::PredicateFailure) => true, (Self::NoTransactionInitialized, Self::NoTransactionInitialized) => true, (Self::RuntimeError(s), Self::RuntimeError(o)) => todo!(), // TODO @@ -110,19 +106,41 @@ impl PartialEq for InterpreterError { } } +#[derive(Debug, PartialEq)] +/// Unrecoverable error +pub enum Unrecoverable { + /// Invalid interpreter state reached unexpectedly, this is a bug + Bug(Bug), + /// Predicate verification failed + PredicateFailure, +} + +impl fmt::Display for Unrecoverable { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + todo!(); + } +} + +impl From for Unrecoverable { + fn from(bug: Bug) -> Self { + Self::Bug(bug) + } +} + #[derive(Debug)] -#[cfg_attr(feature = "std", derive(thiserror::Error))] /// Runtime error description that should either be specified in the protocol or /// halt the execution. pub enum RuntimeError { - /// Specified error with well-formed fallback strategy. - #[error(transparent)] - Recoverable(#[from] PanicReason), - /// Unspecified error that should halt the execution. - Halt, // TODO + /// Specified error with well-formed fallback strategy, i.e. vm panics. + Recoverable(PanicReason), + /// Unspecified error that should halt the execution, i.e. IO errors. + Unrecoverable(Unrecoverable), } impl RuntimeError { + pub const INVALID_PREDICATE: Self = + Self::Unrecoverable(Unrecoverable::PredicateFailure); + /// Flag whether the error is recoverable. pub const fn is_recoverable(&self) -> bool { matches!(self, Self::Recoverable(_)) @@ -130,21 +148,20 @@ impl RuntimeError { /// Flag whether the error must halt the execution. pub const fn must_halt(&self) -> bool { - matches!(self, Self::Halt) + matches!(self, Self::Unrecoverable(_)) } /// Unexpected behavior occurred pub fn unexpected_behavior(error: E) -> Self { - todo!(); - Self::Halt // TODO: contents + todo!(); // TODO } } impl PartialEq for RuntimeError { fn eq(&self, other: &Self) -> bool { match (self, other) { - (RuntimeError::Recoverable(s), RuntimeError::Recoverable(o)) => s == o, - (RuntimeError::Halt, RuntimeError::Halt) => todo!(), // TODO + (RuntimeError::Recoverable(a), RuntimeError::Recoverable(b)) => a == b, + (RuntimeError::Unrecoverable(a), RuntimeError::Unrecoverable(b)) => a == b, _ => false, } } @@ -154,11 +171,17 @@ impl fmt::Display for RuntimeError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Recoverable(reason) => write!(f, "Recoverable error: {}", reason), - Self::Halt => write!(f, "Unrecoverable error"), + Self::Unrecoverable(err) => write!(f, "Unrecoverable error: {}", err), } } } +impl From for RuntimeError { + fn from(value: PanicReason) -> Self { + Self::Recoverable(value) + } +} + #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] /// Infallible implementation that converts into [`io::Error`]. pub struct Infallible(StdInfallible); @@ -202,6 +225,12 @@ impl From for RuntimeError { } } +impl From for RuntimeError { + fn from(bug: Bug) -> Self { + Self::Unrecoverable(bug.into()) + } +} + /// Predicates checking failed #[derive(Debug, Error)] pub enum PredicateVerificationFailed { @@ -342,6 +371,12 @@ pub struct Bug { bt: backtrace::Backtrace, } +impl PartialEq for Bug { + fn eq(&self, other: &Self) -> bool { + self.id == other.id && self.variant == other.variant + } +} + impl Bug { #[cfg(not(feature = "backtrace"))] /// Report a bug without backtrace data diff --git a/fuel-vm/src/interpreter/blockchain.rs b/fuel-vm/src/interpreter/blockchain.rs index f87639eb4b..974d360777 100644 --- a/fuel-vm/src/interpreter/blockchain.rs +++ b/fuel-vm/src/interpreter/blockchain.rs @@ -46,7 +46,10 @@ use crate::{ receipts::ReceiptsCtx, InputContracts, }, - prelude::Profiler, + prelude::{ + InterpreterError, + Profiler, + }, storage::{ ContractsAssets, ContractsAssetsStorage, @@ -552,7 +555,7 @@ struct BurnCtx<'vm, S> { impl<'vm, S> BurnCtx<'vm, S> where S: ContractsAssetsStorage, - >::Error: Into, + >::Error: Into, { pub(crate) fn burn(self, a: Word, b: Word) -> Result<(), RuntimeError> { let range = internal_contract_bounds(self.context, self.fp)?; @@ -569,9 +572,11 @@ where .checked_sub(a) .ok_or(PanicReason::NotEnoughBalance)?; - self.storage - .merkle_contract_asset_id_balance_insert(contract_id, &asset_id, balance) - .map_err(RuntimeError::from_io)?; + self.storage.merkle_contract_asset_id_balance_insert( + contract_id, + &asset_id, + balance, + )?; let receipt = Receipt::burn(*sub_id, *contract_id, a, *self.pc, *self.is); @@ -593,7 +598,7 @@ struct MintCtx<'vm, S> { impl<'vm, S> MintCtx<'vm, S> where S: ContractsAssetsStorage, - >::Error: Into, + >::Error: Into, { pub(crate) fn mint(self, a: Word, b: Word) -> Result<(), RuntimeError> { let range = internal_contract_bounds(self.context, self.fp)?; @@ -608,9 +613,11 @@ where let balance = balance(self.storage, contract_id, &asset_id)?; let balance = checked_add_word(balance, a)?; - self.storage - .merkle_contract_asset_id_balance_insert(contract_id, &asset_id, balance) - .map_err(RuntimeError::from_io)?; + self.storage.merkle_contract_asset_id_balance_insert( + contract_id, + &asset_id, + balance, + )?; let receipt = Receipt::mint(*sub_id, *contract_id, a, *self.pc, *self.is); @@ -709,7 +716,7 @@ pub(crate) fn coinbase( pc: RegMut, a: Word, ) -> Result<(), RuntimeError> { - let coinbase = storage.coinbase().map_err(RuntimeError::from_io)?; + let coinbase = storage.coinbase()?; try_mem_write(a, coinbase.as_ref(), owner, memory)?; inc_pc(pc) } @@ -738,8 +745,7 @@ impl<'vm, S, I: Iterator> CodeRootCtx<'vm, S, I> { .storage .storage_contract_root(contract_id) .transpose() - .ok_or(PanicReason::ContractNotFound)? - .map_err(RuntimeError::from_io)? + .ok_or(PanicReason::ContractNotFound)?? .into_owned(); try_mem_write(a, root.as_ref(), self.owner, self.memory)?; @@ -769,7 +775,7 @@ impl<'vm, S, I: Iterator> CodeSizeCtx<'vm, S, I> { ) -> Result<(), RuntimeError> where S: StorageSize, - >::Error: Into, + >::Error: Into, { let contract_id = CheckedMemConstLen::<{ ContractId::LEN }>::new(b)?; @@ -817,16 +823,13 @@ pub(crate) fn state_read_word( let key = Bytes32::from_bytes_ref(key.read(memory)); - let value = storage - .merkle_contract_state(contract, key) - .map_err(RuntimeError::from_io)? - .map(|bytes| { - Word::from_be_bytes( - bytes[..8] - .try_into() - .expect("8 bytes can be converted to a Word"), - ) - }); + let value = storage.merkle_contract_state(contract, key)?.map(|bytes| { + Word::from_be_bytes( + bytes[..8] + .try_into() + .expect("8 bytes can be converted to a Word"), + ) + }); *result = value.unwrap_or(0); *got_result = value.is_some() as Word; @@ -858,9 +861,7 @@ pub(crate) fn state_write_word( value[..WORD_SIZE].copy_from_slice(&c.to_be_bytes()); - let result = storage - .merkle_contract_state_insert(contract, key, &value) - .map_err(RuntimeError::from_io)?; + let result = storage.merkle_contract_state_insert(contract, key, &value)?; *exists = result.is_some() as Word; @@ -888,7 +889,7 @@ pub(crate) fn timestamp( struct MessageOutputCtx<'vm, Tx, S> where S: ContractsAssetsStorage + ?Sized, - >::Error: Into, + >::Error: Into, { base_asset_id: AssetId, max_message_data_length: u64, @@ -914,7 +915,7 @@ where impl MessageOutputCtx<'_, Tx, S> where S: ContractsAssetsStorage + ?Sized, - >::Error: Into, + >::Error: Into, { pub(crate) fn message_output(self) -> Result<(), RuntimeError> where @@ -1022,8 +1023,7 @@ fn state_read_qword( let mut all_set = true; let result: Vec = storage - .merkle_contract_state_range(contract_id, origin_key, input.num_slots) - .map_err(RuntimeError::from_io)? + .merkle_contract_state_range(contract_id, origin_key, input.num_slots)? .into_iter() .flat_map(|bytes| match bytes { Some(bytes) => **bytes, @@ -1090,8 +1090,7 @@ fn state_write_qword( .collect(); let any_none = storage - .merkle_contract_state_insert_range(contract_id, destination_key, &values) - .map_err(RuntimeError::from_io)? + .merkle_contract_state_insert_range(contract_id, destination_key, &values)? .is_some(); *result_register = any_none as Word; @@ -1135,8 +1134,7 @@ fn state_clear_qword( Bytes32::from_bytes_ref(input.start_storage_key_memory_range.read(memory)); let all_previously_set = storage - .merkle_contract_state_remove_range(contract_id, start_key, input.num_slots) - .map_err(RuntimeError::from_io)? + .merkle_contract_state_remove_range(contract_id, start_key, input.num_slots)? .is_some(); *result_register = all_previously_set as Word; diff --git a/fuel-vm/src/interpreter/contract.rs b/fuel-vm/src/interpreter/contract.rs index f2a032c2c3..08870b69e9 100644 --- a/fuel-vm/src/interpreter/contract.rs +++ b/fuel-vm/src/interpreter/contract.rs @@ -25,6 +25,7 @@ use crate::{ InputContracts, PanicContext, }, + prelude::InterpreterError, storage::{ ContractsAssets, ContractsAssetsStorage, @@ -135,9 +136,7 @@ where &self, contract: &ContractId, ) -> Result { - self.storage - .storage_contract_exists(contract) - .map_err(RuntimeError::from_io) + self.storage.storage_contract_exists(contract) } } @@ -149,8 +148,7 @@ where S: InterpreterStorage, { storage - .storage_contract(contract) - .map_err(RuntimeError::from_io)? + .storage_contract(contract)? .ok_or_else(|| PanicReason::ContractNotFound.into()) } @@ -171,7 +169,7 @@ impl<'vm, S, I> ContractBalanceCtx<'vm, S, I> { where I: Iterator, S: ContractsAssetsStorage, - >::Error: Into, + >::Error: Into, { let asset_id = CheckedMemConstLen::<{ AssetId::LEN }>::new(b)?; let contract = CheckedMemConstLen::<{ ContractId::LEN }>::new(c)?; @@ -217,7 +215,7 @@ impl<'vm, S, Tx> TransferCtx<'vm, S, Tx> { where Tx: ExecutableTransaction, S: ContractsAssetsStorage, - >::Error: Into, + >::Error: Into, { let amount = transfer_amount; let destination = @@ -289,7 +287,7 @@ impl<'vm, S, Tx> TransferCtx<'vm, S, Tx> { where Tx: ExecutableTransaction, S: ContractsAssetsStorage, - >::Error: Into, + >::Error: Into, { let out_idx = output_index as usize; let to = Address::from(read_bytes(self.memory, recipient_offset)?); @@ -352,11 +350,10 @@ pub(crate) fn contract_size( ) -> Result where S: StorageSize + ?Sized, - >::Error: Into, + >::Error: Into, { Ok(storage - .size_of_value(contract) - .map_err(RuntimeError::from_io)? + .size_of_value(contract)? .ok_or(PanicReason::ContractNotFound)? as Word) } @@ -367,11 +364,10 @@ pub(crate) fn balance( ) -> Result where S: ContractsAssetsStorage + ?Sized, - >::Error: Into, + >::Error: Into, { Ok(storage - .merkle_contract_asset_id_balance(contract, asset_id) - .map_err(RuntimeError::from_io)? + .merkle_contract_asset_id_balance(contract, asset_id)? .unwrap_or_default()) } @@ -384,15 +380,13 @@ pub(crate) fn balance_increase( ) -> Result where S: ContractsAssetsStorage + ?Sized, - >::Error: Into, + >::Error: Into, { let balance = balance(storage, contract, asset_id)?; let balance = balance .checked_add(amount) .ok_or(PanicReason::ArithmeticOverflow)?; - storage - .merkle_contract_asset_id_balance_insert(contract, asset_id, balance) - .map_err(RuntimeError::from_io)?; + storage.merkle_contract_asset_id_balance_insert(contract, asset_id, balance)?; Ok(balance) } @@ -405,14 +399,12 @@ pub(crate) fn balance_decrease( ) -> Result where S: ContractsAssetsStorage + ?Sized, - >::Error: Into, + >::Error: Into, { let balance = balance(storage, contract, asset_id)?; let balance = balance .checked_sub(amount) .ok_or(PanicReason::NotEnoughBalance)?; - storage - .merkle_contract_asset_id_balance_insert(contract, asset_id, balance) - .map_err(RuntimeError::from_io)?; + storage.merkle_contract_asset_id_balance_insert(contract, asset_id, balance)?; Ok(balance) } diff --git a/fuel-vm/src/interpreter/executors/main.rs b/fuel-vm/src/interpreter/executors/main.rs index ad9e8a06c1..b64495e119 100644 --- a/fuel-vm/src/interpreter/executors/main.rs +++ b/fuel-vm/src/interpreter/executors/main.rs @@ -457,18 +457,13 @@ where }; // Prevent redeployment of contracts - if storage - .storage_contract_exists(&id) - .map_err(InterpreterError::from_io)? - { + if storage.storage_contract_exists(&id)? { return Err(InterpreterError::Panic( PanicReason::ContractIdAlreadyDeployed, )) } - storage - .deploy_contract_with_id(salt, storage_slots, &contract, &root, &id) - .map_err(InterpreterError::from_io)?; + storage.deploy_contract_with_id(salt, storage_slots, &contract, &root, &id)?; Self::finalize_outputs( create, fee_params, diff --git a/fuel-vm/src/interpreter/executors/predicate.rs b/fuel-vm/src/interpreter/executors/predicate.rs index 981f5e1d44..0e7f02c2ad 100644 --- a/fuel-vm/src/interpreter/executors/predicate.rs +++ b/fuel-vm/src/interpreter/executors/predicate.rs @@ -3,6 +3,7 @@ use crate::{ prelude::{ ExecutableTransaction, Interpreter, + RuntimeError, }, state::{ ExecuteState, @@ -24,7 +25,7 @@ where let range = self .context .predicate() - .ok_or(InterpreterError::PredicateFailure)? + .ok_or(RuntimeError::INVALID_PREDICATE)? .program() .words(); diff --git a/fuel-vm/src/interpreter/flow.rs b/fuel-vm/src/interpreter/flow.rs index 7e353a0b0a..7b82733d8f 100644 --- a/fuel-vm/src/interpreter/flow.rs +++ b/fuel-vm/src/interpreter/flow.rs @@ -40,6 +40,7 @@ use crate::{ InputContracts, PanicContext, }, + prelude::InterpreterError, profiler::Profiler, storage::{ ContractsAssets, @@ -496,8 +497,8 @@ where + ContractsAssetsStorage + StorageRead + StorageAsRef, - >::Error: Into, - >::Error: Into, + >::Error: Into, + >::Error: Into, { let call = self.memory.call_params.try_from(self.memory.memory)?; let asset_id = self.memory.asset_id.try_from(self.memory.memory)?; @@ -639,7 +640,7 @@ fn write_call_to_memory( ) -> Result where S: StorageSize + StorageRead + StorageAsRef, - >::Error: Into, + >::Error: Into, { let mut code_frame_range = code_mem_range.clone(); // Addition is safe because code size + padding is always less than len @@ -654,8 +655,7 @@ where code_range.shrink_end(frame.code_size_padding() as usize); let bytes_read = storage .storage::() - .read(frame.to(), code_range.write(memory)) - .map_err(RuntimeError::from_io)? + .read(frame.to(), code_range.write(memory))? .ok_or(PanicReason::ContractNotFound)?; if bytes_read as Word != frame.code_size() { return Err(PanicReason::ContractMismatch.into()) @@ -678,7 +678,7 @@ fn call_frame( ) -> Result where S: StorageSize + ?Sized, - >::Error: Into, + >::Error: Into, { let (to, a, b) = call.into_inner(); diff --git a/fuel-vm/src/interpreter/initialization.rs b/fuel-vm/src/interpreter/initialization.rs index d413fd521b..2c3163050f 100644 --- a/fuel-vm/src/interpreter/initialization.rs +++ b/fuel-vm/src/interpreter/initialization.rs @@ -16,6 +16,7 @@ use crate::{ BugId, InterpreterError, }, + prelude::RuntimeError, storage::InterpreterStorage, }; @@ -41,7 +42,7 @@ where initial_balances: InitialBalances, runtime_balances: RuntimeBalances, gas_limit: Word, - ) -> Result<(), InterpreterError> { + ) -> Result<(), RuntimeError> { self.tx = tx; self.initial_balances = initial_balances.clone(); @@ -87,7 +88,7 @@ where context: Context, mut tx: Tx, gas_limit: Word, - ) -> Result<(), InterpreterError> { + ) -> Result<(), RuntimeError> { self.context = context; tx.prepare_init_predicate(); @@ -108,10 +109,7 @@ where /// /// For predicate estimation and verification, check [`Self::init_predicate`] pub fn init_script(&mut self, checked: Checked) -> Result<(), InterpreterError> { - let block_height = self - .storage - .block_height() - .map_err(InterpreterError::from_io)?; + let block_height = self.storage.block_height()?; self.context = Context::Script { block_height }; @@ -125,6 +123,6 @@ where let initial_balances = metadata.balances(); let runtime_balances = initial_balances.try_into()?; - self.init_inner(tx, metadata.balances(), runtime_balances, gas_limit) + Ok(self.init_inner(tx, metadata.balances(), runtime_balances, gas_limit)?) } } diff --git a/fuel-vm/src/storage/interpreter.rs b/fuel-vm/src/storage/interpreter.rs index 545b6645ae..6422a22dc1 100644 --- a/fuel-vm/src/storage/interpreter.rs +++ b/fuel-vm/src/storage/interpreter.rs @@ -23,7 +23,10 @@ use fuel_types::{ }; use crate::{ - prelude::InterpreterError, + prelude::{ + InterpreterError, + RuntimeError, + }, storage::{ ContractsAssets, ContractsInfo, @@ -35,9 +38,12 @@ use alloc::{ borrow::Cow, vec::Vec, }; -use core::ops::{ - Deref, - DerefMut, +use core::{ + fmt, + ops::{ + Deref, + DerefMut, + }, }; /// When this trait is implemented, the underlying interpreter is guaranteed to @@ -51,7 +57,7 @@ pub trait InterpreterStorage: + ContractsAssetsStorage { /// Error implementation for reasons unspecified in the protocol. - type DataError: Into; // TODO + type DataError: Into + fmt::Debug; // TODO /// Provide the current block height in which the transactions should be /// executed. diff --git a/fuel-vm/src/storage/memory.rs b/fuel-vm/src/storage/memory.rs index 20eed62d1d..0a7b0bbe12 100644 --- a/fuel-vm/src/storage/memory.rs +++ b/fuel-vm/src/storage/memory.rs @@ -205,11 +205,11 @@ impl StorageRead for MemoryStorage { key: &ContractId, buf: &mut [u8], ) -> Result, Self::Error> { - Ok(self - .memory - .contracts - .get(key) - .and_then(|c| c.as_ref().read(buf).ok())) + Ok(self.memory.contracts.get(key).and_then(|c| { + let len = buf.len().min(c.as_ref().len()); + buf.copy_from_slice(&c.as_ref()[..len]); + Some(buf.len()) + })) } fn read_alloc(&self, key: &ContractId) -> Result>, Self::Error> { diff --git a/fuel-vm/src/storage/predicate.rs b/fuel-vm/src/storage/predicate.rs index db26aa5a8e..a297ebe2d8 100644 --- a/fuel-vm/src/storage/predicate.rs +++ b/fuel-vm/src/storage/predicate.rs @@ -5,6 +5,7 @@ use alloc::{ use crate::{ error::InterpreterError, + prelude::RuntimeError, storage::InterpreterStorage, }; @@ -40,17 +41,17 @@ use super::{ pub struct PredicateStorage; impl StorageInspect for PredicateStorage { - type Error = InterpreterError; + type Error = RuntimeError; fn get( &self, _key: &Type::Key, - ) -> Result>, InterpreterError> { - Err(InterpreterError::PredicateFailure) + ) -> Result>, RuntimeError> { + Err(RuntimeError::INVALID_PREDICATE) } - fn contains_key(&self, _key: &Type::Key) -> Result { - Err(InterpreterError::PredicateFailure) + fn contains_key(&self, _key: &Type::Key) -> Result { + Err(RuntimeError::INVALID_PREDICATE) } } @@ -59,24 +60,21 @@ impl StorageMutate for PredicateStorage { &mut self, _key: &Type::Key, _value: &Type::Value, - ) -> Result, InterpreterError> { - Err(InterpreterError::PredicateFailure) + ) -> Result, RuntimeError> { + Err(RuntimeError::INVALID_PREDICATE) } fn remove( &mut self, _key: &Type::Key, - ) -> Result, InterpreterError> { - Err(InterpreterError::PredicateFailure) + ) -> Result, RuntimeError> { + Err(RuntimeError::INVALID_PREDICATE) } } impl StorageSize for PredicateStorage { - fn size_of_value( - &self, - _key: &ContractId, - ) -> Result, InterpreterError> { - Err(InterpreterError::PredicateFailure) + fn size_of_value(&self, _key: &ContractId) -> Result, RuntimeError> { + Err(RuntimeError::INVALID_PREDICATE) } } @@ -86,45 +84,42 @@ impl StorageRead for PredicateStorage { _key: &::Key, _buf: &mut [u8], ) -> Result, Self::Error> { - Err(InterpreterError::PredicateFailure) + Err(RuntimeError::INVALID_PREDICATE) } fn read_alloc( &self, _key: &::Key, ) -> Result>, Self::Error> { - Err(InterpreterError::PredicateFailure) + Err(RuntimeError::INVALID_PREDICATE) } } impl MerkleRootStorage for PredicateStorage { - fn root(&self, _parent: &Key) -> Result { - Err(InterpreterError::PredicateFailure) + fn root(&self, _parent: &Key) -> Result { + Err(RuntimeError::INVALID_PREDICATE) } } impl ContractsAssetsStorage for PredicateStorage {} impl InterpreterStorage for PredicateStorage { - type DataError = InterpreterError; + type DataError = RuntimeError; - fn block_height(&self) -> Result { - Err(InterpreterError::PredicateFailure) + fn block_height(&self) -> Result { + Err(RuntimeError::INVALID_PREDICATE) } fn timestamp(&self, _height: BlockHeight) -> Result { - Err(InterpreterError::PredicateFailure) + Err(RuntimeError::INVALID_PREDICATE) } - fn block_hash( - &self, - _block_height: BlockHeight, - ) -> Result { - Err(InterpreterError::PredicateFailure) + fn block_hash(&self, _block_height: BlockHeight) -> Result { + Err(RuntimeError::INVALID_PREDICATE) } - fn coinbase(&self) -> Result { - Err(InterpreterError::PredicateFailure) + fn coinbase(&self) -> Result { + Err(RuntimeError::INVALID_PREDICATE) } fn merkle_contract_state_range( @@ -133,7 +128,7 @@ impl InterpreterStorage for PredicateStorage { _start_key: &Bytes32, _range: Word, ) -> Result>>, Self::DataError> { - Err(InterpreterError::PredicateFailure) + Err(RuntimeError::INVALID_PREDICATE) } fn merkle_contract_state_insert_range( @@ -142,7 +137,7 @@ impl InterpreterStorage for PredicateStorage { _start_key: &Bytes32, _values: &[Bytes32], ) -> Result, Self::DataError> { - Err(InterpreterError::PredicateFailure) + Err(RuntimeError::INVALID_PREDICATE) } fn merkle_contract_state_remove_range( @@ -151,6 +146,6 @@ impl InterpreterStorage for PredicateStorage { _start_key: &Bytes32, _range: Word, ) -> Result, Self::DataError> { - Err(InterpreterError::PredicateFailure) + Err(RuntimeError::INVALID_PREDICATE) } } From c0f193d3c0ae6b04b69d06cb2a3873e85c83902d Mon Sep 17 00:00:00 2001 From: Hannes Karppila Date: Wed, 13 Sep 2023 14:36:53 +0300 Subject: [PATCH 15/44] Error type rework --- fuel-merkle/Cargo.toml | 2 +- fuel-merkle/src/binary/merkle_tree.rs | 21 +- fuel-merkle/src/common/node.rs | 13 +- fuel-merkle/src/common/path_iterator.rs | 3 - fuel-merkle/src/common/position.rs | 3 - fuel-merkle/src/common/storage_map.rs | 14 +- fuel-merkle/src/sparse/branch.rs | 3 +- fuel-merkle/src/sparse/in_memory.rs | 21 +- fuel-merkle/src/sparse/merkle_tree.rs | 33 +- fuel-merkle/src/sparse/node.rs | 6 +- fuel-merkle/src/storage.rs | 5 +- fuel-merkle/src/sum/merkle_tree.rs | 5 +- fuel-storage/Cargo.toml | 7 + fuel-storage/src/impls.rs | 64 ++-- fuel-storage/src/lib.rs | 46 ++- fuel-vm/Cargo.toml | 9 +- fuel-vm/src/arith.rs | 54 --- fuel-vm/src/error.rs | 312 +++++++++--------- fuel-vm/src/interpreter/blockchain.rs | 20 +- fuel-vm/src/interpreter/contract.rs | 16 +- fuel-vm/src/interpreter/diff.rs | 26 +- fuel-vm/src/interpreter/diff/storage.rs | 35 +- fuel-vm/src/interpreter/executors/main.rs | 8 +- .../src/interpreter/executors/predicate.rs | 8 +- fuel-vm/src/interpreter/flow.rs | 25 +- fuel-vm/src/interpreter/gas.rs | 16 +- fuel-vm/src/interpreter/gas/tests.rs | 2 +- fuel-vm/src/interpreter/initialization.rs | 7 +- fuel-vm/src/interpreter/post_execution.rs | 17 +- fuel-vm/src/lib.rs | 2 - fuel-vm/src/storage/interpreter.rs | 101 +++--- fuel-vm/src/storage/memory.rs | 74 ++--- fuel-vm/src/storage/predicate.rs | 71 ++-- 33 files changed, 483 insertions(+), 566 deletions(-) diff --git a/fuel-merkle/Cargo.toml b/fuel-merkle/Cargo.toml index 3836f08428..eda4ed64a4 100644 --- a/fuel-merkle/Cargo.toml +++ b/fuel-merkle/Cargo.toml @@ -29,7 +29,7 @@ thiserror = "1.0" [features] default = ["std"] -std = ["dep:thiserror", "digest/default", "hex/default", "sha2/default"] +std = ["dep:thiserror", "fuel-storage/std", "digest/default", "hex/default", "sha2/default"] test-helpers = [] [[test]] diff --git a/fuel-merkle/src/binary/merkle_tree.rs b/fuel-merkle/src/binary/merkle_tree.rs index 2b92b2c631..e4909f5089 100644 --- a/fuel-merkle/src/binary/merkle_tree.rs +++ b/fuel-merkle/src/binary/merkle_tree.rs @@ -23,10 +23,11 @@ use crate::{ use alloc::vec::Vec; use core::marker::PhantomData; +use fuel_storage::StorageError; #[derive(Debug, Clone)] #[cfg_attr(feature = "std", derive(thiserror::Error))] -pub enum MerkleTreeError { +pub enum MerkleTreeError { #[cfg_attr(feature = "std", error("proof index {0} is not valid"))] InvalidProofIndex(u64), @@ -40,8 +41,8 @@ pub enum MerkleTreeError { StorageError(StorageError), } -impl From for MerkleTreeError { - fn from(err: StorageError) -> MerkleTreeError { +impl From for MerkleTreeError { + fn from(err: StorageError) -> MerkleTreeError { MerkleTreeError::StorageError(err) } } @@ -130,10 +131,10 @@ impl MerkleTree { } } -impl MerkleTree +impl MerkleTree where TableType: Mappable, - StorageType: StorageInspect, + StorageType: StorageInspect, { pub fn new(storage: StorageType) -> Self { Self { @@ -147,7 +148,7 @@ where pub fn load( storage: StorageType, leaves_count: u64, - ) -> Result> { + ) -> Result { let mut tree = Self { storage, head: None, @@ -163,7 +164,7 @@ where pub fn prove( &self, proof_index: u64, - ) -> Result<(Bytes32, ProofSet), MerkleTreeError> { + ) -> Result<(Bytes32, ProofSet), MerkleTreeError> { if proof_index + 1 > self.leaves_count { return Err(MerkleTreeError::InvalidProofIndex(proof_index)) } @@ -291,7 +292,7 @@ where /// /// By excluding the root position `07`, we have established the set of /// side positions `03`, `09`, and `12`, matching our set of MMR peaks. - fn build(&mut self) -> Result<(), MerkleTreeError> { + fn build(&mut self) -> Result<(), MerkleTreeError> { let mut current_head = None; let peaks = &self.peak_positions(); for peak in peaks.iter() { @@ -312,10 +313,10 @@ where } } -impl MerkleTree +impl MerkleTree where TableType: Mappable, - StorageType: StorageMutate, + StorageType: StorageMutate, { pub fn push(&mut self, data: &[u8]) -> Result<(), StorageError> { let node = Node::create_leaf(self.leaves_count, data); diff --git a/fuel-merkle/src/common/node.rs b/fuel-merkle/src/common/node.rs index 27f36e3fee..d713ac92cf 100644 --- a/fuel-merkle/src/common/node.rs +++ b/fuel-merkle/src/common/node.rs @@ -1,6 +1,9 @@ -use crate::common::{ - Bytes32, - Bytes8, +use crate::{ + common::{ + Bytes32, + Bytes8, + }, + sparse::StorageNodeError, }; use alloc::string::String; @@ -29,14 +32,12 @@ pub trait Node { } pub trait ParentNode: Sized + Node { - type Error; - fn left_child(&self) -> ChildResult; fn right_child(&self) -> ChildResult; } #[allow(type_alias_bounds)] -pub type ChildResult = Result>; +pub type ChildResult = Result>; #[derive(Debug, Clone)] #[cfg_attr(feature = "std", derive(thiserror::Error))] diff --git a/fuel-merkle/src/common/path_iterator.rs b/fuel-merkle/src/common/path_iterator.rs index 671e5fac7e..23875b8cc2 100644 --- a/fuel-merkle/src/common/path_iterator.rs +++ b/fuel-merkle/src/common/path_iterator.rs @@ -211,7 +211,6 @@ mod test { Bytes8, }; use alloc::vec::Vec; - use core::convert::Infallible; #[derive(Debug, Clone, PartialEq)] struct TestNode { @@ -273,8 +272,6 @@ mod test { } impl ParentNode for TestNode { - type Error = Infallible; - fn left_child(&self) -> ChildResult { Ok(TestNode::child(self, -1)) } diff --git a/fuel-merkle/src/common/position.rs b/fuel-merkle/src/common/position.rs index 014fa44194..24591a6a88 100644 --- a/fuel-merkle/src/common/position.rs +++ b/fuel-merkle/src/common/position.rs @@ -7,7 +7,6 @@ use crate::common::{ Bytes8, PositionPath, }; -use core::convert::Infallible; /// # Position /// @@ -273,8 +272,6 @@ impl Node for Position { } impl ParentNode for Position { - type Error = Infallible; - fn left_child(&self) -> ChildResult { Ok(Position::left_child(*self)) } diff --git a/fuel-merkle/src/common/storage_map.rs b/fuel-merkle/src/common/storage_map.rs index 3c39e26822..2b5362e84c 100644 --- a/fuel-merkle/src/common/storage_map.rs +++ b/fuel-merkle/src/common/storage_map.rs @@ -8,6 +8,7 @@ use crate::{ }; use alloc::borrow::Cow; +use fuel_storage::StorageError; use hashbrown::HashMap; #[derive(Debug, Clone)] @@ -44,15 +45,16 @@ where Type::Key: Eq + core::hash::Hash, Type::OwnedKey: Eq + core::hash::Hash + core::borrow::Borrow, { - type Error = core::convert::Infallible; - - fn get(&self, key: &Type::Key) -> Result>, Self::Error> { + fn get( + &self, + key: &Type::Key, + ) -> Result>, StorageError> { let result = self.map.get(key); let value = result.map(Cow::Borrowed); Ok(value) } - fn contains_key(&self, key: &Type::Key) -> Result { + fn contains_key(&self, key: &Type::Key) -> Result { let contains = self.map.contains_key(key); Ok(contains) } @@ -68,7 +70,7 @@ where &mut self, key: &Type::Key, value: &Type::Value, - ) -> Result, Self::Error> { + ) -> Result, StorageError> { let previous = self.map.remove(key); self.map @@ -79,7 +81,7 @@ where fn remove( &mut self, key: &Type::Key, - ) -> Result, Self::Error> { + ) -> Result, StorageError> { let value = self.map.remove(key); Ok(value) } diff --git a/fuel-merkle/src/sparse/branch.rs b/fuel-merkle/src/sparse/branch.rs index 0abda4d262..cc7f0fd6e9 100644 --- a/fuel-merkle/src/sparse/branch.rs +++ b/fuel-merkle/src/sparse/branch.rs @@ -10,6 +10,7 @@ use crate::{ }; use fuel_storage::{ Mappable, + StorageError, StorageMutate, }; @@ -33,7 +34,7 @@ pub(crate) fn merge_branches( storage: &mut Storage, mut left_branch: Branch, mut right_branch: Branch, -) -> Result +) -> Result where Storage: StorageMutate, Table: Mappable, diff --git a/fuel-merkle/src/sparse/in_memory.rs b/fuel-merkle/src/sparse/in_memory.rs index e6202ff7be..9271e54e61 100644 --- a/fuel-merkle/src/sparse/in_memory.rs +++ b/fuel-merkle/src/sparse/in_memory.rs @@ -18,6 +18,7 @@ use alloc::{ borrow::Cow, vec::Vec, }; +use fuel_storage::StorageError; /// The table of the Sparse Merkle tree's nodes. [`MerkleTree`] works with it as a sparse /// merkle tree, where the storage key is `Bytes32` and the value is the @@ -82,13 +83,11 @@ impl MerkleTree { struct EmptyStorage; impl StorageInspect for EmptyStorage { - type Error = core::convert::Infallible; - - fn get(&self, _: &Bytes32) -> Result>, Self::Error> { + fn get(&self, _: &Bytes32) -> Result>, StorageError> { Ok(None) } - fn contains_key(&self, _: &Bytes32) -> Result { + fn contains_key(&self, _: &Bytes32) -> Result { Ok(false) } } @@ -98,11 +97,11 @@ impl MerkleTree { &mut self, _: &Bytes32, _: &Primitive, - ) -> Result, Self::Error> { + ) -> Result, StorageError> { Ok(None) } - fn remove(&mut self, _: &Bytes32) -> Result, Self::Error> { + fn remove(&mut self, _: &Bytes32) -> Result, StorageError> { Ok(None) } } @@ -131,13 +130,11 @@ impl MerkleTree { } impl StorageInspect for VectorStorage { - type Error = core::convert::Infallible; - - fn get(&self, _: &Bytes32) -> Result>, Self::Error> { + fn get(&self, _: &Bytes32) -> Result>, StorageError> { unimplemented!("Read operation is not supported") } - fn contains_key(&self, _: &Bytes32) -> Result { + fn contains_key(&self, _: &Bytes32) -> Result { unimplemented!("Read operation is not supported") } } @@ -147,12 +144,12 @@ impl MerkleTree { &mut self, key: &Bytes32, value: &Primitive, - ) -> Result, Self::Error> { + ) -> Result, StorageError> { self.storage.push((*key, *value)); Ok(None) } - fn remove(&mut self, _: &Bytes32) -> Result, Self::Error> { + fn remove(&mut self, _: &Bytes32) -> Result, StorageError> { unimplemented!("Remove operation is not supported") } } diff --git a/fuel-merkle/src/sparse/merkle_tree.rs b/fuel-merkle/src/sparse/merkle_tree.rs index e9e0f84a91..337ee72f34 100644 --- a/fuel-merkle/src/sparse/merkle_tree.rs +++ b/fuel-merkle/src/sparse/merkle_tree.rs @@ -29,10 +29,11 @@ use core::{ iter, marker::PhantomData, }; +use fuel_storage::StorageError; #[derive(Debug, Clone)] #[cfg_attr(feature = "std", derive(thiserror::Error))] -pub enum MerkleTreeError { +pub enum MerkleTreeError { #[cfg_attr( feature = "std", error("cannot load node with key {}; the key is not found in storage", hex::encode(.0)) @@ -46,11 +47,11 @@ pub enum MerkleTreeError { DeserializeError(DeserializeError), #[cfg_attr(feature = "std", error(transparent))] - ChildError(ChildError>), + ChildError(ChildError), } -impl From for MerkleTreeError { - fn from(err: StorageError) -> MerkleTreeError { +impl From for MerkleTreeError { + fn from(err: StorageError) -> MerkleTreeError { MerkleTreeError::StorageError(err) } } @@ -139,10 +140,10 @@ impl MerkleTree { } } -impl MerkleTree +impl MerkleTree where TableType: Mappable, - StorageType: StorageInspect, + StorageType: StorageInspect, { pub fn new(storage: StorageType) -> Self { Self { @@ -152,10 +153,7 @@ where } } - pub fn load( - storage: StorageType, - root: &Bytes32, - ) -> Result> { + pub fn load(storage: StorageType, root: &Bytes32) -> Result { if root == Self::empty_root() { let tree = Self::new(storage); Ok(tree) @@ -180,7 +178,7 @@ where fn path_set( &self, leaf_key: Bytes32, - ) -> Result<(Vec, Vec), MerkleTreeError> { + ) -> Result<(Vec, Vec), MerkleTreeError> { let root_node = self.root_node().clone(); let root_storage_node = StorageNode::new(&self.storage, root_node); let (mut path_nodes, mut side_nodes): (Vec, Vec) = root_storage_node @@ -191,7 +189,7 @@ where side_node.map_err(MerkleTreeError::ChildError)?.into_node(), )) }) - .collect::, MerkleTreeError>>()? + .collect::, MerkleTreeError>>()? .into_iter() .unzip(); path_nodes.reverse(); @@ -203,10 +201,10 @@ where } } -impl MerkleTree +impl MerkleTree where TableType: Mappable, - StorageType: StorageMutate, + StorageType: StorageMutate, { /// Build a sparse Merkle tree from a set of key-value pairs. This is /// equivalent to creating an empty sparse Merkle tree and sequentially @@ -357,7 +355,7 @@ where &mut self, key: MerkleTreeKey, data: &[u8], - ) -> Result<(), MerkleTreeError> { + ) -> Result<(), MerkleTreeError> { if data.is_empty() { // If the data is empty, this signifies a delete operation for the // given key. @@ -384,10 +382,7 @@ where Ok(()) } - pub fn delete( - &mut self, - key: MerkleTreeKey, - ) -> Result<(), MerkleTreeError> { + pub fn delete(&mut self, key: MerkleTreeKey) -> Result<(), MerkleTreeError> { if self.root() == *Self::empty_root() { // The zero root signifies that all leaves are empty, including the // given key. diff --git a/fuel-merkle/src/sparse/node.rs b/fuel-merkle/src/sparse/node.rs index c5616970a3..6e74b9cab5 100644 --- a/fuel-merkle/src/sparse/node.rs +++ b/fuel-merkle/src/sparse/node.rs @@ -1,3 +1,5 @@ +use fuel_storage::StorageError; + use crate::{ common::{ error::DeserializeError, @@ -330,7 +332,7 @@ impl NodeTrait for StorageNode<'_, TableType, StorageTyp #[derive(Debug, Clone)] #[cfg_attr(feature = "std", derive(thiserror::Error))] -pub enum StorageNodeError { +pub enum StorageNodeError { #[cfg_attr(feature = "std", error(transparent))] StorageError(StorageError), #[cfg_attr(feature = "std", error(transparent))] @@ -342,8 +344,6 @@ where StorageType: StorageInspect, TableType: Mappable, { - type Error = StorageNodeError; - fn left_child(&self) -> ChildResult { if self.is_leaf() { return Err(ChildError::NodeIsLeaf) diff --git a/fuel-merkle/src/storage.rs b/fuel-merkle/src/storage.rs index d6ecd9e28f..725e89bc2c 100644 --- a/fuel-merkle/src/storage.rs +++ b/fuel-merkle/src/storage.rs @@ -1,5 +1,4 @@ use alloc::borrow::Cow; -use core::convert::Infallible; // Re-export fuel-storage traits pub use fuel_storage::{ @@ -24,7 +23,7 @@ pub trait StorageMutateInfallible { impl StorageInspectInfallible for S where - S: StorageInspect, + S: StorageInspect, Type: Mappable, { fn get(&self, key: &Type::Key) -> Option> { @@ -40,7 +39,7 @@ where impl StorageMutateInfallible for S where - S: StorageMutate, + S: StorageMutate, Type: Mappable, { fn insert( diff --git a/fuel-merkle/src/sum/merkle_tree.rs b/fuel-merkle/src/sum/merkle_tree.rs index 03055374ce..71d2a05d5f 100644 --- a/fuel-merkle/src/sum/merkle_tree.rs +++ b/fuel-merkle/src/sum/merkle_tree.rs @@ -11,6 +11,7 @@ use crate::{ use fuel_storage::{ Mappable, + StorageError, StorageMutate, }; @@ -61,10 +62,10 @@ impl MerkleTree { } } -impl MerkleTree +impl MerkleTree where TableType: Mappable, - StorageType: StorageMutate, + StorageType: StorageMutate, { pub fn new(storage: StorageType) -> Self { Self { diff --git a/fuel-storage/Cargo.toml b/fuel-storage/Cargo.toml index e80bac6ed8..069594ce4a 100644 --- a/fuel-storage/Cargo.toml +++ b/fuel-storage/Cargo.toml @@ -9,3 +9,10 @@ keywords = ["blockchain", "cryptocurrencies", "fuel", "fuel-vm"] license = "Apache-2.0" repository = { workspace = true } description = "Storage traits for Fuel storage-backed data structures." + +[dependencies] +thiserror = { version = "1.0", optional = true } + +[features] +default = ["std"] +std = ["thiserror"] diff --git a/fuel-storage/src/impls.rs b/fuel-storage/src/impls.rs index 92ddfeb5cd..ffa39b3c26 100644 --- a/fuel-storage/src/impls.rs +++ b/fuel-storage/src/impls.rs @@ -2,6 +2,7 @@ use crate::{ Mappable, MerkleRoot, MerkleRootStorage, + StorageError, StorageInspect, StorageMut, StorageMutate, @@ -18,16 +19,14 @@ use alloc::{ impl<'a, T: StorageInspect + ?Sized, Type: Mappable> StorageInspect for &'a T { - type Error = T::Error; - fn get( &self, key: &Type::Key, - ) -> Result>, Self::Error> { + ) -> Result>, StorageError> { >::get(self, key) } - fn contains_key(&self, key: &Type::Key) -> Result { + fn contains_key(&self, key: &Type::Key) -> Result { >::contains_key(self, key) } } @@ -35,16 +34,14 @@ impl<'a, T: StorageInspect + ?Sized, Type: Mappable> StorageInspect impl<'a, T: StorageInspect + ?Sized, Type: Mappable> StorageInspect for &'a mut T { - type Error = T::Error; - fn get( &self, key: &Type::Key, - ) -> Result>, Self::Error> { + ) -> Result>, StorageError> { >::get(self, key) } - fn contains_key(&self, key: &Type::Key) -> Result { + fn contains_key(&self, key: &Type::Key) -> Result { >::contains_key(self, key) } } @@ -56,14 +53,14 @@ impl<'a, T: StorageMutate + ?Sized, Type: Mappable> StorageMutate &mut self, key: &Type::Key, value: &Type::Value, - ) -> Result, Self::Error> { + ) -> Result, StorageError> { >::insert(self, key, value) } fn remove( &mut self, key: &Type::Key, - ) -> Result, Self::Error> { + ) -> Result, StorageError> { >::remove(self, key) } } @@ -72,7 +69,7 @@ impl<'a, T: StorageSize + ?Sized, Type: Mappable> StorageSize for &' fn size_of_value( &self, key: &::Key, - ) -> Result, Self::Error> { + ) -> Result, StorageError> { >::size_of_value(self, key) } } @@ -81,7 +78,7 @@ impl<'a, T: StorageSize + ?Sized, Type: Mappable> StorageSize for &' fn size_of_value( &self, key: &::Key, - ) -> Result, Self::Error> { + ) -> Result, StorageError> { >::size_of_value(self, key) } } @@ -93,14 +90,14 @@ impl<'a, T: StorageRead + StorageSize + ?Sized, Type: Mappable> &self, key: &::Key, buf: &mut [u8], - ) -> Result, Self::Error> { + ) -> Result, StorageError> { >::read(self, key, buf) } fn read_alloc( &self, key: &::Key, - ) -> Result>, Self::Error> { + ) -> Result>, StorageError> { >::read_alloc(self, key) } } @@ -112,14 +109,14 @@ impl<'a, T: StorageRead + StorageSize + ?Sized, Type: Mappable> &self, key: &::Key, buf: &mut [u8], - ) -> Result, Self::Error> { + ) -> Result, StorageError> { >::read(self, key, buf) } fn read_alloc( &self, key: &::Key, - ) -> Result>, Self::Error> { + ) -> Result>, StorageError> { >::read_alloc(self, key) } } @@ -127,7 +124,7 @@ impl<'a, T: StorageRead + StorageSize + ?Sized, Type: Mappable> impl<'a, T: MerkleRootStorage + ?Sized, Key, Type: Mappable> MerkleRootStorage for &'a mut T { - fn root(&self, key: &Key) -> Result { + fn root(&self, key: &Key) -> Result { >::root(self, key) } } @@ -137,19 +134,19 @@ impl<'a, T: StorageInspect, Type: Mappable> StorageRef<'a, T, Type> { pub fn get( self, key: &Type::Key, - ) -> Result>, T::Error> { + ) -> Result>, StorageError> { self.0.get(key) } #[inline(always)] - pub fn contains_key(self, key: &Type::Key) -> Result { + pub fn contains_key(self, key: &Type::Key) -> Result { self.0.contains_key(key) } } impl<'a, T, Type: Mappable> StorageRef<'a, T, Type> { #[inline(always)] - pub fn root(self, key: &Key) -> Result + pub fn root(self, key: &Key) -> Result where T: MerkleRootStorage, { @@ -163,7 +160,7 @@ impl<'a, T: StorageRead, Type: Mappable> StorageRef<'a, T, Type> { &self, key: &::Key, buf: &mut [u8], - ) -> Result, T::Error> { + ) -> Result, StorageError> { self.0.read(key, buf) } @@ -171,7 +168,7 @@ impl<'a, T: StorageRead, Type: Mappable> StorageRef<'a, T, Type> { pub fn read_alloc( &self, key: &::Key, - ) -> Result>, T::Error> { + ) -> Result>, StorageError> { self.0.read_alloc(key) } } @@ -181,14 +178,14 @@ impl<'a, T: StorageInspect, Type: Mappable> StorageMut<'a, T, Type> { pub fn get( self, key: &Type::Key, - ) -> Result>, T::Error> { + ) -> Result>, StorageError> { // Workaround, because compiler doesn't convert the lifetime to `'a` by default. let self_: &'a T = self.0; self_.get(key) } #[inline(always)] - pub fn contains_key(self, key: &Type::Key) -> Result { + pub fn contains_key(self, key: &Type::Key) -> Result { self.0.contains_key(key) } } @@ -199,19 +196,22 @@ impl<'a, T: StorageMutate, Type: Mappable> StorageMut<'a, T, Type> { self, key: &Type::Key, value: &Type::Value, - ) -> Result, T::Error> { + ) -> Result, StorageError> { self.0.insert(key, value) } #[inline(always)] - pub fn remove(self, key: &Type::Key) -> Result, T::Error> { + pub fn remove( + self, + key: &Type::Key, + ) -> Result, StorageError> { self.0.remove(key) } } impl<'a, T, Type: Mappable> StorageMut<'a, T, Type> { #[inline(always)] - pub fn root(self, key: &Key) -> Result + pub fn root(self, key: &Key) -> Result where T: MerkleRootStorage, { @@ -221,7 +221,11 @@ impl<'a, T, Type: Mappable> StorageMut<'a, T, Type> { impl<'a, T: StorageWrite, Type: Mappable> StorageMut<'a, T, Type> { #[inline(always)] - pub fn write(&mut self, key: &Type::Key, buf: Vec) -> Result { + pub fn write( + &mut self, + key: &Type::Key, + buf: Vec, + ) -> Result { self.0.write(key, buf) } @@ -230,7 +234,7 @@ impl<'a, T: StorageWrite, Type: Mappable> StorageMut<'a, T, Type> { &mut self, key: &Type::Key, buf: Vec, - ) -> Result<(usize, Option>), T::Error> + ) -> Result<(usize, Option>), StorageError> where T: StorageSize, { @@ -238,7 +242,7 @@ impl<'a, T: StorageWrite, Type: Mappable> StorageMut<'a, T, Type> { } #[inline(always)] - pub fn take(&mut self, key: &Type::Key) -> Result>, T::Error> { + pub fn take(&mut self, key: &Type::Key) -> Result>, StorageError> { self.0.take(key) } } diff --git a/fuel-storage/src/lib.rs b/fuel-storage/src/lib.rs index d0cfa86985..bed8244386 100644 --- a/fuel-storage/src/lib.rs +++ b/fuel-storage/src/lib.rs @@ -1,4 +1,4 @@ -#![no_std] +#![cfg_attr(not(feature = "std"), no_std)] #![deny(unsafe_code)] #![deny(unused_crate_dependencies)] @@ -52,13 +52,12 @@ pub trait Mappable { /// /// Generic should implement [`Mappable`] trait with all storage type information. pub trait StorageInspect { - type Error; - /// Retrieve `Cow` such as `Key->Value`. - fn get(&self, key: &Type::Key) -> Result>, Self::Error>; + fn get(&self, key: &Type::Key) + -> Result>, StorageError>; /// Return `true` if there is a `Key` mapping to a value in the storage. - fn contains_key(&self, key: &Type::Key) -> Result; + fn contains_key(&self, key: &Type::Key) -> Result; } /// Base storage trait for Fuel infrastructure. @@ -73,7 +72,7 @@ pub trait StorageMutate: StorageInspect { &mut self, key: &Type::Key, value: &Type::Value, - ) -> Result, Self::Error>; + ) -> Result, StorageError>; /// Remove `Key->Value` mapping from the storage. /// @@ -82,7 +81,7 @@ pub trait StorageMutate: StorageInspect { fn remove( &mut self, key: &Type::Key, - ) -> Result, Self::Error>; + ) -> Result, StorageError>; } /// Base storage trait for Fuel infrastructure. @@ -92,7 +91,7 @@ pub trait StorageMutate: StorageInspect { /// copying the value into a buffer. pub trait StorageSize: StorageInspect { /// Return the number of bytes stored at this key. - fn size_of_value(&self, key: &Type::Key) -> Result, Self::Error>; + fn size_of_value(&self, key: &Type::Key) -> Result, StorageError>; } /// Base storage trait for Fuel infrastructure. @@ -109,13 +108,16 @@ pub trait StorageRead: StorageInspect + StorageSize /// /// Returns None if the value does not exist. /// Otherwise, returns the number of bytes read. - fn read(&self, key: &Type::Key, buf: &mut [u8]) - -> Result, Self::Error>; + fn read( + &self, + key: &Type::Key, + buf: &mut [u8], + ) -> Result, StorageError>; /// Same as `read` but allocates a new buffer and returns it. /// /// Checks the size of the value and allocates a buffer of that size. - fn read_alloc(&self, key: &Type::Key) -> Result>, Self::Error>; + fn read_alloc(&self, key: &Type::Key) -> Result>, StorageError>; } /// Base storage trait for Fuel infrastructure. @@ -131,7 +133,7 @@ pub trait StorageWrite: StorageMutate { /// Does not perform any serialization. /// /// Returns the number of bytes written. - fn write(&mut self, key: &Type::Key, buf: Vec) -> Result; + fn write(&mut self, key: &Type::Key, buf: Vec) -> Result; /// Write the value to the given key from the provided buffer and /// return the previous value if it existed. @@ -143,12 +145,12 @@ pub trait StorageWrite: StorageMutate { &mut self, key: &Type::Key, buf: Vec, - ) -> Result<(usize, Option>), Self::Error> + ) -> Result<(usize, Option>), StorageError> where Self: StorageSize; /// Removes a value from the storage and returning it without deserializing it. - fn take(&mut self, key: &Type::Key) -> Result>, Self::Error>; + fn take(&mut self, key: &Type::Key) -> Result>, StorageError>; } /// Returns the merkle root for the `StorageType` per merkle `Key`. The type should @@ -162,7 +164,7 @@ where /// /// The cryptographic primitive is an arbitrary choice of the implementor and this /// trait won't impose any restrictions to that. - fn root(&self, key: &Key) -> Result; + fn root(&self, key: &Key) -> Result; } /// The wrapper around the storage that supports only methods from `StorageInspect`. @@ -286,3 +288,17 @@ pub trait StorageAsMut { } impl StorageAsMut for T {} + +/// Wraps around possible errors that can occur during storage operations. +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "std", derive(thiserror::Error))] +pub enum StorageError { + /// Storage is unavailable in predicate context + Unavailable, +} + +impl core::fmt::Display for StorageError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "Storage error") + } +} diff --git a/fuel-vm/Cargo.toml b/fuel-vm/Cargo.toml index affead9774..7af2704845 100644 --- a/fuel-vm/Cargo.toml +++ b/fuel-vm/Cargo.toml @@ -24,6 +24,7 @@ fuel-merkle = { workspace = true, default-features = false } fuel-storage = { workspace = true } fuel-tx = { workspace = true, default-features = false } fuel-types = { workspace = true, default-features = false } +hashbrown = "0.14" itertools = { version = "0.10", default-features = false } paste = "1.0" primitive-types = { version = "0.12", default-features = false } @@ -31,10 +32,11 @@ rand = { version = "0.8", optional = true } serde = { version = "1.0", features = ["derive", "rc"], optional = true } sha3 = { version = "0.10", default-features = false } static_assertions = "1.1" -strum = { version = "0.24", features = ["derive"], optional = true } +strum = { version = "0.24", features = ["derive"] } tai64 = { version = "4.0", default-features = false } thiserror = { version = "1.0", optional = true } -hashbrown = "0.14" +url-escape = { version = "0.1", default-features = false} + [dev-dependencies] ed25519-dalek = { version = "2.0.0", features = ["rand_core"] } @@ -55,10 +57,9 @@ tokio-rayon = "2.1.0" [features] default = ["std"] -std = ["alloc", "fuel-tx/builder", "fuel-types/std", "fuel-asm/std", "fuel-tx/std", "itertools/use_std", "thiserror"] +std = ["alloc", "fuel-storage/std", "fuel-tx/builder", "fuel-types/std", "fuel-asm/std", "fuel-tx/std", "itertools/use_std", "thiserror"] alloc = ["fuel-tx/alloc"] arbitrary = ["fuel-asm/arbitrary"] -optimized = [] profile-gas = ["profile-any"] profile-coverage = ["profile-any"] profile-any = ["dyn-clone"] # All profiling features should depend on this diff --git a/fuel-vm/src/arith.rs b/fuel-vm/src/arith.rs index d7247a1eb3..87c63959eb 100644 --- a/fuel-vm/src/arith.rs +++ b/fuel-vm/src/arith.rs @@ -5,8 +5,6 @@ use fuel_asm::{ Word, }; -use crate::error::RuntimeError; - /// Add two unchecked words, returning an error if overflow #[inline(always)] pub fn checked_add_word(a: Word, b: Word) -> Result { @@ -30,55 +28,3 @@ pub fn checked_add_usize(a: usize, b: usize) -> Result { pub fn checked_sub_usize(a: usize, b: usize) -> Result { a.checked_sub(b).ok_or(PanicReason::ArithmeticOverflow) } - -/// Add two checked words. Might wrap if overflow -/// -/// The function is inlined so it will be optimized to `a + b` if `optimized` feature is -/// enabled so the operation is unchecked. -/// -/// This should be used in contexts that are checked and guaranteed by the protocol to -/// never overflow, but then they might due to some bug in the code. -#[inline(always)] -pub fn add_word(a: Word, b: Word) -> Result { - #[cfg(feature = "optimized")] - #[allow(clippy::arithmetic_side_effects)] - { - Ok(a + b) - } - - #[cfg(not(feature = "optimized"))] - { - a.checked_add(b) - .ok_or_else(|| RuntimeError::unexpected_behavior("unexpected overflow")) - } -} - -/// Subtract two checked words. Might wrap if overflow -/// -/// The function is inlined so it will be optimized to `a + b` if `optimized` feature is -/// enabled so the operation is unchecked. -/// -/// This should be used in contexts that are checked and guaranteed by the protocol to -/// never overflow, but then they might due to some bug in the code. -#[inline(always)] -#[allow(clippy::arithmetic_side_effects)] -pub fn sub_word(a: Word, b: Word) -> Result { - #[cfg(feature = "optimized")] - { - Ok(a - b) - } - - #[cfg(not(feature = "optimized"))] - { - a.checked_sub(b) - .ok_or_else(|| RuntimeError::unexpected_behavior("unexpected underflow")) - } -} - -/// Add two numbers. Should be used only in compile-time evaluations so the code won't -/// compile in case of unexpected overflow. -#[inline(always)] -#[allow(clippy::arithmetic_side_effects)] -pub const fn add_usize(a: usize, b: usize) -> usize { - a + b -} diff --git a/fuel-vm/src/error.rs b/fuel-vm/src/error.rs index c082218093..e233e43a62 100644 --- a/fuel-vm/src/error.rs +++ b/fuel-vm/src/error.rs @@ -5,13 +5,11 @@ use fuel_asm::{ PanicReason, RawInstruction, }; +use fuel_storage::StorageError; use fuel_tx::CheckError; use thiserror::Error; -use core::{ - convert::Infallible as StdInfallible, - fmt, -}; +use core::fmt; /// Interpreter runtime error variants. #[derive(Debug, Error)] @@ -32,15 +30,15 @@ pub enum InterpreterError { /// state transitions. #[error("Execution error")] NoTransactionInitialized, - // /// I/O and OS related errors. - // #[error("Unrecoverable error: {0}")] - // Io(#[from] io::Error), #[error("Execution error")] /// The debug state is not initialized; debug routines can't be called. DebugStateNotInitialized, - /// VM panics, and I/O and OS related errors during VM execution. - #[error("Runtime error: {0}")] - RuntimeError(RuntimeError), + /// Storage I/O error + #[error("Storage error: {0}")] + Storage(StorageError), + /// Encountered a bug + #[error("Bug: {0}")] + Bug(Bug), } impl InterpreterError { @@ -85,11 +83,24 @@ impl From for InterpreterError { fn from(error: RuntimeError) -> Self { match error { RuntimeError::Recoverable(e) => Self::Panic(e), - RuntimeError::Unrecoverable(_) => todo!(), // TODO + RuntimeError::Unrecoverable(e) => match e { + Unrecoverable::Bug(bug) => Self::Bug(bug), + Unrecoverable::PredicateFailure => { + Self::CheckError(CheckError::PredicateVerificationFailed) + } + Unrecoverable::Storage(err) => Self::Storage(err), + }, } } } +impl From for InterpreterError { + fn from(error: StorageError) -> Self { + let error: RuntimeError = error.into(); + error.into() + } +} + impl PartialEq for InterpreterError { fn eq(&self, other: &Self) -> bool { match (self, other) { @@ -97,8 +108,7 @@ impl PartialEq for InterpreterError { (Self::Panic(s), Self::Panic(o)) => s == o, (Self::CheckError(s), Self::CheckError(o)) => s == o, (Self::NoTransactionInitialized, Self::NoTransactionInitialized) => true, - (Self::RuntimeError(s), Self::RuntimeError(o)) => todo!(), // TODO - + (Self::Storage(a), Self::Storage(b)) => a == b, (Self::DebugStateNotInitialized, Self::DebugStateNotInitialized) => true, _ => false, @@ -112,12 +122,18 @@ pub enum Unrecoverable { /// Invalid interpreter state reached unexpectedly, this is a bug Bug(Bug), /// Predicate verification failed - PredicateFailure, + PredicateFailure, // TODO: conserve the inner error + /// Storage io error + Storage(StorageError), } impl fmt::Display for Unrecoverable { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - todo!(); + match self { + Unrecoverable::Bug(bug) => write!(f, "{:#?}", bug), + Unrecoverable::PredicateFailure => write!(f, "Predicate verification failed"), + Unrecoverable::Storage(err) => write!(f, "{:#?}", err), + } } } @@ -127,6 +143,12 @@ impl From for Unrecoverable { } } +impl From for Unrecoverable { + fn from(err: StorageError) -> Self { + Self::Storage(err) + } +} + #[derive(Debug)] /// Runtime error description that should either be specified in the protocol or /// halt the execution. @@ -138,6 +160,7 @@ pub enum RuntimeError { } impl RuntimeError { + /// Predicate verification failed while running it pub const INVALID_PREDICATE: Self = Self::Unrecoverable(Unrecoverable::PredicateFailure); @@ -150,11 +173,6 @@ impl RuntimeError { pub const fn must_halt(&self) -> bool { matches!(self, Self::Unrecoverable(_)) } - - /// Unexpected behavior occurred - pub fn unexpected_behavior(error: E) -> Self { - todo!(); // TODO - } } impl PartialEq for RuntimeError { @@ -182,40 +200,10 @@ impl From for RuntimeError { } } -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -/// Infallible implementation that converts into [`io::Error`]. -pub struct Infallible(StdInfallible); - -impl fmt::Display for Infallible { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.0.fmt(f) - } -} - -impl From for Infallible -where - E: Into, -{ - fn from(e: E) -> Infallible { - Self(e.into()) - } -} - -impl From for InterpreterError { - fn from(_e: Infallible) -> InterpreterError { - unreachable!() - } -} - -impl From for RuntimeError { - fn from(_e: Infallible) -> RuntimeError { - unreachable!() - } -} - -impl From for PanicReason { - fn from(_e: Infallible) -> PanicReason { - unreachable!() +impl From for RuntimeError { + fn from(err: StorageError) -> Self { + let err: Unrecoverable = err.into(); + Self::Unrecoverable(err) } } @@ -255,9 +243,9 @@ pub enum PredicateVerificationFailed { /// The cumulative gas overflowed the u64 accumulator #[error("Cumulative gas computation overflowed the u64 accumulator")] GasOverflow, - /// TODO - #[error("TODO")] // TODO - RuntimeError, + /// Predicate verification failed since it attempted to access storage + #[error("Predicate verification failed since it attempted to access storage")] + Storage, } impl From for CheckError { @@ -275,123 +263,109 @@ impl From for PredicateVerificationFailed { error if error.panic_reason() == Some(PanicReason::OutOfGas) => { PredicateVerificationFailed::OutOfGas } - InterpreterError::RuntimeError(e) => { - PredicateVerificationFailed::RuntimeError - } + InterpreterError::Storage(_) => PredicateVerificationFailed::Storage, _ => PredicateVerificationFailed::False, } } } -/// Unique bug identifier -#[allow(missing_docs)] -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "strum", derive(strum::EnumVariantNames))] -pub enum BugId { - // Not used - ID001, - ID002, - ID003, - ID004, - // Not used - ID005, - // Not used - ID006, - ID007, - ID008, -} - /// Traceable bug variants -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, strum::EnumMessage)] pub enum BugVariant { /// Context gas increase has overflow + #[strum( + message = "The context gas cannot overflow since it was created by a valid transaction and the total gas does not increase - hence, it always fits a word." + )] ContextGasOverflow, /// Context gas increase has underflow + #[strum( + message = "The context gas cannot underflow since any script should halt upon gas exhaustion." + )] ContextGasUnderflow, /// Global gas subtraction has underflow + #[strum( + message = "The gas consumption cannot exceed the gas context since it is capped by the transaction gas limit." + )] GlobalGasUnderflow, + /// The global gas is less than the context gas. + #[strum(message = "The global gas cannot ever be less than the context gas. ")] + GlobalGasLessThanContext, + /// The stack point has overflow + #[strum(message = "The stack pointer cannot overflow under checked operations.")] StackPointerOverflow, - /// The global gas is less than the context gas. - GlobalGasLessThanContext, + /// Code size of a contract doesn't fit into a Word. This is prevented by tx size + /// limit. + #[strum(message = "Contract size doesn't fit into a word.")] + CodeSizeOverflow, + + /// Refund cannot be computed in the current vm state. + #[strum(message = "Refund cannot be computed in the current vm state.")] + UncomputableRefund, } impl fmt::Display for BugVariant { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::ContextGasOverflow => write!( - f, - r#"The context gas cannot overflow since it was created by a valid transaction and the total gas does not increase - hence, it always fits a word. - - This overflow means the registers are corrupted."# - ), - - Self::ContextGasUnderflow => write!( - f, - r#"The context gas cannot underflow since any script should halt upon gas exhaustion. - - This underflow means the registers are corrupted."# - ), - - Self::GlobalGasUnderflow => write!( - f, - r#"The gas consumption cannot exceed the gas context since it is capped by the transaction gas limit. - - This underflow means the registers are corrupted."# - ), - - Self::StackPointerOverflow => write!( - f, - r#"The stack pointer cannot overflow under checked operations. - - This overflow means the registers are corrupted."# - ), - - Self::GlobalGasLessThanContext => write!( - f, - r#"The global gas cannot ever be less than the context gas. - - This means the registers are corrupted."# - ), + use strum::EnumMessage; + if let Some(msg) = self.get_message() { + write!(f, "{}", msg) + } else { + write!(f, "{:?}", self) } } } -/// Bug information with backtrace data +/// VM encountered unexpected state. This is a bug. +/// The execution must terminate since the VM is in an invalid state. +/// +/// The bug it self is identified by the caller location. #[derive(Debug, Clone)] pub struct Bug { - id: BugId, + /// Source code location of the bug, in `path/to/file.rs:line:column` notation + location: String, + + /// Type of bug variant: BugVariant, + /// Additional error message for the bug, if it's caused by a runtime error + inner_message: Option, + + /// Optionally include a backtrace for the instruction triggering this bug. + /// This is only available when the `backtrace` feature is enabled. #[cfg(feature = "backtrace")] bt: backtrace::Backtrace, } -impl PartialEq for Bug { - fn eq(&self, other: &Self) -> bool { - self.id == other.id && self.variant == other.variant - } -} - impl Bug { - #[cfg(not(feature = "backtrace"))] - /// Report a bug without backtrace data - pub const fn new(id: BugId, variant: BugVariant) -> Self { - Self { id, variant } + /// Construct a new bug with the specified variant, using caller location for + /// idenitfying the bug. + #[track_caller] + pub fn new(variant: BugVariant) -> Self { + let caller = core::panic::Location::caller(); + let location = format!("{}:{}:{}", caller.file(), caller.line(), caller.column()); + Self { + location, + variant, + inner_message: None, + #[cfg(feature = "backtrace")] + bt: backtrace::Backtrace::new(), + } } - /// Unique bug identifier per location - pub const fn id(&self) -> BugId { - self.id + /// Set an additional error message. + pub fn with_message(mut self, error: E) -> Self { + self.inner_message = Some(error.to_string()); + self } +} - /// Class variant of the bug - pub const fn variant(&self) -> BugVariant { - self.variant +impl PartialEq for Bug { + fn eq(&self, other: &Self) -> bool { + self.location == other.location } } @@ -399,40 +373,57 @@ impl Bug { mod bt { use super::*; use backtrace::Backtrace; - use core::ops::Deref; impl Bug { - /// Report a bug with backtrace data - pub fn new(id: BugId, variant: BugVariant) -> Self { - let bt = Backtrace::new(); - - Self { id, variant, bt } - } - /// Backtrace data pub const fn bt(&self) -> &Backtrace { &self.bt } } - - impl Deref for Bug { - type Target = Backtrace; - - fn deref(&self) -> &Self::Target { - &self.bt - } - } } impl fmt::Display for Bug { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let issue_title = + format!("Bug%20report: {:?} in {}", self.variant, self.location); + + let issue_body = format!( + "Error: {:?} {}\nLocation: {}\nVersion: {} {}\n", + self.variant, + self.inner_message + .as_ref() + .map(|msg| format!("({msg})")) + .unwrap_or_default(), + self.location, + env!("CARGO_PKG_NAME"), + env!("CARGO_PKG_VERSION") + ); + write!( f, - "This is a bug [{:?}]! Please, report this incident as an issue in fuel-vm repository\n\n", - self.id() + concat!( + "Encountered a bug! Please report this using the following link: ", + "https://github.com/FuelLabs/fuel-vm/issues/new", + "?title={}", + "&body={}", + "\n\n", + "{:?} error in {}: {} {}\n", + ), + url_escape::encode_fragment(&issue_title), + url_escape::encode_fragment(&issue_body), + self.variant, + self.location, + self.variant, + self.inner_message + .as_ref() + .map(|msg| format!("({msg})")) + .unwrap_or_default(), )?; - write!(f, "{}", self.variant())?; + #[cfg(feature = "backtrace")] + { + write!(f, "\nBacktrace:\n{:?}\n", self.bt)?; + } Ok(()) } @@ -450,3 +441,18 @@ impl From for PredicateVerificationFailed { e.into() } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn bug_report_message() { + let bug = Bug::new(BugVariant::ContextGasOverflow).with_message("Test message"); + let text = format!("{}", bug); + assert!(text.contains(file!())); + assert!(text.contains("https://github.com/FuelLabs/fuel-vm/issues/new")); + assert!(text.contains("ContextGasOverflow")); + assert!(text.contains("Test message")); + } +} diff --git a/fuel-vm/src/interpreter/blockchain.rs b/fuel-vm/src/interpreter/blockchain.rs index 974d360777..6207704824 100644 --- a/fuel-vm/src/interpreter/blockchain.rs +++ b/fuel-vm/src/interpreter/blockchain.rs @@ -46,22 +46,15 @@ use crate::{ receipts::ReceiptsCtx, InputContracts, }, - prelude::{ - InterpreterError, - Profiler, - }, + prelude::Profiler, storage::{ - ContractsAssets, ContractsAssetsStorage, ContractsRawCode, InterpreterStorage, }, }; use fuel_asm::PanicReason; -use fuel_storage::{ - StorageInspect, - StorageSize, -}; +use fuel_storage::StorageSize; use fuel_tx::{ ContractIdExt, DependentCost, @@ -555,7 +548,6 @@ struct BurnCtx<'vm, S> { impl<'vm, S> BurnCtx<'vm, S> where S: ContractsAssetsStorage, - >::Error: Into, { pub(crate) fn burn(self, a: Word, b: Word) -> Result<(), RuntimeError> { let range = internal_contract_bounds(self.context, self.fp)?; @@ -598,7 +590,6 @@ struct MintCtx<'vm, S> { impl<'vm, S> MintCtx<'vm, S> where S: ContractsAssetsStorage, - >::Error: Into, { pub(crate) fn mint(self, a: Word, b: Word) -> Result<(), RuntimeError> { let range = internal_contract_bounds(self.context, self.fp)?; @@ -688,7 +679,7 @@ pub(crate) fn block_hash( let height = u32::try_from(b) .map_err(|_| PanicReason::ArithmeticOverflow)? .into(); - let hash = storage.block_hash(height).map_err(|e| e.into())?; + let hash = storage.block_hash(height)?; try_mem_write(a, hash.as_ref(), owner, memory)?; @@ -775,7 +766,6 @@ impl<'vm, S, I: Iterator> CodeSizeCtx<'vm, S, I> { ) -> Result<(), RuntimeError> where S: StorageSize, - >::Error: Into, { let contract_id = CheckedMemConstLen::<{ ContractId::LEN }>::new(b)?; @@ -882,14 +872,13 @@ pub(crate) fn timestamp( .then_some(()) .ok_or(PanicReason::TransactionValidity)?; - *result = storage.timestamp(b).map_err(|e| e.into())?; + *result = storage.timestamp(b)?; inc_pc(pc) } struct MessageOutputCtx<'vm, Tx, S> where S: ContractsAssetsStorage + ?Sized, - >::Error: Into, { base_asset_id: AssetId, max_message_data_length: u64, @@ -915,7 +904,6 @@ where impl MessageOutputCtx<'_, Tx, S> where S: ContractsAssetsStorage + ?Sized, - >::Error: Into, { pub(crate) fn message_output(self) -> Result<(), RuntimeError> where diff --git a/fuel-vm/src/interpreter/contract.rs b/fuel-vm/src/interpreter/contract.rs index 08870b69e9..a79c9857ec 100644 --- a/fuel-vm/src/interpreter/contract.rs +++ b/fuel-vm/src/interpreter/contract.rs @@ -25,9 +25,7 @@ use crate::{ InputContracts, PanicContext, }, - prelude::InterpreterError, storage::{ - ContractsAssets, ContractsAssetsStorage, ContractsRawCode, InterpreterStorage, @@ -38,10 +36,7 @@ use fuel_asm::{ RegisterId, Word, }; -use fuel_storage::{ - StorageInspect, - StorageSize, -}; +use fuel_storage::StorageSize; use fuel_tx::{ Contract, Output, @@ -136,7 +131,7 @@ where &self, contract: &ContractId, ) -> Result { - self.storage.storage_contract_exists(contract) + Ok(self.storage.storage_contract_exists(contract)?) } } @@ -169,7 +164,6 @@ impl<'vm, S, I> ContractBalanceCtx<'vm, S, I> { where I: Iterator, S: ContractsAssetsStorage, - >::Error: Into, { let asset_id = CheckedMemConstLen::<{ AssetId::LEN }>::new(b)?; let contract = CheckedMemConstLen::<{ ContractId::LEN }>::new(c)?; @@ -215,7 +209,6 @@ impl<'vm, S, Tx> TransferCtx<'vm, S, Tx> { where Tx: ExecutableTransaction, S: ContractsAssetsStorage, - >::Error: Into, { let amount = transfer_amount; let destination = @@ -287,7 +280,6 @@ impl<'vm, S, Tx> TransferCtx<'vm, S, Tx> { where Tx: ExecutableTransaction, S: ContractsAssetsStorage, - >::Error: Into, { let out_idx = output_index as usize; let to = Address::from(read_bytes(self.memory, recipient_offset)?); @@ -350,7 +342,6 @@ pub(crate) fn contract_size( ) -> Result where S: StorageSize + ?Sized, - >::Error: Into, { Ok(storage .size_of_value(contract)? @@ -364,7 +355,6 @@ pub(crate) fn balance( ) -> Result where S: ContractsAssetsStorage + ?Sized, - >::Error: Into, { Ok(storage .merkle_contract_asset_id_balance(contract, asset_id)? @@ -380,7 +370,6 @@ pub(crate) fn balance_increase( ) -> Result where S: ContractsAssetsStorage + ?Sized, - >::Error: Into, { let balance = balance(storage, contract, asset_id)?; let balance = balance @@ -399,7 +388,6 @@ pub(crate) fn balance_decrease( ) -> Result where S: ContractsAssetsStorage + ?Sized, - >::Error: Into, { let balance = balance(storage, contract, asset_id)?; let balance = balance diff --git a/fuel-vm/src/interpreter/diff.rs b/fuel-vm/src/interpreter/diff.rs index ac25ec1d38..98b9d1cf0d 100644 --- a/fuel-vm/src/interpreter/diff.rs +++ b/fuel-vm/src/interpreter/diff.rs @@ -112,7 +112,7 @@ where pub trait VmStateCapture { /// The actual type is defined by the implementations of /// the Capture trait. - type State: std::fmt::Debug + Clone; + type State: core::fmt::Debug + Clone; } #[derive(Debug, Clone)] @@ -122,7 +122,7 @@ pub trait VmStateCapture { pub struct Deltas; impl VmStateCapture for Deltas { - type State = Delta; + type State = Delta; } #[derive(Debug, Clone)] @@ -140,7 +140,7 @@ pub struct Delta { pub struct InitialVmState; impl VmStateCapture for InitialVmState { - type State = Previous; + type State = Previous; } #[derive(Debug, Clone)] /// The State type when capturing the initial state of the VM. @@ -178,7 +178,7 @@ struct Memory { } impl Debug for Memory { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { if f.alternate() { f.debug_struct("Memory") .field("start", &self.start) @@ -226,7 +226,7 @@ fn capture_map_state<'iter, K, V>( change: ChangeDeltaVariant>>, ) -> Vec> where - K: 'static + core::cmp::PartialEq + Eq + Clone + Hash + Debug, + K: 'static + PartialEq + Eq + Clone + Hash + Debug, V: 'static + core::cmp::PartialEq + Clone + Debug, { let a_keys: HashSet<_> = a.keys().collect(); @@ -243,19 +243,13 @@ fn capture_map_state_inner<'iter, K, V>( b_keys: &'iter HashSet<&K>, ) -> impl Iterator>>> + 'iter where - K: 'static - + core::cmp::PartialEq - + Eq - + Clone - + Hash - + Debug - + for<'a> core::borrow::Borrow<&'a K>, + K: 'static + PartialEq + Eq + Clone + Hash + Debug, V: 'static + core::cmp::PartialEq + Clone + Debug, { let a_diff = a_keys.difference(b_keys).map(|k| Delta { from: MapState { key: (*k).clone(), - value: Some(a[k].clone()), + value: Some(a[*k].clone()), }, to: MapState { key: (*k).clone(), @@ -269,12 +263,12 @@ where }, to: MapState { key: (*k).clone(), - value: Some(b[k].clone()), + value: Some(b[*k].clone()), }, }); let intersection = a_keys.intersection(b_keys).filter_map(|k| { - let value_a = &a[k]; - let value_b = &b[k]; + let value_a = &a[*k]; + let value_b = &b[*k]; (value_a != value_b).then(|| Delta { from: MapState { key: (*k).clone(), diff --git a/fuel-vm/src/interpreter/diff/storage.rs b/fuel-vm/src/interpreter/diff/storage.rs index b7fa30c1cf..f50c4b577e 100644 --- a/fuel-vm/src/interpreter/diff/storage.rs +++ b/fuel-vm/src/interpreter/diff/storage.rs @@ -2,6 +2,7 @@ use core::fmt::Debug; use hashbrown::HashMap; use fuel_storage::{ + StorageError, StorageRead, StorageSize, }; @@ -292,17 +293,15 @@ where S: StorageInspect, S: InterpreterStorage, { - type Error = >::Error; - fn get( &self, key: &::Key, - ) -> Result::OwnedValue>>, Self::Error> + ) -> Result::OwnedValue>>, StorageError> { >::get(&self.0, key) } - fn contains_key(&self, key: &::Key) -> Result { + fn contains_key(&self, key: &::Key) -> Result { >::contains_key(&self.0, key) } } @@ -315,7 +314,7 @@ where fn size_of_value( &self, key: &::Key, - ) -> Result, Self::Error> { + ) -> Result, StorageError> { >::size_of_value(&self.0, key) } } @@ -329,14 +328,14 @@ where &self, key: &::Key, buf: &mut [u8], - ) -> Result, Self::Error> { + ) -> Result, StorageError> { >::read(&self.0, key, buf) } fn read_alloc( &self, key: &::Key, - ) -> Result>, Self::Error> { + ) -> Result>, StorageError> { >::read_alloc(&self.0, key) } } @@ -351,7 +350,7 @@ where &mut self, key: &::Key, value: &::Value, - ) -> Result::OwnedValue>, Self::Error> { + ) -> Result::OwnedValue>, StorageError> { let existing = >::insert(&mut self.0, key, value)?; self.1.push(::record_insert( key, @@ -364,7 +363,7 @@ where fn remove( &mut self, key: &::Key, - ) -> Result::OwnedValue>, Self::Error> { + ) -> Result::OwnedValue>, StorageError> { let existing = >::remove(&mut self.0, key)?; if let Some(existing) = &existing { self.1 @@ -379,7 +378,7 @@ where S: InterpreterStorage, S: MerkleRootStorage, { - fn root(&self, key: &Key) -> Result { + fn root(&self, key: &Key) -> Result { >::root(&self.0, key) } } @@ -393,21 +392,19 @@ impl InterpreterStorage for Record where S: InterpreterStorage, { - type DataError = ::DataError; - - fn block_height(&self) -> Result { + fn block_height(&self) -> Result { self.0.block_height() } - fn timestamp(&self, height: BlockHeight) -> Result { + fn timestamp(&self, height: BlockHeight) -> Result { self.0.timestamp(height) } - fn block_hash(&self, block_height: BlockHeight) -> Result { + fn block_hash(&self, block_height: BlockHeight) -> Result { self.0.block_hash(block_height) } - fn coinbase(&self) -> Result { + fn coinbase(&self) -> Result { self.0.coinbase() } @@ -416,7 +413,7 @@ where id: &ContractId, start_key: &Bytes32, range: Word, - ) -> Result>>, Self::DataError> { + ) -> Result>>, StorageError> { self.0.merkle_contract_state_range(id, start_key, range) } @@ -425,7 +422,7 @@ where contract: &ContractId, start_key: &Bytes32, values: &[Bytes32], - ) -> Result, Self::DataError> { + ) -> Result, StorageError> { self.0 .merkle_contract_state_insert_range(contract, start_key, values) } @@ -435,7 +432,7 @@ where contract: &ContractId, start_key: &Bytes32, range: Word, - ) -> Result, Self::DataError> { + ) -> Result, StorageError> { self.0 .merkle_contract_state_remove_range(contract, start_key, range) } diff --git a/fuel-vm/src/interpreter/executors/main.rs b/fuel-vm/src/interpreter/executors/main.rs index b64495e119..c0cbcdc97a 100644 --- a/fuel-vm/src/interpreter/executors/main.rs +++ b/fuel-vm/src/interpreter/executors/main.rs @@ -15,8 +15,6 @@ use crate::{ context::Context, error::{ Bug, - BugId, - BugVariant, InterpreterError, PredicateVerificationFailed, }, @@ -28,6 +26,7 @@ use crate::{ RuntimeBalances, }, predicate::RuntimePredicate, + prelude::BugVariant, state::{ ExecuteState, ProgramState, @@ -42,7 +41,6 @@ use crate::{ use crate::{ checked_transaction::CheckPredicateParams, - error::BugVariant::GlobalGasUnderflow, interpreter::InterpreterParams, }; use fuel_asm::{ @@ -337,7 +335,7 @@ impl Interpreter { let gas_used = available_gas .checked_sub(vm.remaining_gas()) - .ok_or_else(|| Bug::new(BugId::ID004, GlobalGasUnderflow))?; + .ok_or_else(|| Bug::new(BugVariant::GlobalGasUnderflow))?; if let PredicateAction::Verifying = predicate_action { if !is_successful { @@ -545,7 +543,7 @@ where .transaction() .limit() .checked_sub(self.remaining_gas()) - .ok_or_else(|| Bug::new(BugId::ID002, BugVariant::GlobalGasUnderflow))?; + .ok_or_else(|| Bug::new(BugVariant::GlobalGasUnderflow))?; // Catch VM panic and don't propagate, generating a receipt let (status, program) = match program { diff --git a/fuel-vm/src/interpreter/executors/predicate.rs b/fuel-vm/src/interpreter/executors/predicate.rs index 0e7f02c2ad..ffaaff14db 100644 --- a/fuel-vm/src/interpreter/executors/predicate.rs +++ b/fuel-vm/src/interpreter/executors/predicate.rs @@ -42,13 +42,17 @@ where if r == 1 { return Ok(ProgramState::Return(r)) } else { - return Err(InterpreterError::PredicateFailure) + return Err(InterpreterError::Panic( + PanicReason::ContractInstructionNotAllowed, + )) } } // A predicate is not expected to return data ExecuteState::ReturnData(_) => { - return Err(InterpreterError::PredicateFailure) + return Err(InterpreterError::Panic( + PanicReason::ContractInstructionNotAllowed, + )) } ExecuteState::Revert(r) => return Ok(ProgramState::Revert(r)), diff --git a/fuel-vm/src/interpreter/flow.rs b/fuel-vm/src/interpreter/flow.rs index 7b82733d8f..9f45aaf2e6 100644 --- a/fuel-vm/src/interpreter/flow.rs +++ b/fuel-vm/src/interpreter/flow.rs @@ -40,10 +40,12 @@ use crate::{ InputContracts, PanicContext, }, - prelude::InterpreterError, + prelude::{ + Bug, + BugVariant, + }, profiler::Profiler, storage::{ - ContractsAssets, ContractsAssetsStorage, ContractsRawCode, InterpreterStorage, @@ -58,7 +60,6 @@ use fuel_asm::{ }; use fuel_storage::{ StorageAsRef, - StorageInspect, StorageRead, StorageSize, }; @@ -203,8 +204,9 @@ impl RetCtx<'_> { let registers = &mut self.registers; let context = &mut self.context; - registers[RegId::CGAS] = - arith::add_word(registers[RegId::CGAS], frame.context_gas())?; + registers[RegId::CGAS] = registers[RegId::CGAS] + .checked_add(frame.context_gas()) + .ok_or_else(|| Bug::new(BugVariant::ContextGasOverflow))?; let cgas = registers[RegId::CGAS]; let ggas = registers[RegId::GGAS]; @@ -497,8 +499,6 @@ where + ContractsAssetsStorage + StorageRead + StorageAsRef, - >::Error: Into, - >::Error: Into, { let call = self.memory.call_params.try_from(self.memory.memory)?; let asset_id = self.memory.asset_id.try_from(self.memory.memory)?; @@ -557,14 +557,17 @@ where ); // subtract gas - *self.registers.system_registers.cgas = - arith::sub_word(*self.registers.system_registers.cgas, forward_gas_amount)?; + *self.registers.system_registers.cgas = (*self.registers.system_registers.cgas) + .checked_sub(forward_gas_amount) + .ok_or_else(|| Bug::new(BugVariant::ContextGasUnderflow))?; *frame.context_gas_mut() = *self.registers.system_registers.cgas; *frame.global_gas_mut() = *self.registers.system_registers.ggas; let frame_bytes = frame.to_bytes(); - let len = arith::add_word(frame_bytes.len() as Word, frame.total_code_size())?; + let len = (frame_bytes.len() as Word) + .checked_add(frame.total_code_size()) + .ok_or_else(|| Bug::new(BugVariant::CodeSizeOverflow))?; if len > *self.registers.system_registers.hp || *self.registers.system_registers.sp @@ -640,7 +643,6 @@ fn write_call_to_memory( ) -> Result where S: StorageSize + StorageRead + StorageAsRef, - >::Error: Into, { let mut code_frame_range = code_mem_range.clone(); // Addition is safe because code size + padding is always less than len @@ -678,7 +680,6 @@ fn call_frame( ) -> Result where S: StorageSize + ?Sized, - >::Error: Into, { let (to, a, b) = call.into_inner(); diff --git a/fuel-vm/src/interpreter/gas.rs b/fuel-vm/src/interpreter/gas.rs index 44a062c52b..49d1919919 100644 --- a/fuel-vm/src/interpreter/gas.rs +++ b/fuel-vm/src/interpreter/gas.rs @@ -1,11 +1,9 @@ use super::Interpreter; use crate::{ - arith, constraints::reg_key::*, error::RuntimeError, prelude::{ Bug, - BugId, BugVariant, }, profiler::Profiler, @@ -117,15 +115,21 @@ fn gas_charge_inner( gas: Word, ) -> Result<(), RuntimeError> { if *cgas > *ggas { - Err(Bug::new(BugId::ID008, BugVariant::GlobalGasLessThanContext).into()) + Err(Bug::new(BugVariant::GlobalGasLessThanContext).into()) } else if gas > *cgas { - *ggas = arith::sub_word(*ggas, *cgas)?; + *ggas = (*ggas) + .checked_sub(*cgas) + .ok_or_else(|| Bug::new(BugVariant::GlobalGasUnderflow))?; *cgas = 0; Err(PanicReason::OutOfGas.into()) } else { - *cgas = arith::sub_word(*cgas, gas)?; - *ggas = arith::sub_word(*ggas, gas)?; + *cgas = (*cgas) + .checked_sub(gas) + .ok_or_else(|| Bug::new(BugVariant::ContextGasUnderflow))?; + *ggas = (*ggas) + .checked_sub(gas) + .ok_or_else(|| Bug::new(BugVariant::GlobalGasUnderflow))?; Ok(()) } diff --git a/fuel-vm/src/interpreter/gas/tests.rs b/fuel-vm/src/interpreter/gas/tests.rs index d0304e7160..5402ea3775 100644 --- a/fuel-vm/src/interpreter/gas/tests.rs +++ b/fuel-vm/src/interpreter/gas/tests.rs @@ -13,7 +13,7 @@ struct GasChargeOutput { } #[test_case(GasChargeInput{cgas: 0, ggas: 0, dependent_factor: 0} => Ok(GasChargeOutput{ cgas: 0, ggas: 0}); "zero")] #[test_case(GasChargeInput{cgas: 0, ggas: 0, dependent_factor: 1} => Err(RuntimeError::Recoverable(PanicReason::OutOfGas)); "no gas")] -#[test_case(GasChargeInput{cgas: 2, ggas: 0, dependent_factor: 1} => matches Err(RuntimeError::Halt(_)); "global gas less than context")] +#[test_case(GasChargeInput{cgas: 2, ggas: 0, dependent_factor: 1} => matches Err(RuntimeError::Unrecoverable(_)); "global gas less than context")] #[test_case(GasChargeInput{cgas: 0, ggas: 2, dependent_factor: 1} => Err(RuntimeError::Recoverable(PanicReason::OutOfGas)); "no call gas")] #[test_case(GasChargeInput{cgas: 1, ggas: 1, dependent_factor: 1} => Ok(GasChargeOutput{ cgas: 0, ggas: 0}); "just enough")] #[test_case(GasChargeInput{cgas: 10, ggas: 15, dependent_factor: 1} => Ok(GasChargeOutput{ cgas: 9, ggas: 14}); "heaps")] diff --git a/fuel-vm/src/interpreter/initialization.rs b/fuel-vm/src/interpreter/initialization.rs index 2c3163050f..547ac6e0a0 100644 --- a/fuel-vm/src/interpreter/initialization.rs +++ b/fuel-vm/src/interpreter/initialization.rs @@ -13,7 +13,6 @@ use crate::{ context::Context, error::{ Bug, - BugId, InterpreterError, }, prelude::RuntimeError, @@ -88,13 +87,13 @@ where context: Context, mut tx: Tx, gas_limit: Word, - ) -> Result<(), RuntimeError> { + ) -> Result<(), InterpreterError> { self.context = context; tx.prepare_init_predicate(); let initial_balances: InitialBalances = Default::default(); let runtime_balances = initial_balances.clone().try_into()?; - self.init_inner(tx, initial_balances, runtime_balances, gas_limit) + Ok(self.init_inner(tx, initial_balances, runtime_balances, gas_limit)?) } } @@ -119,7 +118,7 @@ where let gas_limit = tx .limit() .checked_sub(gas_used_by_predicates) - .ok_or_else(|| Bug::new(BugId::ID003, GlobalGasUnderflow))?; + .ok_or_else(|| Bug::new(GlobalGasUnderflow))?; let initial_balances = metadata.balances(); let runtime_balances = initial_balances.try_into()?; diff --git a/fuel-vm/src/interpreter/post_execution.rs b/fuel-vm/src/interpreter/post_execution.rs index 1aac6aa7e9..befc63d14c 100644 --- a/fuel-vm/src/interpreter/post_execution.rs +++ b/fuel-vm/src/interpreter/post_execution.rs @@ -1,4 +1,6 @@ use crate::prelude::{ + Bug, + BugVariant, ExecutableTransaction, Interpreter, InterpreterStorage, @@ -43,12 +45,15 @@ where where Tx: ExecutableTransaction, { - tx.update_outputs(revert, remaining_gas, initial_balances, balances, fee_params, base_asset_id) - .map_err(|e| RuntimeError::unexpected_behavior( - - format!("a valid VM execution shouldn't result in a state where it can't compute its refund. This is a bug! {e}") - ) - )?; + tx.update_outputs( + revert, + remaining_gas, + initial_balances, + balances, + fee_params, + base_asset_id, + ) + .map_err(|e| Bug::new(BugVariant::UncomputableRefund).with_message(e))?; Ok(()) } diff --git a/fuel-vm/src/lib.rs b/fuel-vm/src/lib.rs index 7397beb9ed..606af6b346 100644 --- a/fuel-vm/src/lib.rs +++ b/fuel-vm/src/lib.rs @@ -119,9 +119,7 @@ pub mod prelude { context::Context, error::{ Bug, - BugId, BugVariant, - Infallible, InterpreterError, RuntimeError, }, diff --git a/fuel-vm/src/storage/interpreter.rs b/fuel-vm/src/storage/interpreter.rs index 6422a22dc1..1b19aced2d 100644 --- a/fuel-vm/src/storage/interpreter.rs +++ b/fuel-vm/src/storage/interpreter.rs @@ -3,6 +3,7 @@ use fuel_storage::{ MerkleRootStorage, StorageAsRef, + StorageError, StorageInspect, StorageMutate, StorageRead, @@ -22,46 +23,34 @@ use fuel_types::{ Word, }; -use crate::{ - prelude::{ - InterpreterError, - RuntimeError, - }, - storage::{ - ContractsAssets, - ContractsInfo, - ContractsRawCode, - ContractsState, - }, +use crate::storage::{ + ContractsAssets, + ContractsInfo, + ContractsRawCode, + ContractsState, }; use alloc::{ borrow::Cow, vec::Vec, }; -use core::{ - fmt, - ops::{ - Deref, - DerefMut, - }, +use core::ops::{ + Deref, + DerefMut, }; /// When this trait is implemented, the underlying interpreter is guaranteed to /// have full functionality pub trait InterpreterStorage: - StorageMutate - + StorageSize - + StorageRead - + StorageMutate - + MerkleRootStorage - + ContractsAssetsStorage + StorageMutate + + StorageSize + + StorageRead + + StorageMutate + + MerkleRootStorage + + ContractsAssetsStorage { - /// Error implementation for reasons unspecified in the protocol. - type DataError: Into + fmt::Debug; // TODO - /// Provide the current block height in which the transactions should be /// executed. - fn block_height(&self) -> Result; + fn block_height(&self) -> Result; /// Return the timestamp of a given block /// @@ -69,13 +58,13 @@ pub trait InterpreterStorage: /// is passed - under the assumption that the block height is consistent, the /// storage should necessarily have the timestamp for the block, unless some I/O /// error prevents it from fetching it. - fn timestamp(&self, height: BlockHeight) -> Result; + fn timestamp(&self, height: BlockHeight) -> Result; /// Provide the block hash from a given height. - fn block_hash(&self, block_height: BlockHeight) -> Result; + fn block_hash(&self, block_height: BlockHeight) -> Result; /// Provide the coinbase address for the VM instructions implementation. - fn coinbase(&self) -> Result; + fn coinbase(&self) -> Result; /// Deploy a contract into the storage with contract id fn deploy_contract_with_id( @@ -85,7 +74,7 @@ pub trait InterpreterStorage: contract: &Contract, root: &Bytes32, id: &ContractId, - ) -> Result<(), Self::DataError> { + ) -> Result<(), StorageError> { self.storage_contract_insert(id, contract)?; self.storage_contract_root_insert(id, salt, root)?; @@ -101,7 +90,7 @@ pub trait InterpreterStorage: fn storage_contract( &self, id: &ContractId, - ) -> Result>, Self::DataError> { + ) -> Result>, StorageError> { StorageInspect::::get(self, id) } @@ -110,7 +99,7 @@ pub trait InterpreterStorage: fn storage_contract_size( &self, id: &ContractId, - ) -> Result, Self::DataError> { + ) -> Result, StorageError> { StorageSize::::size_of_value(self, id) } @@ -119,7 +108,7 @@ pub trait InterpreterStorage: &self, id: &ContractId, writer: &mut [u8], - ) -> Result, Self::DataError> { + ) -> Result, StorageError> { Ok(StorageRead::::read(self, id, writer)?.map(|r| r as Word)) } @@ -130,12 +119,12 @@ pub trait InterpreterStorage: &mut self, id: &ContractId, contract: &Contract, - ) -> Result, Self::DataError> { + ) -> Result, StorageError> { StorageMutate::::insert(self, id, contract.as_ref()) } /// Check if a provided contract exists in the chain. - fn storage_contract_exists(&self, id: &ContractId) -> Result { + fn storage_contract_exists(&self, id: &ContractId) -> Result { self.storage::().contains_key(id) } @@ -144,7 +133,7 @@ pub trait InterpreterStorage: fn storage_contract_root( &self, id: &ContractId, - ) -> Result>, Self::DataError> { + ) -> Result>, StorageError> { StorageInspect::::get(self, id) } @@ -154,7 +143,7 @@ pub trait InterpreterStorage: id: &ContractId, salt: &Salt, root: &Bytes32, - ) -> Result, Self::DataError> { + ) -> Result, StorageError> { StorageMutate::::insert(self, id, &(*salt, *root)) } @@ -163,7 +152,7 @@ pub trait InterpreterStorage: &self, id: &ContractId, key: &Bytes32, - ) -> Result>, Self::DataError> { + ) -> Result>, StorageError> { StorageInspect::::get(self, &(id, key).into()) } @@ -173,7 +162,7 @@ pub trait InterpreterStorage: contract: &ContractId, key: &Bytes32, value: &Bytes32, - ) -> Result, Self::DataError> { + ) -> Result, StorageError> { StorageMutate::::insert(self, &(contract, key).into(), value) } @@ -182,7 +171,7 @@ pub trait InterpreterStorage: &mut self, contract: &ContractId, key: &Bytes32, - ) -> Result, Self::DataError> { + ) -> Result, StorageError> { StorageMutate::::remove(self, &(contract, key).into()) } @@ -194,7 +183,7 @@ pub trait InterpreterStorage: id: &ContractId, start_key: &Bytes32, range: Word, - ) -> Result>>, Self::DataError>; + ) -> Result>>, StorageError>; /// Insert a range of key-value mappings into contract storage. /// Returns None if any of the keys in the range were previously unset. @@ -203,7 +192,7 @@ pub trait InterpreterStorage: contract: &ContractId, start_key: &Bytes32, values: &[Bytes32], - ) -> Result, Self::DataError>; + ) -> Result, StorageError>; /// Remove a range of key-values from contract storage. /// Returns None if any of the keys in the range were already unset. @@ -212,7 +201,7 @@ pub trait InterpreterStorage: contract: &ContractId, start_key: &Bytes32, range: Word, - ) -> Result, Self::DataError>; + ) -> Result, StorageError>; } /// Storage operations for contract assets. @@ -222,7 +211,7 @@ pub trait ContractsAssetsStorage: MerkleRootStorage &self, id: &ContractId, asset_id: &AssetId, - ) -> Result, Self::Error> { + ) -> Result, StorageError> { let balance = self .storage::() .get(&(id, asset_id).into())? @@ -237,7 +226,7 @@ pub trait ContractsAssetsStorage: MerkleRootStorage contract: &ContractId, asset_id: &AssetId, value: Word, - ) -> Result, Self::Error> { + ) -> Result, StorageError> { StorageMutate::::insert( self, &(contract, asset_id).into(), @@ -252,28 +241,26 @@ impl InterpreterStorage for &mut S where S: InterpreterStorage, { - type DataError = S::DataError; - - fn block_height(&self) -> Result { + fn block_height(&self) -> Result { ::block_height(self.deref()) } - fn timestamp(&self, height: BlockHeight) -> Result { + fn timestamp(&self, height: BlockHeight) -> Result { ::timestamp(self.deref(), height) } - fn block_hash(&self, block_height: BlockHeight) -> Result { + fn block_hash(&self, block_height: BlockHeight) -> Result { ::block_hash(self.deref(), block_height) } - fn coinbase(&self) -> Result { + fn coinbase(&self) -> Result { ::coinbase(self.deref()) } fn storage_contract_size( &self, id: &ContractId, - ) -> Result, Self::DataError> { + ) -> Result, StorageError> { ::storage_contract_size(self.deref(), id) } @@ -281,7 +268,7 @@ where &self, id: &ContractId, writer: &mut [u8], - ) -> Result, Self::DataError> { + ) -> Result, StorageError> { ::read_contract(self.deref(), id, writer) } @@ -290,7 +277,7 @@ where id: &ContractId, start_key: &Bytes32, range: Word, - ) -> Result>>, Self::DataError> { + ) -> Result>>, StorageError> { ::merkle_contract_state_range( self.deref(), id, @@ -304,7 +291,7 @@ where contract: &ContractId, start_key: &Bytes32, values: &[Bytes32], - ) -> Result, Self::DataError> { + ) -> Result, StorageError> { ::merkle_contract_state_insert_range( self.deref_mut(), contract, @@ -318,7 +305,7 @@ where contract: &ContractId, start_key: &Bytes32, range: Word, - ) -> Result, Self::DataError> { + ) -> Result, StorageError> { ::merkle_contract_state_remove_range( self.deref_mut(), contract, diff --git a/fuel-vm/src/storage/memory.rs b/fuel-vm/src/storage/memory.rs index 0a7b0bbe12..ca45486f9d 100644 --- a/fuel-vm/src/storage/memory.rs +++ b/fuel-vm/src/storage/memory.rs @@ -1,6 +1,5 @@ use crate::{ crypto, - error::Infallible, storage::{ ContractsAssetKey, ContractsAssets, @@ -18,6 +17,7 @@ use fuel_storage::{ MerkleRoot, MerkleRootStorage, StorageAsRef, + StorageError, StorageInspect, StorageMutate, StorageRead, @@ -140,13 +140,11 @@ impl Default for MemoryStorage { } impl StorageInspect for MemoryStorage { - type Error = Infallible; - - fn get(&self, key: &ContractId) -> Result>, Infallible> { + fn get(&self, key: &ContractId) -> Result>, StorageError> { Ok(self.memory.contracts.get(key).map(Cow::Borrowed)) } - fn contains_key(&self, key: &ContractId) -> Result { + fn contains_key(&self, key: &ContractId) -> Result { Ok(self.memory.contracts.contains_key(key)) } } @@ -156,17 +154,17 @@ impl StorageMutate for MemoryStorage { &mut self, key: &ContractId, value: &[u8], - ) -> Result, Infallible> { + ) -> Result, StorageError> { Ok(self.memory.contracts.insert(*key, value.into())) } - fn remove(&mut self, key: &ContractId) -> Result, Infallible> { + fn remove(&mut self, key: &ContractId) -> Result, StorageError> { Ok(self.memory.contracts.remove(key)) } } impl StorageWrite for MemoryStorage { - fn write(&mut self, key: &ContractId, buf: Vec) -> Result { + fn write(&mut self, key: &ContractId, buf: Vec) -> Result { let size = buf.len(); self.memory.contracts.insert(*key, Contract::from(buf)); Ok(size) @@ -176,7 +174,7 @@ impl StorageWrite for MemoryStorage { &mut self, key: &::Key, buf: Vec, - ) -> Result<(usize, Option>), Self::Error> + ) -> Result<(usize, Option>), StorageError> where Self: StorageSize, { @@ -188,13 +186,13 @@ impl StorageWrite for MemoryStorage { fn take( &mut self, key: &::Key, - ) -> Result>, Self::Error> { + ) -> Result>, StorageError> { Ok(self.memory.contracts.remove(key).map(Vec::from)) } } impl StorageSize for MemoryStorage { - fn size_of_value(&self, key: &ContractId) -> Result, Infallible> { + fn size_of_value(&self, key: &ContractId) -> Result, StorageError> { Ok(self.memory.contracts.get(key).map(|c| c.as_ref().len())) } } @@ -204,7 +202,7 @@ impl StorageRead for MemoryStorage { &self, key: &ContractId, buf: &mut [u8], - ) -> Result, Self::Error> { + ) -> Result, StorageError> { Ok(self.memory.contracts.get(key).and_then(|c| { let len = buf.len().min(c.as_ref().len()); buf.copy_from_slice(&c.as_ref()[..len]); @@ -212,22 +210,20 @@ impl StorageRead for MemoryStorage { })) } - fn read_alloc(&self, key: &ContractId) -> Result>, Self::Error> { + fn read_alloc(&self, key: &ContractId) -> Result>, StorageError> { Ok(self.memory.contracts.get(key).map(|c| c.as_ref().to_vec())) } } impl StorageInspect for MemoryStorage { - type Error = Infallible; - fn get( &self, key: &ContractId, - ) -> Result>, Infallible> { + ) -> Result>, StorageError> { Ok(self.memory.contract_code_root.get(key).map(Cow::Borrowed)) } - fn contains_key(&self, key: &ContractId) -> Result { + fn contains_key(&self, key: &ContractId) -> Result { Ok(self.memory.contract_code_root.contains_key(key)) } } @@ -237,32 +233,30 @@ impl StorageMutate for MemoryStorage { &mut self, key: &ContractId, value: &(Salt, Bytes32), - ) -> Result, Infallible> { + ) -> Result, StorageError> { Ok(self.memory.contract_code_root.insert(*key, *value)) } fn remove( &mut self, key: &ContractId, - ) -> Result, Infallible> { + ) -> Result, StorageError> { Ok(self.memory.contract_code_root.remove(key)) } } impl StorageInspect for MemoryStorage { - type Error = Infallible; - fn get( &self, key: &::Key, - ) -> Result>, Infallible> { + ) -> Result>, StorageError> { Ok(self.memory.balances.get(key).map(Cow::Borrowed)) } fn contains_key( &self, key: &::Key, - ) -> Result { + ) -> Result { Ok(self.memory.balances.contains_key(key)) } } @@ -272,20 +266,20 @@ impl StorageMutate for MemoryStorage { &mut self, key: &::Key, value: &Word, - ) -> Result, Infallible> { + ) -> Result, StorageError> { Ok(self.memory.balances.insert(*key, *value)) } fn remove( &mut self, key: &::Key, - ) -> Result, Infallible> { + ) -> Result, StorageError> { Ok(self.memory.balances.remove(key)) } } impl MerkleRootStorage for MemoryStorage { - fn root(&self, parent: &ContractId) -> Result { + fn root(&self, parent: &ContractId) -> Result { let root = self .memory .balances @@ -302,19 +296,17 @@ impl MerkleRootStorage for MemoryStorage { } impl StorageInspect for MemoryStorage { - type Error = Infallible; - fn get( &self, key: &::Key, - ) -> Result>, Infallible> { + ) -> Result>, StorageError> { Ok(self.memory.contract_state.get(key).map(Cow::Borrowed)) } fn contains_key( &self, key: &::Key, - ) -> Result { + ) -> Result { Ok(self.memory.contract_state.contains_key(key)) } } @@ -324,20 +316,20 @@ impl StorageMutate for MemoryStorage { &mut self, key: &::Key, value: &Bytes32, - ) -> Result, Infallible> { + ) -> Result, StorageError> { Ok(self.memory.contract_state.insert(*key, *value)) } fn remove( &mut self, key: &::Key, - ) -> Result, Infallible> { + ) -> Result, StorageError> { Ok(self.memory.contract_state.remove(key)) } } impl MerkleRootStorage for MemoryStorage { - fn root(&self, parent: &ContractId) -> Result { + fn root(&self, parent: &ContractId) -> Result { let root = self .memory .contract_state @@ -355,24 +347,22 @@ impl MerkleRootStorage for MemoryStorage { impl ContractsAssetsStorage for MemoryStorage {} impl InterpreterStorage for MemoryStorage { - type DataError = Infallible; - - fn block_height(&self) -> Result { + fn block_height(&self) -> Result { Ok(self.block_height) } - fn timestamp(&self, height: BlockHeight) -> Result { + fn timestamp(&self, height: BlockHeight) -> Result { const GENESIS: Tai64 = Tai64::UNIX_EPOCH; const INTERVAL: Word = 10; Ok((GENESIS + (*height as Word * INTERVAL)).0) } - fn block_hash(&self, block_height: BlockHeight) -> Result { + fn block_hash(&self, block_height: BlockHeight) -> Result { Ok(Hasher::hash(block_height.to_be_bytes())) } - fn coinbase(&self) -> Result { + fn coinbase(&self) -> Result { Ok(self.coinbase) } @@ -381,7 +371,7 @@ impl InterpreterStorage for MemoryStorage { id: &ContractId, start_key: &Bytes32, range: Word, - ) -> Result>>, Self::DataError> { + ) -> Result>>, StorageError> { let start: ContractsStateKey = (id, start_key).into(); let end: ContractsStateKey = (id, &Bytes32::new([u8::MAX; 32])).into(); let mut iter = self.memory.contract_state.range(start..end); @@ -418,7 +408,7 @@ impl InterpreterStorage for MemoryStorage { contract: &ContractId, start_key: &Bytes32, values: &[Bytes32], - ) -> Result, Self::DataError> { + ) -> Result, StorageError> { let mut any_unset_key = false; let values: Vec<_> = core::iter::successors(Some(**start_key), |n| { let mut n = *n; @@ -444,7 +434,7 @@ impl InterpreterStorage for MemoryStorage { contract: &ContractId, start_key: &Bytes32, range: Word, - ) -> Result, Self::DataError> { + ) -> Result, StorageError> { let mut all_set_key = true; let mut values: std::collections::HashSet<_> = core::iter::successors(Some(**start_key), |n| { diff --git a/fuel-vm/src/storage/predicate.rs b/fuel-vm/src/storage/predicate.rs index a297ebe2d8..147845bf35 100644 --- a/fuel-vm/src/storage/predicate.rs +++ b/fuel-vm/src/storage/predicate.rs @@ -3,17 +3,14 @@ use alloc::{ vec::Vec, }; -use crate::{ - error::InterpreterError, - prelude::RuntimeError, - storage::InterpreterStorage, -}; +use crate::storage::InterpreterStorage; use fuel_asm::Word; use fuel_storage::{ Mappable, MerkleRoot, MerkleRootStorage, + StorageError, StorageInspect, StorageMutate, StorageRead, @@ -41,17 +38,15 @@ use super::{ pub struct PredicateStorage; impl StorageInspect for PredicateStorage { - type Error = RuntimeError; - fn get( &self, _key: &Type::Key, - ) -> Result>, RuntimeError> { - Err(RuntimeError::INVALID_PREDICATE) + ) -> Result>, StorageError> { + Err(StorageError::Unavailable) } - fn contains_key(&self, _key: &Type::Key) -> Result { - Err(RuntimeError::INVALID_PREDICATE) + fn contains_key(&self, _key: &Type::Key) -> Result { + Err(StorageError::Unavailable) } } @@ -60,21 +55,21 @@ impl StorageMutate for PredicateStorage { &mut self, _key: &Type::Key, _value: &Type::Value, - ) -> Result, RuntimeError> { - Err(RuntimeError::INVALID_PREDICATE) + ) -> Result, StorageError> { + Err(StorageError::Unavailable) } fn remove( &mut self, _key: &Type::Key, - ) -> Result, RuntimeError> { - Err(RuntimeError::INVALID_PREDICATE) + ) -> Result, StorageError> { + Err(StorageError::Unavailable) } } impl StorageSize for PredicateStorage { - fn size_of_value(&self, _key: &ContractId) -> Result, RuntimeError> { - Err(RuntimeError::INVALID_PREDICATE) + fn size_of_value(&self, _key: &ContractId) -> Result, StorageError> { + Err(StorageError::Unavailable) } } @@ -83,43 +78,41 @@ impl StorageRead for PredicateStorage { &self, _key: &::Key, _buf: &mut [u8], - ) -> Result, Self::Error> { - Err(RuntimeError::INVALID_PREDICATE) + ) -> Result, StorageError> { + Err(StorageError::Unavailable) } fn read_alloc( &self, _key: &::Key, - ) -> Result>, Self::Error> { - Err(RuntimeError::INVALID_PREDICATE) + ) -> Result>, StorageError> { + Err(StorageError::Unavailable) } } impl MerkleRootStorage for PredicateStorage { - fn root(&self, _parent: &Key) -> Result { - Err(RuntimeError::INVALID_PREDICATE) + fn root(&self, _parent: &Key) -> Result { + Err(StorageError::Unavailable) } } impl ContractsAssetsStorage for PredicateStorage {} impl InterpreterStorage for PredicateStorage { - type DataError = RuntimeError; - - fn block_height(&self) -> Result { - Err(RuntimeError::INVALID_PREDICATE) + fn block_height(&self) -> Result { + Err(StorageError::Unavailable) } - fn timestamp(&self, _height: BlockHeight) -> Result { - Err(RuntimeError::INVALID_PREDICATE) + fn timestamp(&self, _height: BlockHeight) -> Result { + Err(StorageError::Unavailable) } - fn block_hash(&self, _block_height: BlockHeight) -> Result { - Err(RuntimeError::INVALID_PREDICATE) + fn block_hash(&self, _block_height: BlockHeight) -> Result { + Err(StorageError::Unavailable) } - fn coinbase(&self) -> Result { - Err(RuntimeError::INVALID_PREDICATE) + fn coinbase(&self) -> Result { + Err(StorageError::Unavailable) } fn merkle_contract_state_range( @@ -127,8 +120,8 @@ impl InterpreterStorage for PredicateStorage { _id: &ContractId, _start_key: &Bytes32, _range: Word, - ) -> Result>>, Self::DataError> { - Err(RuntimeError::INVALID_PREDICATE) + ) -> Result>>, StorageError> { + Err(StorageError::Unavailable) } fn merkle_contract_state_insert_range( @@ -136,8 +129,8 @@ impl InterpreterStorage for PredicateStorage { _contract: &ContractId, _start_key: &Bytes32, _values: &[Bytes32], - ) -> Result, Self::DataError> { - Err(RuntimeError::INVALID_PREDICATE) + ) -> Result, StorageError> { + Err(StorageError::Unavailable) } fn merkle_contract_state_remove_range( @@ -145,7 +138,7 @@ impl InterpreterStorage for PredicateStorage { _contract: &ContractId, _start_key: &Bytes32, _range: Word, - ) -> Result, Self::DataError> { - Err(RuntimeError::INVALID_PREDICATE) + ) -> Result, StorageError> { + Err(StorageError::Unavailable) } } From 3838725a53805edd0689ebbc758aa3e146785bf6 Mon Sep 17 00:00:00 2001 From: Hannes Karppila Date: Wed, 13 Sep 2023 14:58:21 +0300 Subject: [PATCH 16/44] Change deps from last commit to be no_std --- fuel-vm/Cargo.toml | 5 ++--- fuel-vm/src/error.rs | 11 ++++++++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/fuel-vm/Cargo.toml b/fuel-vm/Cargo.toml index 7af2704845..946f4cbc4f 100644 --- a/fuel-vm/Cargo.toml +++ b/fuel-vm/Cargo.toml @@ -27,16 +27,15 @@ fuel-types = { workspace = true, default-features = false } hashbrown = "0.14" itertools = { version = "0.10", default-features = false } paste = "1.0" +percent-encoding = { version = "2.3", features = ["alloc"], default-features = false } primitive-types = { version = "0.12", default-features = false } rand = { version = "0.8", optional = true } serde = { version = "1.0", features = ["derive", "rc"], optional = true } sha3 = { version = "0.10", default-features = false } static_assertions = "1.1" -strum = { version = "0.24", features = ["derive"] } +strum = { version = "0.24", features = ["derive"], default-features = false } tai64 = { version = "4.0", default-features = false } thiserror = { version = "1.0", optional = true } -url-escape = { version = "0.1", default-features = false} - [dev-dependencies] ed25519-dalek = { version = "2.0.0", features = ["rand_core"] } diff --git a/fuel-vm/src/error.rs b/fuel-vm/src/error.rs index e233e43a62..58a5ba39ab 100644 --- a/fuel-vm/src/error.rs +++ b/fuel-vm/src/error.rs @@ -384,8 +384,13 @@ mod bt { impl fmt::Display for Bug { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use percent_encoding::{ + utf8_percent_encode, + NON_ALPHANUMERIC, + }; + let issue_title = - format!("Bug%20report: {:?} in {}", self.variant, self.location); + format!("Bug report: {:?} in {}", self.variant, self.location); let issue_body = format!( "Error: {:?} {}\nLocation: {}\nVersion: {} {}\n", @@ -409,8 +414,8 @@ impl fmt::Display for Bug { "\n\n", "{:?} error in {}: {} {}\n", ), - url_escape::encode_fragment(&issue_title), - url_escape::encode_fragment(&issue_body), + utf8_percent_encode(&issue_title, NON_ALPHANUMERIC), + utf8_percent_encode(&issue_body, NON_ALPHANUMERIC), self.variant, self.location, self.variant, From 2511ad91a672ec038db10651efa14ff82d8938e6 Mon Sep 17 00:00:00 2001 From: Hannes Karppila Date: Wed, 13 Sep 2023 15:30:03 +0300 Subject: [PATCH 17/44] Make cargo check work under --target x86_64-unknown-none --features alloc --- fuel-tx/Cargo.toml | 7 +- fuel-tx/src/transaction.rs | 5 +- fuel-tx/src/transaction/id.rs | 3 - fuel-tx/src/transaction/metadata.rs | 4 +- fuel-tx/src/transaction/types.rs | 1 - fuel-tx/src/transaction/types/create.rs | 11 +-- fuel-tx/src/transaction/types/input.rs | 1 - fuel-tx/src/transaction/types/mint.rs | 5 -- fuel-tx/src/transaction/types/output.rs | 1 - fuel-tx/src/transaction/types/script.rs | 9 +-- fuel-tx/src/transaction/types/witness.rs | 3 - fuel-tx/src/transaction/validity.rs | 8 +- fuel-vm/Cargo.toml | 5 +- fuel-vm/src/backtrace.rs | 5 +- fuel-vm/src/checked_transaction.rs | 5 +- fuel-vm/src/checked_transaction/builder.rs | 4 + fuel-vm/src/error.rs | 76 ++++++++++++++----- fuel-vm/src/interpreter.rs | 5 +- fuel-vm/src/interpreter/constructors.rs | 2 + fuel-vm/src/interpreter/diff/storage.rs | 4 +- .../src/interpreter/executors/instruction.rs | 8 +- fuel-vm/src/storage/memory.rs | 2 +- 22 files changed, 98 insertions(+), 76 deletions(-) diff --git a/fuel-tx/Cargo.toml b/fuel-tx/Cargo.toml index b95a247eb4..9a8b27ff76 100644 --- a/fuel-tx/Cargo.toml +++ b/fuel-tx/Cargo.toml @@ -16,9 +16,10 @@ fuel-asm = { workspace = true, default-features = false } fuel-crypto = { workspace = true, default-features = false } fuel-merkle = { workspace = true, default-features = false, optional = true } fuel-types = { workspace = true, default-features = false } +hashbrown = { version = "0.14", optional = true } itertools = { version = "0.10", default-features = false, optional = true } -num-integer = { version = "0.1", default-features = false, optional = true } num_enum = { version = "0.7", default-features = false, optional = true } +num-integer = { version = "0.1", default-features = false, optional = true } rand = { version = "0.8", default-features = false, features = ["std_rng"], optional = true } serde = { version = "1.0", default-features = false, features = ["alloc", "derive"], optional = true } serde_json = { version = "1.0", default-features = false, features = ["alloc"], optional = true } @@ -40,10 +41,10 @@ rstest = "0.15" [features] default = ["fuel-asm/default", "fuel-crypto/default", "fuel-merkle/default", "fuel-types/default", "std"] -alloc = ["fuel-types/alloc", "itertools/use_alloc", "derivative", "fuel-merkle", "num_enum", "num-integer", "strum", "strum_macros"] +alloc = ["hashbrown", "fuel-types/alloc", "itertools/use_alloc", "derivative", "fuel-merkle", "num_enum", "num-integer", "strum", "strum_macros"] builder = ["alloc", "internals"] internals = [] random = ["fuel-crypto/random", "fuel-types/random", "rand"] std = ["alloc", "fuel-asm/std", "fuel-crypto/std", "fuel-merkle/std", "fuel-types/std", "itertools/default", "rand?/default", "serde?/default", "hex/std"] # serde is requiring alloc because its mandatory for serde_json. to avoid adding a new feature only for serde_json, we just require `alloc` here since as of the moment we don't have a use case of serde without alloc. -serde = ["alloc", "dep:serde", "fuel-asm/serde", "fuel-crypto/serde", "fuel-types/serde", "serde_json"] +serde = ["alloc", "dep:serde", "fuel-asm/serde", "fuel-crypto/serde", "fuel-types/serde", "serde_json", "hashbrown/serde"] diff --git a/fuel-tx/src/transaction.rs b/fuel-tx/src/transaction.rs index ac8fdb6917..360d7980e9 100644 --- a/fuel-tx/src/transaction.rs +++ b/fuel-tx/src/transaction.rs @@ -59,7 +59,6 @@ use crate::input::coin::{ }; use input::*; -#[cfg(feature = "std")] use crate::input::{ contract::Contract, message::{ @@ -303,7 +302,6 @@ pub trait Executable: field::Inputs + field::Outputs + field::Witnesses { /// Returns ids of all `Input::Contract` that are present in the inputs. // TODO: Return `Vec` instead - #[cfg(feature = "std")] fn input_contracts(&self) -> IntoIter<&fuel_types::ContractId> { self.inputs() .iter() @@ -311,7 +309,7 @@ pub trait Executable: field::Inputs + field::Outputs + field::Witnesses { Input::Contract(Contract { contract_id, .. }) => Some(contract_id), _ => None, }) - .unique() + .dedup() .collect_vec() .into_iter() } @@ -411,7 +409,6 @@ pub trait Executable: field::Inputs + field::Outputs + field::Witnesses { /// /// note: Fields dependent on storage/state such as balance and state roots, or tx /// pointers, should already set by the client beforehand. - #[cfg(feature = "std")] fn prepare_init_script(&mut self) -> &mut Self { self.outputs_mut() .iter_mut() diff --git a/fuel-tx/src/transaction/id.rs b/fuel-tx/src/transaction/id.rs index f6b9f6b675..0d39cef357 100644 --- a/fuel-tx/src/transaction/id.rs +++ b/fuel-tx/src/transaction/id.rs @@ -1,4 +1,3 @@ -#[cfg(feature = "std")] use crate::{ field, input::{ @@ -11,7 +10,6 @@ use crate::{ Input, Transaction, }; -#[cfg(feature = "std")] use fuel_crypto::{ Message, PublicKey, @@ -33,7 +31,6 @@ pub trait UniqueIdentifier { fn cached_id(&self) -> Option; } -#[cfg(feature = "std")] impl UniqueIdentifier for Transaction { fn id(&self, chain_id: &ChainId) -> Bytes32 { match self { diff --git a/fuel-tx/src/transaction/metadata.rs b/fuel-tx/src/transaction/metadata.rs index 6539ab2ddf..13b11599e7 100644 --- a/fuel-tx/src/transaction/metadata.rs +++ b/fuel-tx/src/transaction/metadata.rs @@ -4,10 +4,9 @@ use fuel_types::{ ChainId, }; -use crate::CheckError; -#[cfg(feature = "std")] use crate::{ field, + CheckError, UniqueIdentifier, }; @@ -54,7 +53,6 @@ pub(crate) struct CommonMetadata { pub witnesses_offset_at: Vec, } -#[cfg(feature = "std")] impl CommonMetadata { /// Computes the `Metadata` for the `tx` transaction. pub fn compute(tx: &Tx, chain_id: &ChainId) -> Self diff --git a/fuel-tx/src/transaction/types.rs b/fuel-tx/src/transaction/types.rs index 61f619dd01..8b39ae3728 100644 --- a/fuel-tx/src/transaction/types.rs +++ b/fuel-tx/src/transaction/types.rs @@ -18,7 +18,6 @@ pub use storage::StorageSlot; pub use utxo_id::UtxoId; pub use witness::Witness; -#[cfg(feature = "std")] pub fn compute_transaction_id( chain_id: &fuel_types::ChainId, tx: &mut T, diff --git a/fuel-tx/src/transaction/types/create.rs b/fuel-tx/src/transaction/types/create.rs index 31b4fcec9f..cea0080051 100644 --- a/fuel-tx/src/transaction/types/create.rs +++ b/fuel-tx/src/transaction/types/create.rs @@ -35,6 +35,7 @@ use fuel_types::{ canonical::SerializedSize, BlockHeight, Bytes32, + ChainId, ContractId, Salt, Word, @@ -42,10 +43,8 @@ use fuel_types::{ #[cfg(feature = "alloc")] use alloc::vec::Vec; -#[cfg(feature = "std")] -use fuel_types::ChainId; -#[cfg(feature = "std")] -use std::collections::HashMap; + +use hashbrown::HashMap; #[cfg(all(test, feature = "std"))] mod ser_de_tests; @@ -66,7 +65,6 @@ pub struct CreateMetadata { pub witnesses_offset_at: Vec, } -#[cfg(feature = "std")] impl CreateMetadata { /// Computes the `Metadata` for the `tx` transaction. pub fn compute(tx: &Create, chain_id: &ChainId) -> Result { @@ -134,7 +132,6 @@ impl Create { } } -#[cfg(feature = "std")] impl crate::UniqueIdentifier for Create { fn id(&self, chain_id: &ChainId) -> crate::TxId { if let Some(id) = self.cached_id() { @@ -189,7 +186,6 @@ impl Chargeable for Create { } impl FormatValidityChecks for Create { - #[cfg(feature = "std")] fn check_signatures(&self, chain_id: &ChainId) -> Result<(), CheckError> { use crate::UniqueIdentifier; @@ -359,7 +355,6 @@ impl FormatValidityChecks for Create { } } -#[cfg(feature = "std")] impl crate::Cacheable for Create { fn is_computed(&self) -> bool { self.metadata.is_some() diff --git a/fuel-tx/src/transaction/types/input.rs b/fuel-tx/src/transaction/types/input.rs index 7151557e9b..a7acace051 100644 --- a/fuel-tx/src/transaction/types/input.rs +++ b/fuel-tx/src/transaction/types/input.rs @@ -808,7 +808,6 @@ impl Input { (*hasher.digest()).into() } - #[cfg(feature = "std")] pub fn is_predicate_owner_valid

( owner: &Address, predicate: P, diff --git a/fuel-tx/src/transaction/types/mint.rs b/fuel-tx/src/transaction/types/mint.rs index c9d9999d93..1a7022a392 100644 --- a/fuel-tx/src/transaction/types/mint.rs +++ b/fuel-tx/src/transaction/types/mint.rs @@ -20,7 +20,6 @@ use fuel_types::{ Bytes32, }; -#[cfg(feature = "std")] use fuel_types::ChainId; #[cfg(feature = "alloc")] @@ -33,7 +32,6 @@ pub(crate) struct MintMetadata { pub outputs_offset_at: Vec, } -#[cfg(feature = "std")] impl MintMetadata { fn compute(tx: &Tx, chain_id: &ChainId) -> Self where @@ -89,7 +87,6 @@ pub struct Mint { pub(crate) metadata: Option, } -#[cfg(feature = "std")] impl crate::UniqueIdentifier for Mint { fn id(&self, chain_id: &ChainId) -> Bytes32 { if let Some(id) = self.cached_id() { @@ -106,7 +103,6 @@ impl crate::UniqueIdentifier for Mint { } impl FormatValidityChecks for Mint { - #[cfg(feature = "std")] fn check_signatures(&self, _: &ChainId) -> Result<(), CheckError> { Ok(()) } @@ -142,7 +138,6 @@ impl FormatValidityChecks for Mint { } } -#[cfg(feature = "std")] impl crate::Cacheable for Mint { fn is_computed(&self) -> bool { self.metadata.is_some() diff --git a/fuel-tx/src/transaction/types/output.rs b/fuel-tx/src/transaction/types/output.rs index baa6599e29..1ab0e2e9d9 100644 --- a/fuel-tx/src/transaction/types/output.rs +++ b/fuel-tx/src/transaction/types/output.rs @@ -223,7 +223,6 @@ impl Output { } /// Prepare the output for VM initialization for script execution - #[cfg(feature = "std")] pub fn prepare_init_script(&mut self) { match self { Output::Change { amount, .. } => { diff --git a/fuel-tx/src/transaction/types/script.rs b/fuel-tx/src/transaction/types/script.rs index a564d99757..e6b425c4ae 100644 --- a/fuel-tx/src/transaction/types/script.rs +++ b/fuel-tx/src/transaction/types/script.rs @@ -34,15 +34,13 @@ use fuel_types::{ fmt_truncated_hex, BlockHeight, Bytes32, + ChainId, Word, }; #[cfg(feature = "alloc")] use alloc::vec::Vec; -#[cfg(feature = "std")] -use fuel_types::ChainId; -#[cfg(feature = "std")] -use std::collections::HashMap; +use hashbrown::HashMap; #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub(crate) struct ScriptMetadata { @@ -95,7 +93,6 @@ impl Default for Script { } } -#[cfg(feature = "std")] impl crate::UniqueIdentifier for Script { fn id(&self, chain_id: &ChainId) -> Bytes32 { if let Some(id) = self.cached_id() { @@ -151,7 +148,6 @@ impl Chargeable for Script { } impl FormatValidityChecks for Script { - #[cfg(feature = "std")] fn check_signatures(&self, chain_id: &ChainId) -> Result<(), CheckError> { use crate::UniqueIdentifier; @@ -211,7 +207,6 @@ impl FormatValidityChecks for Script { } } -#[cfg(feature = "std")] impl crate::Cacheable for Script { fn is_computed(&self) -> bool { self.metadata.is_some() diff --git a/fuel-tx/src/transaction/types/witness.rs b/fuel-tx/src/transaction/types/witness.rs index ca2bcf0b16..8188608dbc 100644 --- a/fuel-tx/src/transaction/types/witness.rs +++ b/fuel-tx/src/transaction/types/witness.rs @@ -3,13 +3,11 @@ use fuel_types::fmt_truncated_hex; use alloc::vec::Vec; -#[cfg(feature = "std")] use crate::{ CheckError, Input, TxId, }; -#[cfg(feature = "std")] use fuel_crypto::{ Message, Signature, @@ -47,7 +45,6 @@ impl Witness { } /// ECRecover an address from a witness - #[cfg(feature = "std")] pub fn recover_witness( &self, txhash: &TxId, diff --git a/fuel-tx/src/transaction/validity.rs b/fuel-tx/src/transaction/validity.rs index 95633bd9e4..adb627944c 100644 --- a/fuel-tx/src/transaction/validity.rs +++ b/fuel-tx/src/transaction/validity.rs @@ -11,19 +11,16 @@ use fuel_types::{ BlockHeight, }; -#[cfg(feature = "std")] use crate::Transaction; -#[cfg(feature = "std")] use fuel_types::{ Address, Bytes32, ChainId, }; +use hashbrown::HashMap; use itertools::Itertools; -#[cfg(feature = "std")] -use std::collections::HashMap; mod error; @@ -69,7 +66,6 @@ impl Input { Ok(()) } - #[cfg(feature = "std")] pub fn check_signature( &self, index: usize, @@ -261,7 +257,6 @@ pub trait FormatValidityChecks { Ok(()) } - #[cfg(feature = "std")] /// Validates that all required signatures are set in the transaction and that they /// are valid. fn check_signatures(&self, chain_id: &ChainId) -> Result<(), CheckError>; @@ -275,7 +270,6 @@ pub trait FormatValidityChecks { ) -> Result<(), CheckError>; } -#[cfg(feature = "std")] impl FormatValidityChecks for Transaction { fn check_signatures(&self, chain_id: &ChainId) -> Result<(), CheckError> { match self { diff --git a/fuel-vm/Cargo.toml b/fuel-vm/Cargo.toml index 946f4cbc4f..a6048ead3a 100644 --- a/fuel-vm/Cargo.toml +++ b/fuel-vm/Cargo.toml @@ -26,6 +26,7 @@ fuel-tx = { workspace = true, default-features = false } fuel-types = { workspace = true, default-features = false } hashbrown = "0.14" itertools = { version = "0.10", default-features = false } +libm = "0.2" paste = "1.0" percent-encoding = { version = "2.3", features = ["alloc"], default-features = false } primitive-types = { version = "0.12", default-features = false } @@ -56,8 +57,8 @@ tokio-rayon = "2.1.0" [features] default = ["std"] -std = ["alloc", "fuel-storage/std", "fuel-tx/builder", "fuel-types/std", "fuel-asm/std", "fuel-tx/std", "itertools/use_std", "thiserror"] -alloc = ["fuel-tx/alloc"] +std = ["alloc", "fuel-storage/std", "fuel-types/std", "fuel-asm/std", "fuel-tx/std", "itertools/use_std", "thiserror"] +alloc = ["fuel-tx/alloc", "fuel-tx/builder"] arbitrary = ["fuel-asm/arbitrary"] profile-gas = ["profile-any"] profile-coverage = ["profile-any"] diff --git a/fuel-vm/src/backtrace.rs b/fuel-vm/src/backtrace.rs index 677681b3c6..dd5dad788b 100644 --- a/fuel-vm/src/backtrace.rs +++ b/fuel-vm/src/backtrace.rs @@ -2,7 +2,10 @@ //! //! As of the moment, doesn't support predicates. -use alloc::vec::Vec; +use alloc::{ + borrow::ToOwned, + vec::Vec, +}; use crate::{ call::CallFrame, diff --git a/fuel-vm/src/checked_transaction.rs b/fuel-vm/src/checked_transaction.rs index e2556763fb..9139738ad4 100644 --- a/fuel-vm/src/checked_transaction.rs +++ b/fuel-vm/src/checked_transaction.rs @@ -19,7 +19,10 @@ use fuel_types::{ ChainId, }; -use alloc::boxed::Box; +use alloc::{ + boxed::Box, + vec::Vec, +}; use core::{ borrow::Borrow, future::Future, diff --git a/fuel-vm/src/checked_transaction/builder.rs b/fuel-vm/src/checked_transaction/builder.rs index 4f4b6ccad1..ebc18eae30 100644 --- a/fuel-vm/src/checked_transaction/builder.rs +++ b/fuel-vm/src/checked_transaction/builder.rs @@ -8,6 +8,10 @@ use crate::{ checked_transaction::CheckPredicates, prelude::*, }; +use fuel_tx::{ + Finalizable, + TransactionBuilder, +}; use fuel_types::BlockHeight; /// Extension trait for [`fuel_tx::TransactionBuilder`] adding finalization methods diff --git a/fuel-vm/src/error.rs b/fuel-vm/src/error.rs index 58a5ba39ab..e0520091eb 100644 --- a/fuel-vm/src/error.rs +++ b/fuel-vm/src/error.rs @@ -7,37 +7,47 @@ use fuel_asm::{ }; use fuel_storage::StorageError; use fuel_tx::CheckError; + +#[cfg(feature = "std")] use thiserror::Error; +use alloc::{ + format, + string::{ + String, + ToString, + }, +}; use core::fmt; /// Interpreter runtime error variants. -#[derive(Debug, Error)] +#[cfg_attr(feature = "std", derive(Error))] +#[derive(Debug)] pub enum InterpreterError { /// The instructions execution resulted in a well-formed panic, caused by an /// explicit instruction. - #[error("Execution error: {0:?}")] + #[cfg_attr(feature = "std", error("Execution error: {0:?}"))] PanicInstruction(PanicInstruction), /// The VM execution resulted in a well-formed panic. This panic wasn't /// caused by an instruction contained in the transaction or a called /// contract. - #[error("Execution error: {0:?}")] + #[cfg_attr(feature = "std", error("Execution error: {0:?}"))] Panic(PanicReason), /// The provided transaction isn't valid. - #[error("Failed to check the transaction: {0}")] - CheckError(#[from] CheckError), + #[cfg_attr(feature = "std", error("Failed to check the transaction: {0}"))] + CheckError(CheckError), /// No transaction was initialized in the interpreter. It cannot provide /// state transitions. - #[error("Execution error")] + #[cfg_attr(feature = "std", error("Execution error"))] NoTransactionInitialized, - #[error("Execution error")] + #[cfg_attr(feature = "std", error("Execution error"))] /// The debug state is not initialized; debug routines can't be called. DebugStateNotInitialized, /// Storage I/O error - #[error("Storage error: {0}")] + #[cfg_attr(feature = "std", error("Storage error: {0}"))] Storage(StorageError), /// Encountered a bug - #[error("Bug: {0}")] + #[cfg_attr(feature = "std", error("Bug: {0}"))] Bug(Bug), } @@ -94,6 +104,12 @@ impl From for InterpreterError { } } +impl From for InterpreterError { + fn from(error: CheckError) -> Self { + Self::CheckError(error) + } +} + impl From for InterpreterError { fn from(error: StorageError) -> Self { let error: RuntimeError = error.into(); @@ -149,9 +165,9 @@ impl From for Unrecoverable { } } -#[derive(Debug)] /// Runtime error description that should either be specified in the protocol or /// halt the execution. +#[derive(Debug)] pub enum RuntimeError { /// Specified error with well-formed fallback strategy, i.e. vm panics. Recoverable(PanicReason), @@ -220,31 +236,50 @@ impl From for RuntimeError { } /// Predicates checking failed -#[derive(Debug, Error)] +#[cfg_attr(feature = "std", derive(Error))] +#[derive(Debug)] pub enum PredicateVerificationFailed { /// The predicate did not use the amount of gas provided - #[error("Predicate used less than the required amount of gas")] + #[cfg_attr( + feature = "std", + error("Predicate used less than the required amount of gas") + )] GasMismatch, /// The transaction doesn't contain enough gas to evaluate the predicate - #[error("Insufficient gas available for single predicate")] + #[cfg_attr( + feature = "std", + error("Insufficient gas available for single predicate") + )] OutOfGas, /// The predicate owner does not correspond to the predicate code - #[error("Predicate owner invalid, doesn't match code root")] + #[cfg_attr( + feature = "std", + error("Predicate owner invalid, doesn't match code root") + )] InvalidOwner, /// The predicate wasn't successfully evaluated to true - #[error("Predicate failed to evaluate")] + #[cfg_attr(feature = "std", error("Predicate failed to evaluate"))] False, /// The predicate gas used was not specified before execution - #[error("Predicate failed to evaluate")] + #[cfg_attr(feature = "std", error("Predicate failed to evaluate"))] GasNotSpecified, /// The transaction doesn't contain enough gas to evaluate all predicates - #[error("Insufficient gas available for all predicates")] + #[cfg_attr( + feature = "std", + error("Insufficient gas available for all predicates") + )] CumulativePredicateGasExceededTxGasLimit, /// The cumulative gas overflowed the u64 accumulator - #[error("Cumulative gas computation overflowed the u64 accumulator")] + #[cfg_attr( + feature = "std", + error("Cumulative gas computation overflowed the u64 accumulator") + )] GasOverflow, /// Predicate verification failed since it attempted to access storage - #[error("Predicate verification failed since it attempted to access storage")] + #[cfg_attr( + feature = "std", + error("Predicate verification failed since it attempted to access storage") + )] Storage, } @@ -389,8 +424,7 @@ impl fmt::Display for Bug { NON_ALPHANUMERIC, }; - let issue_title = - format!("Bug report: {:?} in {}", self.variant, self.location); + let issue_title = format!("Bug report: {:?} in {}", self.variant, self.location); let issue_body = format!( "Error: {:?} {}\nLocation: {}\nVersion: {} {}\n", diff --git a/fuel-vm/src/interpreter.rs b/fuel-vm/src/interpreter.rs index b9305f6879..20e108113e 100644 --- a/fuel-vm/src/interpreter.rs +++ b/fuel-vm/src/interpreter.rs @@ -8,7 +8,10 @@ use crate::{ context::Context, state::Debugger, }; -use alloc::vec::Vec; +use alloc::{ + borrow::ToOwned, + vec::Vec, +}; use core::{ mem, ops::Index, diff --git a/fuel-vm/src/interpreter/constructors.rs b/fuel-vm/src/interpreter/constructors.rs index 5ff7d9700f..a1710c3273 100644 --- a/fuel-vm/src/interpreter/constructors.rs +++ b/fuel-vm/src/interpreter/constructors.rs @@ -17,6 +17,8 @@ use crate::{ storage::MemoryStorage, }; +use alloc::vec; + #[cfg(feature = "profile-any")] use crate::profiler::ProfileReceiver; diff --git a/fuel-vm/src/interpreter/diff/storage.rs b/fuel-vm/src/interpreter/diff/storage.rs index f50c4b577e..56dc233bf4 100644 --- a/fuel-vm/src/interpreter/diff/storage.rs +++ b/fuel-vm/src/interpreter/diff/storage.rs @@ -296,7 +296,7 @@ where fn get( &self, key: &::Key, - ) -> Result::OwnedValue>>, StorageError> + ) -> Result::OwnedValue>>, StorageError> { >::get(&self.0, key) } @@ -413,7 +413,7 @@ where id: &ContractId, start_key: &Bytes32, range: Word, - ) -> Result>>, StorageError> { + ) -> Result>>, StorageError> { self.0.merkle_contract_state_range(id, start_key, range) } diff --git a/fuel-vm/src/interpreter/executors/instruction.rs b/fuel-vm/src/interpreter/executors/instruction.rs index 96b5362b04..e8f23c7125 100644 --- a/fuel-vm/src/interpreter/executors/instruction.rs +++ b/fuel-vm/src/interpreter/executors/instruction.rs @@ -907,7 +907,13 @@ fn checked_nth_root(target: u64, nth_root: u64) -> Option { // Use floating point operation to get an approximation for the starting point. // This is at most off by one in either direction. - let guess = (target as f64).powf((nth_root as f64).recip()) as u64; + + #[cfg(feature = "std")] + let powf = f64::powf; + #[cfg(not(feature = "std"))] + let powf = libm::pow; + + let guess = powf(target as f64, (nth_root as f64).recip()) as u64; debug_assert!(guess != 0, "This should never occur for {{target, n}} > 1"); diff --git a/fuel-vm/src/storage/memory.rs b/fuel-vm/src/storage/memory.rs index ca45486f9d..ffa4ca01b5 100644 --- a/fuel-vm/src/storage/memory.rs +++ b/fuel-vm/src/storage/memory.rs @@ -436,7 +436,7 @@ impl InterpreterStorage for MemoryStorage { range: Word, ) -> Result, StorageError> { let mut all_set_key = true; - let mut values: std::collections::HashSet<_> = + let mut values: hashbrown::HashSet<_> = core::iter::successors(Some(**start_key), |n| { let mut n = *n; if add_one(&mut n) { From 3f69a4040dbb7496a14fa3d1bd31f4599c47b61e Mon Sep 17 00:00:00 2001 From: Hannes Karppila Date: Wed, 13 Sep 2023 15:35:37 +0300 Subject: [PATCH 18/44] Toml fmt --- fuel-tx/Cargo.toml | 2 +- fuel-vm/Cargo.toml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/fuel-tx/Cargo.toml b/fuel-tx/Cargo.toml index 9a8b27ff76..4923327202 100644 --- a/fuel-tx/Cargo.toml +++ b/fuel-tx/Cargo.toml @@ -18,8 +18,8 @@ fuel-merkle = { workspace = true, default-features = false, optional = true } fuel-types = { workspace = true, default-features = false } hashbrown = { version = "0.14", optional = true } itertools = { version = "0.10", default-features = false, optional = true } -num_enum = { version = "0.7", default-features = false, optional = true } num-integer = { version = "0.1", default-features = false, optional = true } +num_enum = { version = "0.7", default-features = false, optional = true } rand = { version = "0.8", default-features = false, features = ["std_rng"], optional = true } serde = { version = "1.0", default-features = false, features = ["alloc", "derive"], optional = true } serde_json = { version = "1.0", default-features = false, features = ["alloc"], optional = true } diff --git a/fuel-vm/Cargo.toml b/fuel-vm/Cargo.toml index a6048ead3a..c57a2d9e5a 100644 --- a/fuel-vm/Cargo.toml +++ b/fuel-vm/Cargo.toml @@ -23,7 +23,7 @@ fuel-crypto = { workspace = true, default-features = false } fuel-merkle = { workspace = true, default-features = false } fuel-storage = { workspace = true } fuel-tx = { workspace = true, default-features = false } -fuel-types = { workspace = true, default-features = false } +fuel-types = { workspace = true, default-features = false } hashbrown = "0.14" itertools = { version = "0.10", default-features = false } libm = "0.2" @@ -34,7 +34,7 @@ rand = { version = "0.8", optional = true } serde = { version = "1.0", features = ["derive", "rc"], optional = true } sha3 = { version = "0.10", default-features = false } static_assertions = "1.1" -strum = { version = "0.24", features = ["derive"], default-features = false } +strum = { version = "0.24", features = ["derive"], default-features = false } tai64 = { version = "4.0", default-features = false } thiserror = { version = "1.0", optional = true } @@ -58,7 +58,7 @@ tokio-rayon = "2.1.0" [features] default = ["std"] std = ["alloc", "fuel-storage/std", "fuel-types/std", "fuel-asm/std", "fuel-tx/std", "itertools/use_std", "thiserror"] -alloc = ["fuel-tx/alloc", "fuel-tx/builder"] +alloc = ["fuel-tx/alloc", "fuel-tx/builder"] arbitrary = ["fuel-asm/arbitrary"] profile-gas = ["profile-any"] profile-coverage = ["profile-any"] From a2ee758306affecb50c473f4c9e2084aac74c086 Mon Sep 17 00:00:00 2001 From: Hannes Karppila Date: Wed, 13 Sep 2023 18:00:12 +0300 Subject: [PATCH 19/44] Move more stuff out of #[cfg(feature = "std")] --- fuel-tx/src/builder.rs | 29 ++++------------------- fuel-tx/src/lib.rs | 2 +- fuel-tx/src/transaction.rs | 14 +++++------ fuel-tx/src/transaction/id.rs | 2 -- fuel-tx/src/transaction/metadata.rs | 1 - fuel-tx/src/transaction/types/create.rs | 18 ++------------ fuel-tx/src/transaction/validity.rs | 2 -- fuel-tx/src/transaction/validity/error.rs | 19 --------------- fuel-tx/test-helpers/src/lib.rs | 2 +- fuel-vm/src/lib.rs | 3 +++ 10 files changed, 19 insertions(+), 73 deletions(-) diff --git a/fuel-tx/src/builder.rs b/fuel-tx/src/builder.rs index 002e3c562f..1e586de26e 100644 --- a/fuel-tx/src/builder.rs +++ b/fuel-tx/src/builder.rs @@ -11,7 +11,6 @@ use crate::{ Executable, Script, }, - Cacheable, ConsensusParameters, ContractParameters, FeeParameters, @@ -28,8 +27,10 @@ use crate::{ Witness, }; -#[cfg(feature = "std")] -use crate::Signable; +use crate::{ + Cacheable, + Signable, +}; use fuel_crypto::SecretKey; use fuel_types::{ @@ -59,13 +60,8 @@ where { } -#[cfg(feature = "std")] pub trait BuildableStd: Signable + Cacheable {} -#[cfg(not(feature = "std"))] -pub trait BuildableSet: BuildableAloc {} - -#[cfg(feature = "std")] pub trait BuildableSet: BuildableAloc + BuildableStd {} pub trait Buildable @@ -110,12 +106,8 @@ impl BuildableAloc for T where { } -#[cfg(feature = "std")] impl BuildableStd for T where T: Signable + Cacheable {} -#[cfg(feature = "std")] impl BuildableSet for T where T: BuildableAloc + BuildableStd {} -#[cfg(not(feature = "std"))] -impl BuildableSet for T where T: BuildableAloc {} impl Buildable for T where T: BuildableSet {} #[derive(Debug, Clone)] @@ -326,7 +318,6 @@ impl TransactionBuilder { self } - #[cfg(feature = "std")] pub fn add_unsigned_coin_input( &mut self, secret: SecretKey, @@ -353,7 +344,7 @@ impl TransactionBuilder { self } - #[cfg(all(feature = "rand", feature = "std"))] + #[cfg(all(feature = "rand"))] pub fn add_random_fee_input(&mut self) -> &mut Self { use rand::{ Rng, @@ -370,7 +361,6 @@ impl TransactionBuilder { ) } - #[cfg(feature = "std")] pub fn add_unsigned_message_input( &mut self, secret: SecretKey, @@ -421,7 +411,6 @@ impl TransactionBuilder { } /// Adds a secret to the builder, and adds a corresponding witness if it's a new entry - #[cfg(feature = "std")] fn upsert_secret(&mut self, secret_key: SecretKey) -> u8 { let witness_len = self.witnesses().len() as u8; @@ -434,7 +423,6 @@ impl TransactionBuilder { *witness_index } - #[cfg(feature = "std")] fn prepare_finalize(&mut self) { if self.should_prepare_predicate { self.tx.prepare_init_predicate(); @@ -445,7 +433,6 @@ impl TransactionBuilder { } } - #[cfg(feature = "std")] fn finalize_inner(&mut self) -> Tx { self.prepare_finalize(); @@ -461,7 +448,6 @@ impl TransactionBuilder { tx } - #[cfg(feature = "std")] pub fn finalize_without_signature_inner(&mut self) -> Tx { self.prepare_finalize(); @@ -487,7 +473,6 @@ pub trait Finalizable { fn finalize_without_signature(&mut self) -> Tx; } -#[cfg(feature = "std")] impl Finalizable for TransactionBuilder { fn finalize(&mut self) -> Mint { let mut tx = core::mem::take(&mut self.tx); @@ -501,7 +486,6 @@ impl Finalizable for TransactionBuilder { } } -#[cfg(feature = "std")] impl Finalizable for TransactionBuilder { fn finalize(&mut self) -> Create { self.finalize_inner() @@ -512,7 +496,6 @@ impl Finalizable for TransactionBuilder { } } -#[cfg(feature = "std")] impl Finalizable