Skip to content

Commit

Permalink
feat(tee): add support for recoverable signatures
Browse files Browse the repository at this point in the history
Signatures produced by the TEE Prover are now compatible with the
on-chain verifier that uses the `ecrecover` precompile.

Until now, we've been using _non-recoverable_ signatures in the TEE
prover with a compressed (ECDSA) public key in each attestation -- it
was compressed because there are only 64 bytes available in the report
attestation quote. That worked fine for off-chain proof verification,
but for on-chain verification, it's better to use the Ethereum address
derived from the public key so we can call ecrecover in Solidity to
verify the signature.

This PR goes hand in hand with matter-labs/teepot#228
  • Loading branch information
pbeza committed Dec 30, 2024
1 parent 78af2bf commit fe3861f
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 7 deletions.
6 changes: 5 additions & 1 deletion core/bin/zksync_tee_prover/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@ anyhow.workspace = true
async-trait.workspace = true
envy.workspace = true
reqwest = { workspace = true, features = ["zstd"] }
secp256k1 = { workspace = true, features = ["serde"] }
secp256k1 = { workspace = true, features = [
"global-context",
"recovery",
"serde",
] }
serde = { workspace = true, features = ["derive"] }
thiserror.workspace = true
tokio = { workspace = true, features = ["full"] }
Expand Down
6 changes: 3 additions & 3 deletions core/bin/zksync_tee_prover/src/api_client.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use reqwest::{Client, Response, StatusCode};
use secp256k1::{ecdsa::Signature, PublicKey};
use secp256k1::PublicKey;
use serde::Serialize;
use url::Url;
use zksync_basic_types::H256;
Expand Down Expand Up @@ -87,13 +87,13 @@ impl TeeApiClient {
pub async fn submit_proof(
&self,
batch_number: L1BatchNumber,
signature: Signature,
signature: [u8; 65],
pubkey: &PublicKey,
root_hash: H256,
tee_type: TeeType,
) -> Result<(), TeeProverError> {
let request = SubmitTeeProofRequest(Box::new(L1BatchTeeProofForL1 {
signature: signature.serialize_compact().into(),
signature: signature.into(),
pubkey: pubkey.serialize().into(),
proof: root_hash.as_bytes().into(),
tee_type,
Expand Down
64 changes: 61 additions & 3 deletions core/bin/zksync_tee_prover/src/tee_prover.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::fmt;

use secp256k1::{ecdsa::Signature, Message, PublicKey, Secp256k1};
use secp256k1::{Message, PublicKey, Secp256k1, SecretKey, SECP256K1};
use zksync_basic_types::H256;
use zksync_node_framework::{
service::StopReceiver,
Expand Down Expand Up @@ -67,10 +67,25 @@ impl fmt::Debug for TeeProver {
}

impl TeeProver {
// TODO TODO TODO add tests for this function
/// Signs the message in Ethereum-compatible format for on-chain verification.
fn sign_message(&self, sec: &SecretKey, message: Message) -> Result<[u8; 65], TeeProverError> {
let s = SECP256K1.sign_ecdsa_recoverable(&message, sec);
let (rec_id, data) = s.serialize_compact();

let mut signature = [0u8; 65];
signature[..64].copy_from_slice(&data);
// as defined in the Ethereum Yellow Paper (Appendix F)
// https://ethereum.github.io/yellowpaper/paper.pdf
signature[64] = 27 + rec_id.to_i32() as u8;

Ok(signature)
}

fn verify(
&self,
tvi: TeeVerifierInput,
) -> Result<(Signature, L1BatchNumber, H256), TeeProverError> {
) -> Result<([u8; 65], L1BatchNumber, H256), TeeProverError> {
match tvi {
TeeVerifierInput::V1(tvi) => {
let observer = METRICS.proof_generation_time.start();
Expand All @@ -79,7 +94,7 @@ impl TeeProver {
let batch_number = verification_result.batch_number;
let msg_to_sign = Message::from_slice(root_hash_bytes)
.map_err(|e| TeeProverError::Verification(e.into()))?;
let signature = self.config.signing_key.sign_ecdsa(msg_to_sign);
let signature = self.sign_message(&self.config.signing_key, msg_to_sign)?;
let duration = observer.observe();
tracing::info!(
proof_generation_time = duration.as_secs_f64(),
Expand Down Expand Up @@ -182,3 +197,46 @@ impl Task for TeeProver {
}
}
}

// #[cfg(test)]
// mod tests {
// use super::*;

// /// Recovers the address of the sender using secp256k1 pubkey recovery.
// ///
// /// Converts the public key into an ethereum address by hashing the public key with
// /// keccak256.
// ///
// /// This does not ensure that the `s` value in the signature is low, and _just_ wraps the
// /// underlying secp256k1 library.
// // #[allow(dead_code)]
// pub fn recover_signer_unchecked(sig: &[u8; 65], msg: &[u8; 32]) -> Result<Address, Error> {
// let sig = RecoverableSignature::from_compact(
// &sig[0..64],
// RecoveryId::from_i32(sig[64] as i32 - 27)?,
// )?;

// let public = SECP256K1.recover_ecdsa(&Message::from_digest_slice(&msg[..32])?, &sig)?;
// Ok(public_key_to_address(&public))
// }

// // #[test]
// // fn recover() {
// // let proof = "01000000c13bd882edb37ffbabc9f9e34a0d9789633b850fe55e625b768cc8e5feed7d9f7ab536cbc210c2fcc1385aaf88d8a91d8adc2740245f9deee5fd3d61dd2a71662fb6639515f1e2f3354361a82d86c1952352c1a81b";
// // let proof_bytes = hex::decode(proof).unwrap();
// // let msg = "216ac5cd5a5e13b0c9a81efb1ad04526b9f4ddd2fe6ebc02819c5097dfb0958c";
// // let msg_bytes = hex::decode(msg).unwrap();
// // let proof_addr = recover_signer_unchecked(
// // &proof_bytes[24..].try_into().unwrap(),
// // &msg_bytes.try_into().unwrap(),
// // )
// // .unwrap();
// // let priv_key = "324b5d1744ec27d6ac458350ce6a6248680bb0209521b2c730c1fe82a433eb54";
// // let priv_key = SecretKey::from_str(priv_key).unwrap();
// // let pubkey = public_key(&priv_key);
// // let pub_addr = public_key_to_address(&pubkey);
// // assert_eq!(pub_addr, proof_addr);
// // println!("Public address: {pub_addr}");
// // println!("Proof public address: {proof_addr}");
// // }
// }

0 comments on commit fe3861f

Please sign in to comment.