Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
e24b5a2
wip: eip712
developeruche Jul 15, 2024
78dcb10
mod: init draft
developeruche Jul 16, 2024
6d5a3db
fix: fields is FixedBytes
bidzyyys Jul 16, 2024
5a62a97
wip
developeruche Jul 16, 2024
2f4e3f2
wip: abi.encode
developeruche Jul 16, 2024
667f2e5
feat: ABI encode domain separator tuple
bidzyyys Jul 17, 2024
13ce911
mod: eip712 logic implemenation (test in progress)
developeruche Jul 18, 2024
97761cd
mod: added test
developeruche Jul 19, 2024
06140cd
mod: added test
developeruche Jul 19, 2024
ce14f87
mod: hard coded chain-id opcode
developeruche Jul 20, 2024
78a1f81
fix: proper chainid shim
bidzyyys Jul 22, 2024
0e26640
feat: add contract_address shim
bidzyyys Jul 22, 2024
318270b
Merge remote-tracking branch 'origin/main' into feat/eip712
alexfertel Jul 23, 2024
a0598c5
ref(docs): adjust and simplify doc comments
alexfertel Jul 23, 2024
9093ba6
Merge branch 'main' into feat/eip712
bidzyyys Jul 23, 2024
60afe30
mod: resolved first batch comments
developeruche Jul 24, 2024
7727120
mod: added comment to test
developeruche Jul 24, 2024
a59f1bd
mod: port message_hash_utils to lib/crypto
developeruche Jul 24, 2024
9f31c04
mod: hold bug
developeruche Jul 24, 2024
8d0954f
mod: shim update
developeruche Jul 24, 2024
2279ebc
Merge branch 'main' of https://github.com/developeruche/rust-contract…
developeruche Jul 27, 2024
ccd0068
mod: introduced EIP712 domain seperator trait
developeruche Jul 27, 2024
34b9351
mod: introduced internal function
developeruche Jul 27, 2024
4f4b115
mod: fmt
developeruche Jul 27, 2024
36ca3a5
ref: refactor EIP-712 interface
bidzyyys Jul 30, 2024
e496944
chore: rename files
bidzyyys Jul 31, 2024
be10709
feat: add example with EIP-712
bidzyyys Jul 31, 2024
191d840
feat: impl IEIP721 trait for unit tests
bidzyyys Jul 31, 2024
f8e9004
fix: CI build
bidzyyys Jul 31, 2024
0572e8c
chore: remove cryptography example
bidzyyys Jul 31, 2024
75565f1
Merge branch 'main' into feat/eip712
bidzyyys Jul 31, 2024
6da2b93
test: add test for eip712_domain
bidzyyys Jul 31, 2024
4b8b552
fix: proper implementation of ERC-191
bidzyyys Aug 2, 2024
32516ad
ref: use keccak256 from crypto lib
bidzyyys Aug 2, 2024
ec8da2a
ref: get rid of hex macro in crypto lib
bidzyyys Aug 2, 2024
1c37d3b
ref: get rid of alloy_primitives
bidzyyys Aug 4, 2024
53c2291
ref: store EIP-191 prefix as bytes
bidzyyys Aug 5, 2024
87523e3
ref: use const keccak for const values
bidzyyys Aug 5, 2024
6548111
ref: move implementation back into contracts
alexfertel Aug 5, 2024
58c1c70
ref: remove ieip712.rs file
alexfertel Aug 5, 2024
663f3ff
Merge branch 'main' into feat/eip712
bidzyyys Aug 5, 2024
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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ alloy-sol-types = { version = "0.3.1", default-features = false }

const-hex = { version = "1.11.1", default-features = false }
eyre = "0.6.8"
keccak-const = "0.2.0"
koba = "0.1.2"
once_cell = "1.19.0"
rand = "0.8.5"
Expand Down
1 change: 1 addition & 0 deletions contracts/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ alloy-sol-types.workspace = true
stylus-sdk.workspace = true
stylus-proc.workspace = true
mini-alloc.workspace = true
keccak-const.workspace = true

[dev-dependencies]
alloy-primitives = { workspace = true, features = ["arbitrary"] }
Expand Down
152 changes: 152 additions & 0 deletions contracts/src/utils/cryptography/eip712.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
//! [EIP-712](https://eips.ethereum.org/EIPS/eip-712) is a standard for hashing
//! and signing typed structured data.
//!
//! The implementation of the domain separator was designed to be as efficient
//! as possible while still properly updating the chain id to protect against
//! replay attacks on an eventual fork of the chain.
//!
//! NOTE: This contract implements the version of the encoding known as "v4", as
//! implemented by the JSON RPC method [`eth_signTypedDataV4`] in `MetaMask`.
//!
//! [`eth_signTypedDataV4`]: https://docs.metamask.io/guide/signing-data.html

