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

feat: make signing eip 712 compliant #916

Merged
merged 29 commits into from
Sep 10, 2024
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
bcd6663
feat: first version
NicolasRampoldi Aug 29, 2024
206cabf
feat: working implementation
NicolasRampoldi Sep 3, 2024
d333b62
docs: comment
NicolasRampoldi Sep 3, 2024
d48a42e
Merge branch 'main' into feat-make-signing-eip-712-compliant
NicolasRampoldi Sep 3, 2024
b2b512a
chore: anvil state
NicolasRampoldi Sep 3, 2024
88d0248
fix: linter
NicolasRampoldi Sep 3, 2024
3e671e9
forge install: openzeppelin-contracts
NicolasRampoldi Sep 3, 2024
a96d73e
chore: install openzepellin and bump solidity version
NicolasRampoldi Sep 3, 2024
9db71b0
fix: lower gap
NicolasRampoldi Sep 3, 2024
f259a96
refactor: hasher update
NicolasRampoldi Sep 3, 2024
801f690
Merge branch 'main' into feat-make-signing-eip-712-compliant
NicolasRampoldi Sep 4, 2024
b82987e
chore: merge
NicolasRampoldi Sep 4, 2024
83d8d77
chore: bump up protocol version
NicolasRampoldi Sep 5, 2024
aab9476
feat: add chain param to submit and submit_multiple
NicolasRampoldi Sep 5, 2024
e8be0d7
refactor: add errors
NicolasRampoldi Sep 5, 2024
76ed477
forge install: openzeppelin-contracts-upgradeable
NicolasRampoldi Sep 6, 2024
7772e3b
Merge branch 'main' into feat-make-signing-eip-712-compliant
NicolasRampoldi Sep 6, 2024
2d9e71f
chore: anvil state
NicolasRampoldi Sep 6, 2024
4ffd79c
feat: add setHashType in contract
NicolasRampoldi Sep 6, 2024
ab2b391
Merge branch 'main' into feat-make-signing-eip-712-compliant
NicolasRampoldi Sep 6, 2024
6610aca
fix: deployment script
NicolasRampoldi Sep 6, 2024
516c00d
feat: spearate BatcherPaymentServiceStorage
uri-99 Sep 6, 2024
71f8359
feat: include the storage in the contract
uri-99 Sep 6, 2024
a679cdd
fix: deploy batche
uri-99 Sep 6, 2024
ae1f63e
refactor: change make target to upgrade_batcher_payments_add_type_hash
uri-99 Sep 6, 2024
1bf323d
fix: remove 0x from nonced verification data type hash
NicolasRampoldi Sep 9, 2024
1bd4352
chore: new line
NicolasRampoldi Sep 9, 2024
7672899
refactor: nonce from bytes32 to uint256
NicolasRampoldi Sep 9, 2024
8d619bb
chore: anvil state
NicolasRampoldi Sep 9, 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
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,6 @@
[submodule "examples/validating-public-input/contracts/lib/forge-std"]
path = examples/validating-public-input/contracts/lib/forge-std
url = https://github.com/foundry-rs/forge-std
[submodule "contracts/lib/openzeppelin-contracts"]
path = contracts/lib/openzeppelin-contracts
url = https://github.com/OpenZeppelin/openzeppelin-contracts
4 changes: 3 additions & 1 deletion batcher/aligned-batcher/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1249,13 +1249,15 @@ impl Batcher {
nonce_bytes,
DEFAULT_MAX_FEE_PER_PROOF.into(), // 13_000 gas per proof * 100 gwei gas price (upper bound)
self.chain_id,
self.payment_service.address(),
)
};

let client_msg = ClientMessage::new(
nonced_verification_data.clone(),
non_paying_config.replacement.clone(),
);
)
.await;

let batch_state = self.batch_state.lock().await;
self.clone()
Expand Down
2 changes: 1 addition & 1 deletion batcher/aligned-sdk/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ version = "0.1.0"
edition = "2021"

