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 9c15a1b
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 7 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

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

10 changes: 9 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 All @@ -31,3 +35,7 @@ zksync_prover_interface.workspace = true
zksync_tee_verifier.workspace = true
zksync_types.workspace = true
zksync_vlog.workspace = true

[dev-dependencies]
hex.workspace = true
sha3.workspace = true
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
72 changes: 69 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,24 @@ impl fmt::Debug for TeeProver {
}

impl TeeProver {
/// 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 +93,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 +196,55 @@ impl Task for TeeProver {
}
}
}

#[cfg(test)]
mod tests {
use super::*;
use anyhow::Result;
use secp256k1::ecdsa::{RecoverableSignature, RecoveryId};
use sha3::{Digest, Keccak256};

/// Converts a public key into an Ethereum address by hashing the encoded public key with Keccak256.
pub fn public_key_to_address(public: &PublicKey) -> [u8; 20] {
let public_key_bytes = public.serialize_uncompressed();

// Skip the first byte (0x04) which indicates uncompressed key
let hash: [u8; 32] = Keccak256::digest(&public_key_bytes[1..]).into();

// Take the last 20 bytes of the hash to get the Ethereum address
let mut address = [0u8; 20];
address.copy_from_slice(&hash[12..]);
address
}

/// Equivalent to the ecrecover precompile.
pub fn recover_signer_unchecked(sig: &[u8; 65], msg: &Message) -> Result<[u8; 20]> {
let sig = RecoverableSignature::from_compact(
&sig[0..64],
RecoveryId::from_i32(sig[64] as i32 - 27)?,
)?;
let public = SECP256K1.recover_ecdsa(msg, &sig)?;
Ok(public_key_to_address(&public))
}

#[test]
/// Credit to:
/// https://github.com/taikoxyz/raiko/blob/ddba6b0add73f05778617e3950ffafc371b9293d/provers/sgx/guest/src/signature.rs#L67
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(),
&Message::from_slice(&msg_bytes).unwrap(),
)
.unwrap();
let priv_key = "324b5d1744ec27d6ac458350ce6a6248680bb0209521b2c730c1fe82a433eb54";
let priv_key_bytes = hex::decode(priv_key).unwrap();
let priv_key = SecretKey::from_slice(&priv_key_bytes).unwrap();
let pubkey = PublicKey::from_secret_key_global(&priv_key);
let pub_addr = public_key_to_address(&pubkey);
assert_eq!(pub_addr, proof_addr);
}
}

0 comments on commit 9c15a1b

Please sign in to comment.