use alloc::{borrow::ToOwned, string::String, vec::Vec};

use alloy_primitives::{keccak256, Address, FixedBytes, U256};
use alloy_sol_types::{sol, SolType};

use crate::utils::cryptography::message_hash_utils::to_typed_data_hash;

/// keccak256("EIP712Domain(string name,string version,uint256 chainId,address
/// verifyingContract)")
pub const TYPE_HASH: [u8; 32] = [
0x8b, 0x73, 0xc3, 0xc6, 0x9b, 0xb8, 0xfe, 0x3d, 0x51, 0x2e, 0xcc, 0x4c,
0xf7, 0x59, 0xcc, 0x79, 0x23, 0x9f, 0x7b, 0x17, 0x9b, 0x0f, 0xfa, 0xca,
0xa9, 0xa7, 0x5d, 0x52, 0x2b, 0x39, 0x40, 0x0f,
];

/// Field for the domain separator.
pub const FIELDS: [u8; 1] = [0x0f];

/// Salt for the domain separator.
pub const SALT: [u8; 32] = [0u8; 32];

/// Tuple for the domain separator.
pub type DomainSeparatorTuple = sol! {
tuple(bytes32, bytes32, bytes32, uint256, address)
};

/// EIP-712 Contract interface.
pub trait IEIP712 {
/// Immutable name of EIP-712 instance.
const NAME: &'static str;
/// Hashed name of EIP-712 instance.
const HASHED_NAME: [u8; 32] =
keccak_const::Keccak256::new().update(Self::NAME.as_bytes()).finalize();

/// Immutable version of EIP-712 instance.
const VERSION: &'static str;
/// Hashed version of EIP-712 instance.
const HASHED_VERSION: [u8; 32] = keccak_const::Keccak256::new()
.update(Self::VERSION.as_bytes())
.finalize();

/// Returns chain id.
fn chain_id() -> U256;
/// Returns the contract's address.
fn contract_address() -> Address;

/// Returns the fields and values that describe the domain separator used by
/// this contract for EIP-712 signature.
///
/// # Arguments
///
/// * `&self` - Read access to the contract's state.
fn eip712_domain(
&self,
) -> ([u8; 1], String, String, U256, Address, [u8; 32], Vec<U256>) {
(
FIELDS,
Self::NAME.to_owned(),
Self::VERSION.to_owned(),
Self::chain_id(),
Self::contract_address(),
SALT,
Vec::new(),
)
}

/// Returns the domain separator for the current chain.
///
/// # Arguments
///
/// * `&self` - Read access to the contract's state.
fn domain_separator_v4(&self) -> FixedBytes<32> {
let encoded = DomainSeparatorTuple::encode(&(
TYPE_HASH,
Self::HASHED_NAME,
Self::HASHED_VERSION,
Self::chain_id(),
Self::contract_address().into(),
));

keccak256(encoded)
}

/// Given an already [hashed struct], this function returns the hash of the
/// fully encoded EIP-712 message for this domain.
///
/// [hashed struct]: https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct
///
/// # Arguments
///
/// * `&self` - Read access to the contract's state.
fn hash_typed_data_v4(
&self,
struct_hash: FixedBytes<32>,
) -> FixedBytes<32> {
let domain_separator = self.domain_separator_v4();
to_typed_data_hash(&domain_separator, &struct_hash)
}
}

#[cfg(all(test, feature = "std"))]
mod tests {
use alloy_primitives::{address, uint, Address, U256};

use super::{FIELDS, IEIP712, SALT};

const CHAIN_ID: U256 = uint!(42161_U256);

const CONTRACT_ADDRESS: Address =
address!("000000000000000000000000000000000000dEaD");

#[derive(Default)]
struct TestEIP712 {}

impl IEIP712 for TestEIP712 {
const NAME: &'static str = "A Name";
const VERSION: &'static str = "1";

fn chain_id() -> U256 {
CHAIN_ID
}

fn contract_address() -> Address {
CONTRACT_ADDRESS
}
}

#[test]
fn domain_test() {
let contract = TestEIP712::default();
let domain = contract.eip712_domain();
assert_eq!(FIELDS, domain.0);
assert_eq!(TestEIP712::NAME, domain.1);
assert_eq!(TestEIP712::VERSION, domain.2);
assert_eq!(CHAIN_ID, domain.3);
assert_eq!(CONTRACT_ADDRESS, domain.4);
assert_eq!(SALT, domain.5);
assert_eq!(Vec::<U256>::new(), domain.6);
}
}
60 changes: 60 additions & 0 deletions contracts/src/utils/cryptography/message_hash_utils.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
//! Signature message hash utilities for producing digests to be consumed by
//! `ECDSA` recovery or signing.
//!
//! The library provides methods for generating a hash of a message that
//! conforms to the [EIP 712] specification.
//!
//! [EIP-712]: https://eips.ethereum.org/EIPS/eip-712