[dependencies]
ethers = { tag = "v2.0.15-fix-reconnections", features = ["ws", "rustls"], git = "https://github.com/yetanotherco/ethers-rs.git" }
ethers = { tag = "v2.0.15-fix-reconnections", features = ["ws", "rustls", "eip712"], git = "https://github.com/yetanotherco/ethers-rs.git" }
log = { version = "0.4.21"}
serde_json = "1.0.117"
tokio-tungstenite = { version = "0.23.1", features = ["native-tls"] }
Expand Down
5 changes: 4 additions & 1 deletion batcher/aligned-sdk/src/communication/messaging.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use ethers::signers::Signer;
use ethers::types::Address;
use futures_util::{stream::SplitStream, SinkExt, StreamExt};
use log::{debug, error, info};
use std::sync::Arc;
Expand Down Expand Up @@ -30,6 +31,7 @@ pub type ResponseStream = TryFilter<
pub async fn send_messages(
response_stream: Arc<Mutex<ResponseStream>>,
ws_write: Arc<Mutex<SplitSink<WebSocketStream<MaybeTlsStream<TcpStream>>, Message>>>,
payment_service_addr: Address,
verification_data: &[VerificationData],
max_fees: &[U256],
wallet: Wallet<SigningKey>,
Expand All @@ -53,11 +55,12 @@ pub async fn send_messages(
nonce_bytes,
max_fees[idx],
chain_id,
payment_service_addr,
);

nonce += U256::one();

let msg = ClientMessage::new(verification_data.clone(), wallet.clone());
let msg = ClientMessage::new(verification_data.clone(), wallet.clone()).await;
let msg_bin = cbor_serialize(&msg).map_err(SubmitError::SerializationError)?;
ws_write
.send(Message::Binary(msg_bin.clone()))
Expand Down
2 changes: 1 addition & 1 deletion batcher/aligned-sdk/src/communication/protocol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use crate::core::{errors::SubmitError, types::ResponseMessage};

use super::serialization::cbor_deserialize;

pub const EXPECTED_PROTOCOL_VERSION: u16 = 3;
pub const EXPECTED_PROTOCOL_VERSION: u16 = 4;

pub async fn check_protocol_version(
ws_read: &mut SplitStream<WebSocketStream<MaybeTlsStream<TcpStream>>>,
Expand Down
31 changes: 31 additions & 0 deletions batcher/aligned-sdk/src/core/errors.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use core::fmt;
use ethers::providers::ProviderError;
use ethers::signers::WalletError;
use ethers::types::transaction::eip712::Eip712Error;
use ethers::types::SignatureError;
use std::io;
use std::path::PathBuf;
use tokio_tungstenite::tungstenite::protocol::CloseFrame;
Expand Down Expand Up @@ -233,3 +235,32 @@ impl fmt::Display for ChainIdError {
}
}
}

#[derive(Debug)]
pub enum VerifySignatureError {
RecoverTypedDataError(SignatureError),
EncodeError(Eip712Error),
}

impl From<SignatureError> for VerifySignatureError {
fn from(e: SignatureError) -> Self {
VerifySignatureError::RecoverTypedDataError(e)
}
}

impl From<Eip712Error> for VerifySignatureError {
fn from(e: Eip712Error) -> Self {
VerifySignatureError::EncodeError(e)
}
}

impl fmt::Display for VerifySignatureError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
VerifySignatureError::RecoverTypedDataError(e) => {
write!(f, "Recover typed data error: {}", e)
}
VerifySignatureError::EncodeError(e) => write!(f, "Encode error: {}", e),
}
}
}
97 changes: 69 additions & 28 deletions batcher/aligned-sdk/src/core/types.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,26 @@
use ethers::core::k256::ecdsa::SigningKey;
use ethers::signers::Signer;
use ethers::signers::Wallet;
use ethers::types::transaction::eip712::EIP712Domain;
use ethers::types::transaction::eip712::Eip712;
use ethers::types::transaction::eip712::Eip712Error;
use ethers::types::Address;
use ethers::types::Signature;
use ethers::types::SignatureError;
use ethers::types::U256;
use lambdaworks_crypto::merkle_tree::{
merkle::MerkleTree, proof::Proof, traits::IsMerkleTreeBackend,
};
use serde::{Deserialize, Serialize};
use sha3::{Digest, Keccak256};

use super::errors::VerifySignatureError;

// VerificationData is a bytes32 instead of a VerificationData struct because in the BatcherPaymentService contract
// we don't have the fields of VerificationData, we only have the hash of the VerificationData.
// chain_id is not included in the type because it is now part of the domain.
const NONCED_VERIFICATION_DATA_TYPE: &[u8] =
b"NoncedVerificationData(bytes32 verification_data_hash,bytes32 nonce,uint256 max_fee)";

