Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

no_std fuel-crypto #578

Merged
merged 22 commits into from
Sep 15, 2023
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Dentosal marked this conversation as resolved.
Show resolved Hide resolved
- command: check
args: --target wasm32-unknown-unknown -p fuel-crypto --no-default-features
- command: check
Expand All @@ -70,6 +70,10 @@ 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
args: check
- command: test
Expand Down
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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]

Expand Down
12 changes: 5 additions & 7 deletions fuel-crypto/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,18 @@ 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 }
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 }
# `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"] }
Expand All @@ -29,18 +31,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"]
alloc = ["rand?/alloc", "secp256k1/alloc", "fuel-types/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", "secp256k1", "coins-bip39", "fuel-types/std", "lazy_static", "rand?/std_rng", "serde?/default"]
test-helpers = []

[[bench]]
Expand Down
36 changes: 18 additions & 18 deletions fuel-crypto/benches/signature.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
Expand All @@ -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");
Expand Down Expand Up @@ -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(
Voxelot marked this conversation as resolved.
Show resolved Hide resolved
digest.clone(),
signature,
*recovery_id,
)
})
},
);
Expand Down
2 changes: 1 addition & 1 deletion fuel-crypto/src/ed25519.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ use fuel_types::{
};

use crate::{
message::Message,
Error,
Message,
};

/// Verify a signature against a message digest and a public key.
Expand Down
19 changes: 0 additions & 19 deletions fuel-crypto/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,31 +51,12 @@ impl From<Infallible> for Error {
mod use_std {
use super::*;
use coins_bip39::MnemonicError;
use secp256k1::Error as Secp256k1Error;
use std::{
error,
fmt,
io,
};

impl From<Secp256k1Error> 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<MnemonicError> for Error {
fn from(_: MnemonicError) -> Self {
Self::InvalidMnemonic
Expand Down
30 changes: 12 additions & 18 deletions fuel-crypto/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
#![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)]
Expand All @@ -14,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)]
Expand All @@ -37,9 +33,20 @@ mod error;
mod hasher;
mod message;
mod mnemonic;
mod secp256;

pub mod ed25519;
pub mod secp256r1;

pub use secp256::backend::{
k1 as secp256k1,
r1 as secp256r1,
};

pub use secp256::{
PublicKey,
SecretKey,
Signature,
};

#[cfg(test)]
mod tests;
Expand All @@ -50,16 +57,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::*;
17 changes: 3 additions & 14 deletions fuel-crypto/src/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,19 +116,8 @@ impl fmt::Display for Message {
}

#[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")
}
impl From<&Message> for secp256k1::Message {
fn from(message: &Message) -> Self {
secp256k1::Message::from_slice(&*message.0).expect("length always matches")
}
}
10 changes: 10 additions & 0 deletions fuel-crypto/src/secp256.rs
Original file line number Diff line number Diff line change
@@ -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;
73 changes: 73 additions & 0 deletions fuel-crypto/src/secp256/backend.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
//! 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,
secp256::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);
}
}
}
Loading
Loading