use alloy_primitives::{keccak256, FixedBytes};

/// Prefix for ERC-191 version with `0x01`.
pub const TYPED_DATA_PREFIX: [u8; 2] = [0x19, 0x01];

/// Returns the keccak256 digest of an EIP-712 typed data (ERC-191 version
/// `0x01`).
///
/// The digest is calculated from a `domain_separator` and a `struct_hash`, by
/// prefixing them with `[TYPED_DATA_PREFIX]` and hashing the result. It
/// corresponds to the hash signed by the [eth_signTypedData] JSON-RPC method as
/// part of EIP-712.
///
/// [eth_signTypedData]: https://eips.ethereum.org/EIPS/eip-712
#[must_use]
pub fn to_typed_data_hash(
domain_separator: &[u8; 32],
struct_hash: &[u8; 32],
) -> FixedBytes<32> {
let mut preimage = [0u8; 66];
preimage[..2].copy_from_slice(&TYPED_DATA_PREFIX);
preimage[2..34].copy_from_slice(domain_separator);
preimage[34..].copy_from_slice(struct_hash);
keccak256(preimage)
}

#[cfg(all(test, feature = "std"))]
mod tests {
use alloy_primitives::b256;

use super::to_typed_data_hash;

#[test]
fn test_to_typed_data_hash() {
// TYPE_HASH
let domain_separator = b256!(
"8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f"
);
// bytes32("stylus");
let struct_hash = b256!(
"7379746c75730000000000000000000000000000000000000000000000000000"
);
let expected = b256!(
"cefc47137f8165d8270433dd62e395f5672966b83a113a7bb7b2805730a2197e"
);

assert_eq!(
expected,
to_typed_data_hash(&domain_separator, &struct_hash),
);
}
}
2 changes: 2 additions & 0 deletions contracts/src/utils/cryptography/mod.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
//! Smart Contracts with cryptography.
pub mod ecdsa;
pub mod eip712;
pub mod message_hash_utils;
31 changes: 31 additions & 0 deletions lib/motsu/src/shims.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,13 @@ pub fn storage_flush_cache(_: bool) {
/// Dummy msg sender set for tests.
pub const MSG_SENDER: &[u8; 42] = b"0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF";

/// Dummy contract address set for tests.
pub const CONTRACT_ADDRESS: &[u8; 42] =
b"0xdCE82b5f92C98F27F116F70491a487EFFDb6a2a9";

/// Arbitrum's CHAID ID.
pub const CHAIN_ID: u64 = 42161;

/// Externally Owned Account (EOA) code hash.
pub const EOA_CODEHASH: &[u8; 66] =
b"0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470";
Expand All @@ -182,6 +189,30 @@ pub unsafe extern "C" fn msg_sender(sender: *mut u8) {
std::ptr::copy(addr.as_ptr(), sender, 20);
}

/// Gets the address of the current program. The semantics are equivalent to
/// that of the EVM's [`ADDRESS`] opcode.
///
/// [`ADDRESS`]: https://www.evm.codes/#30
///
/// # Panics
///
/// May panic if fails to parse `CONTRACT_ADDRESS` as an address.
#[no_mangle]
pub unsafe extern "C" fn contract_address(address: *mut u8) {
let addr =
const_hex::const_decode_to_array::<20>(CONTRACT_ADDRESS).unwrap();
std::ptr::copy(addr.as_ptr(), address, 20);
}

/// Gets the chain ID of the current chain. The semantics are equivalent to
/// that of the EVM's [`CHAINID`] opcode.
///
/// [`CHAINID`]: https://www.evm.codes/#46
#[no_mangle]
pub unsafe extern "C" fn chainid() -> u64 {
CHAIN_ID
}

/// Emits an EVM log with the given number of topics and data, the first bytes
/// of which should be the 32-byte-aligned topic data. The semantics are
/// equivalent to that of the EVM's [`LOG0`], [`LOG1`], [`LOG2`], [`LOG3`], and
Expand Down