#[derive(Debug, Serialize, Deserialize, Default, Clone, PartialEq, Eq)]
#[repr(u8)]
pub enum ProvingSystemId {
Expand Down Expand Up @@ -39,6 +50,7 @@ pub struct NoncedVerificationData {
pub nonce: [u8; 32],
pub max_fee: U256,
pub chain_id: U256,
pub payment_service_addr: Address,
}

impl NoncedVerificationData {
Expand All @@ -47,12 +59,14 @@ impl NoncedVerificationData {
nonce: [u8; 32],
max_fee: U256,
chain_id: U256,
payment_service_addr: Address,
) -> Self {
Self {
verification_data,
nonce,
max_fee,
chain_id,
payment_service_addr,
}
}
}
Expand Down Expand Up @@ -177,14 +191,60 @@ pub struct ClientMessage {
pub signature: Signature,
}

impl Eip712 for NoncedVerificationData {
type Error = Eip712Error;
fn domain(&self) -> Result<EIP712Domain, Self::Error> {
Ok(EIP712Domain {
name: Some("Aligned".into()),
version: Some("1".into()),
chain_id: Some(self.chain_id),
verifying_contract: Some(self.payment_service_addr),
salt: None,
})
}

fn type_hash() -> Result<[u8; 32], Self::Error> {
let mut hasher = Keccak256::new();
hasher.update(NONCED_VERIFICATION_DATA_TYPE);
Ok(hasher.finalize().into())
}

fn struct_hash(&self) -> Result<[u8; 32], Self::Error> {
let verification_data_hash =
VerificationCommitmentBatch::hash_data(&self.verification_data.clone().into());

let mut hasher = Keccak256::new();

hasher.update(NONCED_VERIFICATION_DATA_TYPE);
let nonced_verification_data_type_hash = hasher.finalize_reset();

hasher.update(self.nonce);
let nonce_hash = hasher.finalize_reset();

let mut max_fee_bytes = [0u8; 32];
self.max_fee.to_big_endian(&mut max_fee_bytes);
hasher.update(max_fee_bytes);
let max_fee_hash = hasher.finalize_reset();

hasher.update(nonced_verification_data_type_hash.as_slice());
hasher.update(verification_data_hash.as_slice());
hasher.update(nonce_hash.as_slice());
hasher.update(max_fee_hash.as_slice());

Ok(hasher.finalize().into())
}
}

impl ClientMessage {
/// Client message is a wrap around verification data and its signature.
/// The signature is obtained by calculating the commitments and then hashing them.
pub fn new(verification_data: NoncedVerificationData, wallet: Wallet<SigningKey>) -> Self {
let hashed_data = ClientMessage::hash_with_nonce_and_chain_id(&verification_data);

pub async fn new(
verification_data: NoncedVerificationData,
wallet: Wallet<SigningKey>,
) -> Self {
let signature = wallet
.sign_hash(hashed_data.into())
.sign_typed_data(&verification_data)
.await
.expect("Failed to sign the verification data");

ClientMessage {
Expand All @@ -195,33 +255,14 @@ impl ClientMessage {

/// The signature of the message is verified, and when it correct, the
/// recovered address from the signature is returned.
pub fn verify_signature(&self) -> Result<Address, SignatureError> {
let hashed_data: [u8; 32] =
ClientMessage::hash_with_nonce_and_chain_id(&self.verification_data);
pub fn verify_signature(&self) -> Result<Address, VerifySignatureError> {
let recovered = self.signature.recover_typed_data(&self.verification_data)?;

let hashed_data = self.verification_data.encode_eip712()?;

let recovered = self.signature.recover(hashed_data)?;
self.signature.verify(hashed_data, recovered)?;
Ok(recovered)
}

fn hash_with_nonce_and_chain_id(verification_data: &NoncedVerificationData) -> [u8; 32] {
let hashed_leaf = VerificationCommitmentBatch::hash_data(&verification_data.into());

let mut chain_id_bytes = [0u8; 32];
verification_data
.chain_id
.to_big_endian(&mut chain_id_bytes);

let mut max_fee_bytes = [0u8; 32];
verification_data.max_fee.to_big_endian(&mut max_fee_bytes);

let mut hasher = Keccak256::new();
hasher.update(hashed_leaf);
hasher.update(verification_data.nonce);
hasher.update(max_fee_bytes);
hasher.update(chain_id_bytes);
hasher.finalize().into()
}
}

#[derive(Serialize, Deserialize, Clone)]
Expand Down
Loading