Skip to content

Commit

Permalink
Secp256r1 signature recovery and Ed25519 verification (#486)
Browse files Browse the repository at this point in the history
* Add support for Secp256r1 signature recovery

* Move crypto impls to fuel-crypto, add ED19 opcode

* Fix formatting and imports after merge

* Fix ed25519 tests

* Use uncompressed public keys for secp256r1

* Signature normalization

* Update newly merged tests to use the new eck1 instruction

* Workaround on self-dependency feature-enable for tests

* Fix changelog entries that merge messed up

* Fix entry order in changelog

* Fix no_std build

* Use verify_strict for ed25519

* Document panic conditions of encode_signature

* fmt

* Added `test-helpers` feature to `fuel-crypto`

---------

Co-authored-by: green <xgreenx9999@gmail.com>
  • Loading branch information
Dentosal and xgreenx authored Jul 12, 2023
1 parent 18a2e4a commit 6a3964a
Show file tree
Hide file tree
Showing 30 changed files with 778 additions and 318 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,16 @@ and this project adheres to [Semantic Versioning](http://semver.org/).

Description of the upcoming release here.

### Added

- [#486](https://github.com/FuelLabs/fuel-vm/pull/486/): Adds `ed25519` signature verification and `secp256r1` signature recovery to `fuel-crypto`, and corresponding opcodes `ED19` and `ECR1` to `fuel-vm`.

### Removed

#### Breaking

- [#486](https://github.com/FuelLabs/fuel-vm/pull/486/): Removes apparently unused `Keystore` and `Signer` traits from `fuel-crypto`. Also renames `ECR` opcode to `ECK1`.

### Fixed

#### Breaking
Expand Down
2 changes: 1 addition & 1 deletion fuel-asm/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ let program = vec![
op::addi(0x10, 0x07, 1), // set r[0x10] := `$hp + 1` (allocated heap)
op::move_(0x11, 0x04), // set r[0x11] := $ssp
op::add(0x12, 0x04, 0x20), // set r[0x12] := `$ssp + r[0x20]`
op::ecr(0x10, 0x11, 0x12), // recover public key in memory[r[0x10], 64]
op::eck1(0x10, 0x11, 0x12),// recover public key in memory[r[0x10], 64]
op::ret(0x01), // return `1`
];

Expand Down
22 changes: 13 additions & 9 deletions fuel-asm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -188,13 +188,17 @@ impl_instructions! {
"Transfer coins to a variable output."
0x3D TRO tro [contract_id_addr: RegId output_index: RegId amount: RegId asset_id_addr: RegId]
"The 64-byte public key (x, y) recovered from 64-byte signature on 32-byte message."
0x3E ECR ecr [dst_addr: RegId sig_addr: RegId msg_hash_addr: RegId]
0x3E ECK1 eck1 [dst_addr: RegId sig_addr: RegId msg_hash_addr: RegId]
"The 64-byte Secp256r1 public key (x, y) recovered from 64-byte signature on 32-byte message."
0x3F ECR1 ecr1 [dst_addr: RegId sig_addr: RegId msg_hash_addr: RegId]
"Verify ED25519 public key and signature match a 32-byte message."
0x40 ED19 ed19 [pub_key_addr: RegId sig_addr: RegId msg_hash_addr: RegId]
"The keccak-256 hash of a slice."
0x3F K256 k256 [dst_addr: RegId src_addr: RegId len: RegId]
0x41 K256 k256 [dst_addr: RegId src_addr: RegId len: RegId]
"The SHA-2-256 hash of a slice."
0x40 S256 s256 [dst_addr: RegId src_addr: RegId len: RegId]
0x42 S256 s256 [dst_addr: RegId src_addr: RegId len: RegId]
"Get timestamp of block at given height."
0x41 TIME time [dst: RegId heigth: RegId]
0x43 TIME time [dst: RegId heigth: RegId]

"Performs no operation."
0x47 NOOP noop []
Expand Down Expand Up @@ -489,11 +493,11 @@ impl Opcode {
ADD | AND | DIV | EQ | EXP | GT | LT | MLOG | MROO | MOD | MOVE | MUL
| NOT | OR | SLL | SRL | SUB | XOR | WDCM | WQCM | WDOP | WQOP | WDML
| WQML | WDDV | WQDV | WDMD | WQMD | WDAM | WQAM | WDMM | WQMM | RET
| ALOC | MCL | MCP | MEQ | ECR | K256 | S256 | NOOP | FLAG | ADDI | ANDI
| DIVI | EXPI | MODI | MULI | MLDV | ORI | SLLI | SRLI | SUBI | XORI
| JNEI | LB | LW | SB | SW | MCPI | MCLI | GM | MOVI | JNZI | JI | JMP
| JNE | JMPF | JMPB | JNZF | JNZB | JNEF | JNEB | CFEI | CFSI | CFE | CFS
| GTF => true,
| ALOC | MCL | MCP | MEQ | ECK1 | ECR1 | ED19 | K256 | S256 | NOOP | FLAG
| ADDI | ANDI | DIVI | EXPI | MODI | MULI | MLDV | ORI | SLLI | SRLI
| SUBI | XORI | JNEI | LB | LW | SB | SW | MCPI | MCLI | GM | MOVI | JNZI
| JI | JMP | JNE | JMPF | JMPB | JNZF | JNZB | JNEF | JNEB | CFEI | CFSI
| CFE | CFS | GTF => true,
_ => false,
}
}
Expand Down
2 changes: 1 addition & 1 deletion fuel-asm/src/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@
//! op::addi(0x10, 0x07, 1), // set r[0x10] := `$hp + 1` (allocated heap)
//! op::move_(0x11, 0x04), // set r[0x11] := $ssp
//! op::add(0x12, 0x04, 0x20), // set r[0x12] := `$ssp + r[0x20]`
//! op::ecr(0x10, 0x11, 0x12), // recover public key in memory[r[0x10], 64]
//! op::eck1(0x10, 0x11, 0x12),// recover public key in memory[r[0x10], 64]
//! op::ret(0x01), // return `1`
//! ];
//! ```
Expand Down
6 changes: 5 additions & 1 deletion fuel-crypto/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,11 @@ description = "Fuel cryptographic primitives."
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 = "1.0", default-features = false, features = ["u64_backend"] }
fuel-types = { workspace = true, default-features = false }
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 }
Expand All @@ -25,7 +28,7 @@ zeroize = { version = "1.5", features = ["derive"] }
[dev-dependencies]
bincode = { workspace = true }
criterion = "0.4"
fuel-crypto = { path = ".", default-features = false, features = ["random"] }
fuel-crypto = { workspace = true, features = ["random", "test-helpers"] }
k256 = { version = "0.11", features = [ "ecdsa" ] }
sha2 = "0.10"

Expand All @@ -38,6 +41,7 @@ serde = ["dep:serde", "fuel-types/serde"]
# 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"]
test-helpers = []

[[bench]]
name = "signature"
Expand Down
33 changes: 33 additions & 0 deletions fuel-crypto/src/ed25519.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//! ED25519 signature verification

use ed25519_dalek::Signature;
use fuel_types::{
Bytes32,
Bytes64,
};

use crate::{
Error,
Message,
};

/// Verify a signature against a message digest and a public key.
pub fn verify(
pub_key: &Bytes32,
signature: &Bytes64,
message: &Message,
) -> Result<(), Error> {
let Ok(signature) = Signature::from_bytes(&**signature) else {
return Err(Error::InvalidSignature);
};

let Ok(pub_key) = ed25519_dalek::PublicKey::from_bytes(&**pub_key) else {
return Err(Error::InvalidPublicKey);
};

if pub_key.verify_strict(&**message, &signature).is_ok() {
Ok(())
} else {
Err(Error::InvalidSignature)
}
}
3 changes: 3 additions & 0 deletions fuel-crypto/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ pub enum Error {
/// Invalid secp256k1 signature
InvalidSignature,

/// Coudln't sign the message
FailedToSign,

/// The provided key wasn't found
KeyNotFound,

Expand Down
43 changes: 0 additions & 43 deletions fuel-crypto/src/keystore.rs

This file was deleted.

31 changes: 21 additions & 10 deletions fuel-crypto/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
#![deny(unsafe_code)]
#![deny(unused_crate_dependencies)]

#[cfg(test)]
// 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;
Expand All @@ -30,24 +34,31 @@ pub use rand;

mod error;
mod hasher;
mod keystore;
mod message;
mod mnemonic;
mod public;
mod secret;
mod signature;
mod signer;

pub mod ed25519;
pub mod secp256r1;

#[cfg(test)]
mod tests;

pub use error::Error;
pub use hasher::Hasher;
pub use keystore::Keystore;
pub use message::Message;

#[cfg(all(feature = "std", feature = "random"))]
pub use mnemonic::generate_mnemonic_phrase;
pub use public::PublicKey;
pub use secret::SecretKey;
pub use signature::Signature;
pub use signer::Signer;

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::*;
2 changes: 1 addition & 1 deletion fuel-crypto/src/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use core::{
};
pub use fuel_types::Bytes32;

/// Normalized signature message
/// Normalized (hashed) message authenticated by a signature
#[derive(Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(transparent)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use fuel_types::{
Bytes64,
};

/// Asymmetric public key
/// Asymmetric secp256k1 public key
#[derive(Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(transparent)]
Expand Down
File renamed without changes.
File renamed without changes.
Loading

0 comments on commit 6a3964a

Please sign in to comment.