Skip to content

Commit

Permalink
Introduce crypto module and expand cryptographic functions (#6837)
Browse files Browse the repository at this point in the history
## Description

This PR replaces #5747 and intends
to introduce the crypto module.

> Note: #6832 will also use the
crypto module.

The std-lib currently contains the `ecr.sw` and `vm/evm/ecr.sw` files
which have the following functions:

- ec_recover()
- ec_recover_r1()
- ed_verify()
- ec_recover_address()
- ec_recover_address_r1()
- ec_recover_evm_address()

There are a number of issues with this including no type safety for
signatures from different elliptic curves, functions split across
multiple files, poor naming, and generic arguments. All of these are
resolved by this PR which deprecates both `ecr.sw` files and replaces
them with a crypto module which syntactically matches Rust.

The following new types are introduced:

* `PublicKey` - An Asymmetric public key, supporting both 64 and 32-byte
public keys
* `Message` - Hashed message authenticated by a signature type that
handles variable lengths
* `Secp256k1` - A secp256k1 signature
* `Secp256r1` - A secp256r1 signature
* `Ed25519` -  An ed25519 signature
* `Signature` - An ECDSA signature

All original functionality is retained with the new module:

* `Secp256k1::recover()` - Recovers a public key.
* `Secp256r1::recover()` - Recovers a public key.
* `Secp256k1::address()` - Recovers an address.
* `Secp256r1::address()` - Recovers an address.
* `Secp256k1::evm_address()` - Recovers an EVM address.
* `Ed25519::verify()` - Verify that a signature matches the given public
key.

The following new functionality has been added:

* `Secp256k1::verify()` - Verify that a signature matches the given
public key.
* `Secp256r1::verify()` - Verify that a signature matches the given
public key.
* `Secp256k1::verify_address()` - Verify that a signature matches the
given address.
* `Secp256r1::verify_address()` - Verify that a signature matches the
given address.
* `Secp256k1::verify_evm_address()` - Verify that a signature matches
the given EVM address.
* `Secp256r1::verify_evm_address()` - Verify that a signature matches
the given EVM address.
* `Secp256r1::evm_address()` - Recovers an EVM address.

The following functions have been deprecated: 

- `std::ecr::ec_recover()`
- `std::ecr::ec_recover_r1()`
- `std::ecr::ed_verify()`
- `std::ecr::ec_recover_address()`
- `std::ecr::ec_recover_address_r1()`
- `std::vm::evm::ecr::ec_recover_evm_address()`

Example of changes for recovering a public key:
```sway
// Before
fn foo(signature: B512, message: b256) {
     let recovered_public_key: B512 = ec_recover(signature, message).unwrap();
}

// After
fn bar(signature: Signature, message: Message) {
     let recovered_public_key: PublicKey = signature.recover(message).unwrap();
}
```

Example of changes for recovering an Address:
```sway
// Before
fn foo(signature: B512, message: b256) {
     let recovered_address: Address = ec_recover_address(signature, message).unwrap();
}

// After
fn bar(signature: Signature, message: Message) {
     let recovered_address: Address = signature.address(message).unwrap();
}
```

Complete recovery example using the `Signature` type:

```sway
use std::crypto::{Message, PublicKey, Secp256r1, Signature};

fn foo() {
    let secp256r1_signature = Signature::Secp256r1(Secp256r1::from((
        0xbd0c9b8792876712afadbff382e1bf31c44437823ed761cc3600d0016de511ac,
        0x44ac566bd156b4fc71a4a4cb2655d3da360c695edb27dc3b64d621e122fea23d,
    )));
    let signed_message = Message::from(0x1e45523606c96c98ba970ff7cf9511fab8b25e1bcd52ced30b81df1e4a9c4323);
    
    // A recovered public key pair.
    let secp256r1_public_key = secp256r1_signature.recover(signed_message);
    assert(secp256r1_public_key.is_ok());
    assert(
        secp256r1_public_key
            .unwrap() == PublicKey::from((
            0xd6ea577a54ae42411fbc78d686d4abba2150ca83540528e4b868002e346004b2,
            0x62660ecce5979493fe5684526e8e00875b948e507a89a47096bc84064a175452,
        )),
    );
}
```

## Checklist

- [x] I have linked to any relevant issues.
- [x] I have commented my code, particularly in hard-to-understand
areas.
- [x] I have updated the documentation where relevant (API docs, the
reference, and the Sway book).
- [x] If my change requires substantial documentation changes, I have
[requested support from the DevRel
team](https://github.com/FuelLabs/devrel-requests/issues/new/choose)
- [x] I have added tests that prove my fix is effective or that my
feature works.
- [x] I have added (or requested a maintainer to add) the necessary
`Breaking*` or `New Feature` labels where relevant.
- [x] I have done my best to ensure that my PR adheres to [the Fuel Labs
Code Review
Standards](https://github.com/FuelLabs/rfcs/blob/master/text/code-standards/external-contributors.md).
- [x] I have requested a review from the relevant team or maintainers.
  • Loading branch information
bitzoic authored Jan 25, 2025
1 parent e025355 commit b03c76e
Show file tree
Hide file tree
Showing 26 changed files with 4,266 additions and 16 deletions.
50 changes: 47 additions & 3 deletions docs/book/src/blockchain-development/hashing_and_cryptography.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,54 @@ The Sway standard library provides easy access to a selection of cryptographic h
{{#include ../../../../examples/hashing/src/main.sw}}
```

## Signature Recovery
## Cryptographic Signature Recovery and Verification

Fuel supports 3 asymmetric cryptographic signature schemes; `Secp256k1`, `Secp256r1`, and `Ed25519`.

### Public Key Recovery

Given a `Signature` and a sign `Message`, you can recover a `PublicKey`.

```sway
{{#include ../../../../examples/signatures/src/main.sw:public_key_recovery}}
```

### Signed Message Address Recovery

Given a `Signature` and signed `Message`, you can recover a Fuel `Address`.

```sway
{{#include ../../../../examples/signatures/src/main.sw}}
{{#include ../../../../examples/signatures/src/main.sw:address_recovery}}
```

> **Note**: Recovery of EVM addresses is also supported via `std::vm::evm`.
#### Signed Message EVM Address Recovery

Recovery of EVM addresses is also supported.

```sway
{{#include ../../../../examples/signatures/src/main.sw:evm_address_recovery}}
```

### Public Key Signature Verification

Given a `Signature`, `PublicKey`, and `Message`, you can verify that the message was signed using the public key.

```sway
{{#include ../../../../examples/signatures/src/main.sw:signature_verification}}
```

### Address Signature Verification

Given a `Signature`, `Address`, and `Message`, you can verify that the message was signed by the address.

```sway
{{#include ../../../../examples/signatures/src/main.sw:address_verification}}
```

#### EVM Address Signature Verification

Recovery of EVM addresses verification is also supported.

```sway
{{#include ../../../../examples/signatures/src/main.sw:evm_address_verification}}
```
222 changes: 209 additions & 13 deletions examples/signatures/src/main.sw
Original file line number Diff line number Diff line change
@@ -1,22 +1,218 @@
script;

use std::{b512::B512, ecr::{ec_recover, ec_recover_address, EcRecoverError}};
fn main() {}

const MSG_HASH = 0xee45573606c96c98ba970ff7cf9511f1b8b25e6bcd52ced30b89df1e4a9c4323;
use std::{
crypto::{
ed25519::*,
message::*,
public_key::*,
secp256k1::*,
secp256r1::*,
signature::*,
},
hash::{
Hash,
sha256,
},
vm::evm::evm_address::EvmAddress,
};

fn main() {
let hi = 0xbd0c9b8792876713afa8bff383eebf31c43437823ed761cc3600d0016de5110c;
let lo = 0x44ac566bd156b4fc71a4a4cb2655d3dd360c695edb17dc3b64d611e122fea23d;
let signature: B512 = B512::from((hi, lo));
fn public_key_recovery() {
// ANCHOR: public_key_recovery
// Secp256rk1 Public Key Recovery
let secp256k1_signature: Signature = Signature::Secp256k1(Secp256k1::from((
0x61f3caf4c0912cec69ff0b226638d397115c623a7f057914d48a7e4daf1cf6d8,
0x2555de81cd3a40382d3d64eb1c77e463eea5a76d65ec85f283e0b3d568352678,
)));
let signed_message = Message::from(0xa13f4ab54057ce064d3dd97ac3ff30ed704e73956896c03650fe59b1a561fe15);
// A recovered public key pair.
let secp256k1_public_key = secp256k1_signature.recover(signed_message);
assert(secp256k1_public_key.is_ok());
assert(
secp256k1_public_key
.unwrap() == PublicKey::from((
0x41a55558a3486b6ee3878f55f16879c0798afd772c1506de44aba90d29b6e65c,
0x341ca2e0a3d5827e78d838e35b29bebe2a39ac30b58999e1138c9467bf859965,
)),
);

// Secp256r1 Public Key Recovery
let secp256r1_signature = Signature::Secp256r1(Secp256r1::from((
0xbd0c9b8792876712afadbff382e1bf31c44437823ed761cc3600d0016de511ac,
0x44ac566bd156b4fc71a4a4cb2655d3da360c695edb27dc3b64d621e122fea23d,
)));
let signed_message = Message::from(0x1e45523606c96c98ba970ff7cf9511fab8b25e1bcd52ced30b81df1e4a9c4323);
// A recovered public key pair.
let public_key = ec_recover(signature, MSG_HASH);
let secp256r1_public_key = secp256r1_signature.recover(signed_message);
assert(secp256r1_public_key.is_ok());
assert(
secp256r1_public_key
.unwrap() == PublicKey::from((
0xd6ea577a54ae42411fbc78d686d4abba2150ca83540528e4b868002e346004b2,
0x62660ecce5979493fe5684526e8e00875b948e507a89a47096bc84064a175452,
)),
);
// ANCHOR_END: public_key_recovery
}

fn address_recovery() {
// ANCHOR: address_recovery
// Secp256k1 Address Recovery
let secp256k1_signature = Signature::Secp256k1(Secp256k1::from((
0x61f3caf4c0912cec69ff0b226638d397115c623a7f057914d48a7e4daf1cf6d8,
0x2555de81cd3a40382d3d64eb1c77e463eea5a76d65ec85f283e0b3d568352678,
)));
let signed_message = Message::from(0xa13f4ab54057ce064d3dd97ac3ff30ed704e73956896c03650fe59b1a561fe15);
// A recovered Fuel address.
let result_address: Result<Address, EcRecoverError> = ec_recover_address(signature, MSG_HASH);
if let Ok(address) = result_address {
log(address.bits());
} else {
revert(0);
}
let secp256k1_address = secp256k1_signature.address(signed_message);
assert(secp256k1_address.is_ok());
assert(
secp256k1_address
.unwrap() == Address::from(0x02844f00cce0f608fa3f0f7408bec96bfd757891a6fda6e1fa0f510398304881),
);

// Secp256r1 Address Recovery
let secp256r1_signature = Signature::Secp256r1(Secp256r1::from((
0xbd0c9b8792876713afa8bf3383eebf31c43437823ed761cc3600d0016de5110c,
0x44ac566bd156b4fc71a4a4cb2655d3dd360c695edb17dc3b64d611e122fea23d,
)));
let signed_message = Message::from(0xee45573606c96c98ba970ff7cf9511f1b8b25e6bcd52ced30b89df1e4a9c4323);
// A recovered Fuel address.
let secp256r1_address = secp256r1_signature.address(signed_message);
assert(secp256r1_address.is_ok());
assert(
secp256r1_address
.unwrap() == Address::from(0xb4a5fabee8cc852084b71f17107e9c18d682033a58967027af0ab01edf2f9a6a),
);

// ANCHOR_END: address_recovery
}

fn evm_address_recovery() {
// ANCHOR: evm_address_recovery
// Secp256k1 EVM Address Recovery
let secp256k1_signature = Signature::Secp256k1(Secp256k1::from((
0xbd0c9b8792876713afa8bff383eebf31c43437823ed761cc3600d0016de5110c,
0x44ac566bd156b4fc71a4a4cb2655d3dd360c695edb17dc3b64d611e122fea23d,
)));
let signed_message = Message::from(0xee45573606c96c98ba970ff7cf9511f1b8b25e6bcd52ced30b89df1e4a9c4323);
// A recovered EVM address.
let secp256k1_evm_address = secp256k1_signature.evm_address(signed_message);
assert(secp256k1_evm_address.is_ok());
assert(
secp256k1_evm_address
.unwrap() == EvmAddress::from(0x0000000000000000000000000ec44cf95ce5051ef590e6d420f8e722dd160ecb),
);

// Secp256r1 EVM Address Recovery
let secp256r1_signature = Signature::Secp256r1(Secp256r1::from((
0x62CDC20C0AB6AA7B91E63DA9917792473F55A6F15006BC99DD4E29420084A3CC,
0xF4D99AF28F9D6BD96BDAAB83BFED99212AC3C7D06810E33FBB14C4F29B635414,
)));
let signed_message = Message::from(0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563);
// A recovered EVM address.
let secp256r1_evm_address = secp256r1_signature.evm_address(signed_message);
assert(secp256r1_evm_address.is_ok());
assert(
secp256r1_evm_address
.unwrap() == EvmAddress::from(0x000000000000000000000000408eb2d97ef0beda0a33848d9e052066667cb00a),
);
// ANCHOR_END: evm_address_recovery
}

fn signature_verification() {
// ANCHOR: signature_verification
// Secp256k1 Signature Verification
let secp256k1_signature = Signature::Secp256k1(Secp256k1::from((
0x61f3caf4c0912cec69ff0b226638d397115c623a7f057914d48a7e4daf1cf6d8,
0x2555de81cd3a40382d3d64eb1c77e463eea5a76d65ec85f283e0b3d568352678,
)));
let secp256k1_public_key = PublicKey::from((
0x41a55558a3486b6ee3878f55f16879c0798afd772c1506de44aba90d29b6e65c,
0x341ca2e0a3d5827e78d838e35b29bebe2a39ac30b58999e1138c9467bf859965,
));
let signed_message = Message::from(0xa13f4ab54057ce064d3dd97ac3ff30ed704e73956896c03650fe59b1a561fe15);
// A verified public key
let secp256k1_verified = secp256k1_signature.verify(secp256k1_public_key, signed_message);
assert(secp256k1_verified.is_ok());

// Secp256r1 Signature Verification
let secp256r1_signature = Signature::Secp256r1(Secp256r1::from((
0xbd0c9b8792876712afadbff382e1bf31c44437823ed761cc3600d0016de511ac,
0x44ac566bd156b4fc71a4a4cb2655d3da360c695edb27dc3b64d621e122fea23d,
)));
let secp256r1_public_key = PublicKey::from((
0xd6ea577a54ae42411fbc78d686d4abba2150ca83540528e4b868002e346004b2,
0x62660ecce5979493fe5684526e8e00875b948e507a89a47096bc84064a175452,
));
let signed_message = Message::from(0x1e45523606c96c98ba970ff7cf9511fab8b25e1bcd52ced30b81df1e4a9c4323);
// A verified public key
let secp256r1_verified = secp256r1_signature.verify(secp256r1_public_key, signed_message);
assert(secp256r1_verified.is_ok());

// Ed25519 Signature Verification
let ed25519_public_key = PublicKey::from(0x314fa58689bbe1da2430517de2d772b384a1c1d2e9cb87e73c6afcf246045b10);
let ed25519_signature = Signature::Ed25519(Ed25519::from((
0xf38cef9361894be6c6e0eddec28a663d099d7ddff17c8077a1447d7ecb4e6545,
0xf5084560039486d3462dd65a40c80a74709b2f06d450ffc5dc00345c6b2cdd00,
)));
let hashed_message = Message::from(sha256(b256::zero()));
// A verified public key
let ed25519_verified = ed25519_signature.verify(ed25519_public_key, hashed_message);
assert(ed25519_verified.is_ok());
// ANCHOR_END: signature_verification
}

fn address_verification() {
// ANCHOR: address_verification
// Secp256k1 Address Verification
let secp256k1_address = Address::from(0x02844f00cce0f608fa3f0f7408bec96bfd757891a6fda6e1fa0f510398304881);
let secp256k1_signature = Secp256k1::from((
0x61f3caf4c0912cec69ff0b226638d397115c623a7f057914d48a7e4daf1cf6d8,
0x2555de81cd3a40382d3d64eb1c77e463eea5a76d65ec85f283e0b3d568352678,
));
let signed_message = Message::from(0xa13f4ab54057ce064d3dd97ac3ff30ed704e73956896c03650fe59b1a561fe15);
// A verified address
let secp256k1_verified = secp256k1_signature.verify_address(secp256k1_address, signed_message);
assert(secp256k1_verified.is_ok());

// Secp256r1 Address Verification
let secp256r1_address = Address::from(0xb4a5fabee8cc852084b71f17107e9c18d682033a58967027af0ab01edf2f9a6a);
let secp256r1_signature = Signature::Secp256r1(Secp256r1::from((
0xbd0c9b8792876713afa8bf3383eebf31c43437823ed761cc3600d0016de5110c,
0x44ac566bd156b4fc71a4a4cb2655d3dd360c695edb17dc3b64d611e122fea23d,
)));
let signed_message = Message::from(0xee45573606c96c98ba970ff7cf9511f1b8b25e6bcd52ced30b89df1e4a9c4323);
// A verified address
let secp256r1_verified = secp256r1_signature.verify_address(secp256r1_address, signed_message);
assert(secp256r1_verified.is_ok());

// ANCHOR_END: address_verification
}

fn evm_address_verification() {
// ANCHOR: evm_address_verification
// Secp256k1 Address Verification
let secp256k1_evm_address = EvmAddress::from(0x0000000000000000000000000ec44cf95ce5051ef590e6d420f8e722dd160ecb);
let secp256k1_signature = Signature::Secp256k1(Secp256k1::from((
0xbd0c9b8792876713afa8bff383eebf31c43437823ed761cc3600d0016de5110c,
0x44ac566bd156b4fc71a4a4cb2655d3dd360c695edb17dc3b64d611e122fea23d,
)));
let signed_message = Message::from(0xee45573606c96c98ba970ff7cf9511f1b8b25e6bcd52ced30b89df1e4a9c4323);
// A recovered EVM address.
let secp256k1_verified = secp256k1_signature.verify_evm_address(secp256k1_evm_address, signed_message);
assert(secp256k1_verified.is_ok());

// Secp256r1 Address Verification
let secp256r1_evm_address = EvmAddress::from(0x000000000000000000000000408eb2d97ef0beda0a33848d9e052066667cb00a);
let secp256r1_signature = Signature::Secp256r1(Secp256r1::from((
0x62CDC20C0AB6AA7B91E63DA9917792473F55A6F15006BC99DD4E29420084A3CC,
0xF4D99AF28F9D6BD96BDAAB83BFED99212AC3C7D06810E33FBB14C4F29B635414,
)));
let signed_message = Message::from(0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563);
// A recovered EVM address.
let secp256r1_verified = secp256r1_signature.verify_evm_address(secp256r1_evm_address, signed_message);
assert(secp256r1_verified.is_ok());
// ANCHOR_END: evm_address_verification
}
9 changes: 9 additions & 0 deletions sway-lib-std/src/crypto.sw
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
library;

pub mod signature_error;
pub mod message;
pub mod public_key;
pub mod ed25519;
pub mod secp256k1;
pub mod secp256r1;
pub mod signature;
Loading

0 comments on commit b03c76e

Please sign in to comment.