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 14 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
97 changes: 71 additions & 26 deletions batcher/aligned-sdk/src/core/types.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
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;
Expand All @@ -10,6 +14,12 @@ use lambdaworks_crypto::merkle_tree::{
use serde::{Deserialize, Serialize};
use sha3::{Digest, Keccak256};

// 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 +49,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 +58,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 +190,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 @@ -196,32 +255,18 @@ 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);
let recovered = self.signature.recover_typed_data(&self.verification_data)?;

// We can expect here because encode_eip712 can only error if
// struct_hash or domain_separator return an error, which is not possible
let hashed_data = self
.verification_data
.encode_eip712()
.expect("Failed to encode verification data for signature verification");

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
70 changes: 53 additions & 17 deletions batcher/aligned-sdk/src/sdk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ use ethers::{
prelude::k256::ecdsa::SigningKey,
providers::{Http, Middleware, Provider},
signers::Wallet,
types::{Address, U256},
types::{Address, H160, U256},
};
use sha3::{Digest, Keccak256};
use std::sync::Arc;
use std::{str::FromStr, sync::Arc};
use tokio::{net::TcpStream, sync::Mutex};
use tokio_tungstenite::{connect_async, tungstenite::Message, MaybeTlsStream, WebSocketStream};

Expand Down Expand Up @@ -73,8 +73,15 @@ pub async fn submit_multiple_and_wait_verification(
nonce: U256,
payment_service_addr: &str,
) -> Result<Vec<AlignedVerificationData>, errors::SubmitError> {
let aligned_verification_data =
submit_multiple(batcher_url, verification_data, max_fees, wallet, nonce).await?;
let aligned_verification_data = submit_multiple(
batcher_url,
chain.clone(),
verification_data,
max_fees,
wallet,
nonce,
)
.await?;

for aligned_verification_data_item in aligned_verification_data.iter() {
await_batch_verification(
Expand All @@ -92,6 +99,7 @@ pub async fn submit_multiple_and_wait_verification(
/// Submits multiple proofs to the batcher to be verified in Aligned.
/// # Arguments
/// * `batcher_url` - The url of the batcher to which the proof will be submitted.
/// * `chain` - The chain on which the verification will be done.
/// * `verification_data` - An array of verification data of each proof.
/// * `max_fees` - An array of the maximum fee that the submitter is willing to pay for each proof verification.
/// * `wallet` - The wallet used to sign the proof.
Expand All @@ -115,6 +123,7 @@ pub async fn submit_multiple_and_wait_verification(
/// * `GenericError` if the error doesn't match any of the previous ones.
pub async fn submit_multiple(
batcher_url: &str,
chain: Chain,
verification_data: &[VerificationData],
max_fees: &[U256],
wallet: Wallet<SigningKey>,
Expand All @@ -132,6 +141,7 @@ pub async fn submit_multiple(
_submit_multiple(
ws_write,
ws_read,
chain.clone(),
verification_data,
max_fees,
wallet,
Expand All @@ -143,6 +153,7 @@ pub async fn submit_multiple(
async fn _submit_multiple(
ws_write: Arc<Mutex<SplitSink<WebSocketStream<MaybeTlsStream<TcpStream>>, Message>>>,
mut ws_read: SplitStream<WebSocketStream<MaybeTlsStream<TcpStream>>>,
chain: Chain,
verification_data: &[VerificationData],
max_fees: &[U256],
wallet: Wallet<SigningKey>,
Expand All @@ -163,17 +174,33 @@ async fn _submit_multiple(

let response_stream = Arc::new(Mutex::new(response_stream));

// The sent verification data will be stored here so that we can calculate
// their commitments later.
let sent_verification_data = send_messages(
response_stream.clone(),
ws_write,
verification_data,
max_fees,
wallet,
nonce,
)
.await?;
let payment_service_addr = match chain {
Chain::Devnet => H160::from_str("0x7969c5eD335650692Bc04293B07F5BF2e7A673C0").ok(),
Chain::Holesky => H160::from_str("0x815aeCA64a974297942D2Bbf034ABEe22a38A003").ok(),
Chain::HoleskyStage => H160::from_str("0x7577Ec4ccC1E6C529162ec8019A49C13F6DAd98b").ok(),
};

let sent_verification_data = match payment_service_addr {
// The sent verification data will be stored here so that we can calculate
// their commitments later.
Some(payment_service_addr) => {
send_messages(
response_stream.clone(),
ws_write,
payment_service_addr,
verification_data,
max_fees,
wallet,
nonce,
)
.await?
}
None => {
return Err(errors::SubmitError::GenericError(
"Invalid chain".to_string(),
))
}
};

let num_responses = Arc::new(Mutex::new(0));

Expand Down Expand Up @@ -261,6 +288,7 @@ pub async fn submit_and_wait_verification(
/// Submits a proof to the batcher to be verified in Aligned.
/// # Arguments
/// * `batcher_url` - The url of the batcher to which the proof will be submitted.
/// * `chain` - The chain on which the verification will be done.
/// * `verification_data` - The verification data of the proof.
/// * `max_fee` - The maximum fee that the submitter is willing to pay for the verification.
/// * `wallet` - The wallet used to sign the proof.
Expand All @@ -284,6 +312,7 @@ pub async fn submit_and_wait_verification(
/// * `GenericError` if the error doesn't match any of the previous ones.
pub async fn submit(
batcher_url: &str,
chain: Chain,
verification_data: &VerificationData,
max_fee: U256,
wallet: Wallet<SigningKey>,
Expand All @@ -292,8 +321,15 @@ pub async fn submit(
let verification_data = vec![verification_data.clone()];
let max_fees = vec![max_fee];

let aligned_verification_data =
submit_multiple(batcher_url, &verification_data, &max_fees, wallet, nonce).await?;
let aligned_verification_data = submit_multiple(
batcher_url,
chain.clone(),
&verification_data,
&max_fees,
wallet,
nonce,
)
.await?;

Ok(aligned_verification_data[0].clone())
}
Expand Down
Loading