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(tee-proof-verifier): add support for Solidity-compatible pubkey in report_data #240

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,5 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
- uses: cachix/install-nix-action@v27
- uses: cachix/install-nix-action@v30
- run: nix run nixpkgs#taplo -- fmt --check
6 changes: 3 additions & 3 deletions .github/workflows/nix.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
- uses: cachix/install-nix-action@v27
- uses: cachix/install-nix-action@v30
with:
extra_nix_config: |
access-tokens = github.com=${{ github.token }}
Expand All @@ -37,7 +37,7 @@ jobs:
runs-on: [ matterlabs-default-infra-runners ]
steps:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
- uses: cachix/install-nix-action@v27
- uses: cachix/install-nix-action@v30
with:
extra_nix_config: |
access-tokens = github.com=${{ github.token }}
Expand Down Expand Up @@ -75,7 +75,7 @@ jobs:
- { nixpackage: 'container-verify-era-proof-attestation-sgx' }
steps:
- uses: actions/checkout@v4
- uses: cachix/install-nix-action@v27
- uses: cachix/install-nix-action@v30
with:
extra_nix_config: |
access-tokens = github.com=${{ github.token }}
Expand Down
5 changes: 4 additions & 1 deletion Cargo.lock

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

26 changes: 18 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,5 @@
# teepot

Key Value store in a TEE with Remote Attestation for Authentication

## Introduction

This project is a key-value store that runs in a Trusted Execution Environment (TEE) and uses Remote Attestation for
Authentication.
The key-value store is implemented using Hashicorp Vault running in an Intel SGX enclave via the Gramine runtime.

## Parts of this project

- `teepot`: The main rust crate that abstracts TEEs and key-value stores.
Expand All @@ -22,6 +14,18 @@ The key-value store is implemented using Hashicorp Vault running in an Intel SGX
- `verify-attestation`: A client utility that verifies the attestation of an enclave.
- `tee-key-preexec`: A pre-exec utility that generates a p256 secret key and passes it as an environment variable to the
enclave along with the attestation quote containing the hash of the public key.
- `tdx_google`: A base VM running on Google Cloud TDX. It receives a container URL via the instance metadata,
measures the sha384 of the URL to RTMR3 and launches the container.
- `tdx-extend`: A utility to extend an RTMR register with a hash value.
- `rtmr-calc`: A utility to calculate RTMR1 and RTMR2 from a GPT disk, the linux kernel, the linux initrd
and a UKI (unified kernel image).
- `sha384-extend`: A utility to calculate RTMR registers after extending them with a digest.

## Vault

Part of this project is a key-value store that runs in a Trusted Execution Environment (TEE) and uses Remote Attestation
for Authentication. The key-value store is implemented using Hashicorp Vault running in an Intel SGX enclave via the
Gramine runtime.

## Development

Expand Down Expand Up @@ -96,3 +100,9 @@ Attributes:
isv_svn: 0
debug_enclave: False
```

### TDX VM testing

```shell
nixos-rebuild -L --flake .#tdxtest build-vm && ./result/bin/run-tdxtest-vm
```
45 changes: 45 additions & 0 deletions assets/gcloud-deploy.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#!/usr/bin/env bash

#
# SPDX-License-Identifier: Apache-2.0
# Copyright (c) 2025 Matter Labs
#

set -ex

NO=${NO:-1}

nix build -L .#tdx_google

gsutil cp result/tdx_base_1.vmdk gs://tdx_vms/

gcloud migration vms image-imports create \
--location=us-central1 \
--target-project=tdx-pilot \
--project=tdx-pilot \
--skip-os-adaptation \
--source-file=gs://tdx_vms/tdx_base_1.vmdk \
tdx-img-pre-"${NO}"

gcloud compute instances stop tdx-pilot --zone us-central1-c --project tdx-pilot || :
gcloud compute instances delete tdx-pilot --zone us-central1-c --project tdx-pilot || :

while gcloud migration vms image-imports list --location=us-central1 --project=tdx-pilot | grep -F RUNNING; do
sleep 1
done

gcloud compute images create \
--project tdx-pilot \
--guest-os-features=UEFI_COMPATIBLE,TDX_CAPABLE,GVNIC,VIRTIO_SCSI_MULTIQUEUE \
--storage-location=us-central1 \
--source-image=tdx-img-pre-"${NO}" \
tdx-img-f-"${NO}"

gcloud compute instances create tdx-pilot \
--machine-type c3-standard-4 --zone us-central1-c \
--confidential-compute-type=TDX \
--maintenance-policy=TERMINATE \
--image-project=tdx-pilot \
--project tdx-pilot \
--metadata=container_hub="docker.io",container_image="amd64/hello-world@sha256:e2fc4e5012d16e7fe466f5291c476431beaa1f9b90a5c2125b493ed28e2aba57" \
--image tdx-img-f-"${NO}"
1 change: 0 additions & 1 deletion bin/tee-key-preexec/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ clap.workspace = true
hex.workspace = true
rand.workspace = true
secp256k1.workspace = true
sha3.workspace = true
teepot.workspace = true
tracing.workspace = true
tracing-log.workspace = true
Expand Down
23 changes: 6 additions & 17 deletions bin/tee-key-preexec/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@

use anyhow::{Context, Result};
use clap::Parser;
use secp256k1::{rand, PublicKey, Secp256k1, SecretKey};
use sha3::{Digest, Keccak256};
use secp256k1::{rand, Secp256k1};
use std::{ffi::OsString, os::unix::process::CommandExt, process::Command};
use teepot::ethereum::public_key_to_ethereum_address;
use teepot::quote::get_quote;
use tracing::error;
use tracing_log::LogTracer;
Expand All @@ -29,19 +29,6 @@ struct Args {
cmd_args: Vec<OsString>,
}

/// 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
}

fn main_with_error() -> Result<()> {
LogTracer::init().context("Failed to set logger")?;

Expand All @@ -54,7 +41,7 @@ fn main_with_error() -> Result<()> {
let mut rng = rand::thread_rng();
let secp = Secp256k1::new();
let (signing_key, verifying_key) = secp.generate_keypair(&mut rng);
let ethereum_address = public_key_to_address(&verifying_key);
let ethereum_address = public_key_to_ethereum_address(&verifying_key);
let tee_type = match get_quote(ethereum_address.as_ref()) {
Ok((tee_type, quote)) => {
// save quote to file
Expand Down Expand Up @@ -99,6 +86,8 @@ fn main() -> Result<()> {

#[cfg(test)]
mod tests {
use secp256k1::{PublicKey, Secp256k1, SecretKey};

use super::*;

#[test]
Expand All @@ -110,7 +99,7 @@ mod tests {
let secret_key = SecretKey::from_slice(&secret_key_bytes).unwrap();
let public_key = PublicKey::from_secret_key(&secp, &secret_key);
let expected_address = hex::decode("627306090abaB3A6e1400e9345bC60c78a8BEf57").unwrap();
let address = public_key_to_address(&public_key);
let address = public_key_to_ethereum_address(&public_key);

assert_eq!(address, expected_address.as_slice());
}
Expand Down
1 change: 1 addition & 0 deletions bin/verify-attestation/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@ anyhow.workspace = true
clap.workspace = true
hex.workspace = true
secp256k1.workspace = true
sha3.workspace = true
teepot.workspace = true
zksync_basic_types.workspace = true
32 changes: 23 additions & 9 deletions bin/verify-attestation/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@

use anyhow::{Context, Result};
use clap::{Args, Parser, Subcommand};
use secp256k1::{ecdsa::Signature, Message, PublicKey};
use core::convert::TryInto;
use hex::encode;
use secp256k1::{Message, PublicKey};
use std::{fs, io::Read, path::PathBuf, str::FromStr, time::UNIX_EPOCH};
use teepot::{
client::TcbLevel,
ethereum::recover_signer,
quote::{error, tee_qv_get_collateral, verify_quote_with_collateral, QuoteVerificationResult},
};
use zksync_basic_types::H256;
Expand Down Expand Up @@ -87,14 +90,25 @@ fn verify_signature(
let reportdata = &quote_verification_result.quote.get_report_data();
let public_key = PublicKey::from_slice(reportdata)?;
println!("Public key from attestation quote: {}", public_key);
let signature_bytes = fs::read(&signature_args.signature_file)?;
let signature = Signature::from_compact(&signature_bytes)?;
let root_hash_msg = Message::from_digest_slice(&signature_args.root_hash.0)?;
if signature.verify(&root_hash_msg, &public_key).is_ok() {
println!("Signature verified successfully");
} else {
println!("Failed to verify signature");
}
let signature_bytes: &[u8] = &fs::read(&signature_args.signature_file)?;
let ethereum_address_from_quote = &quote_verification_result.quote.get_report_data()[..20];
let root_hash_bytes = signature_args.root_hash.as_bytes();
let root_hash_msg = Message::from_digest_slice(root_hash_bytes)?;
let ethereum_address_from_signature =
recover_signer(&signature_bytes.try_into()?, &root_hash_msg)?;
let verification_successful = ethereum_address_from_signature == ethereum_address_from_quote;

println!(
"Signature '{}' {}. Ethereum address from attestation quote: {}. Ethereum address from signature: {}.",
encode(signature_bytes),
if verification_successful {
"verified successfully"
} else {
"verification failed"
},
encode(ethereum_address_from_quote),
encode(ethereum_address_from_signature)
);
Ok(())
}

Expand Down
51 changes: 31 additions & 20 deletions bin/verify-era-proof-attestation/src/verification.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,17 @@
use crate::{args::AttestationPolicyArgs, client::JsonRpcClient};
use anyhow::{Context, Result};
use hex::encode;
use secp256k1::{constants::PUBLIC_KEY_SIZE, ecdsa::Signature, Message, PublicKey};
use secp256k1::Message;
use teepot::{
client::TcbLevel,
ethereum::recover_signer,
quote::{
error::QuoteContext, tee_qv_get_collateral, verify_quote_with_collateral,
QuoteVerificationResult, Report,
},
};
use tracing::{debug, info, warn};
use zksync_basic_types::{L1BatchNumber, H256};
use zksync_basic_types::L1BatchNumber;

pub async fn verify_batch_proof(
quote_verification_result: &QuoteVerificationResult,
Expand All @@ -27,22 +28,38 @@ pub async fn verify_batch_proof(
}

let batch_no = batch_number.0;

let public_key = PublicKey::from_slice(
&quote_verification_result.quote.get_report_data()[..PUBLIC_KEY_SIZE],
)?;
debug!(batch_no, "public key: {}", public_key);

let root_hash = node_client.get_root_hash(batch_number).await?;
debug!(batch_no, "root hash: {}", root_hash);
let ethereum_address_from_quote = &quote_verification_result.quote.get_report_data()[..20];
let signature_bytes: &[u8; 65] = signature.try_into()?;
let root_hash_bytes = root_hash.as_bytes();
let root_hash_msg = Message::from_digest_slice(root_hash_bytes)?;
let ethereum_address_from_signature = recover_signer(signature_bytes, &root_hash_msg)?;
let verification_successful = ethereum_address_from_signature == ethereum_address_from_quote;
debug!(
batch_no,
"Root hash: {}. Ethereum address from the attestation quote: {}. Ethereum address from the signature: {}.",
root_hash,
encode(ethereum_address_from_quote),
encode(ethereum_address_from_signature),
);

let is_verified = verify_signature(signature, public_key, root_hash)?;
if is_verified {
info!(batch_no, signature = %encode(signature), "Signature verified successfully.");
if verification_successful {
info!(
batch_no,
signature = encode(signature),
ethereum_address = encode(ethereum_address_from_quote),
"Signature verified successfully."
);
} else {
warn!(batch_no, signature = %encode(signature), "Failed to verify signature!");
warn!(
batch_no,
signature = encode(signature),
ethereum_address_from_signature = encode(ethereum_address_from_signature),
ethereum_address_from_quote = encode(ethereum_address_from_quote),
"Failed to verify signature!"
);
}
Ok(is_verified)
Ok(verification_successful)
}

pub fn verify_attestation_quote(attestation_quote_bytes: &[u8]) -> Result<QuoteVerificationResult> {
Expand Down Expand Up @@ -85,12 +102,6 @@ pub fn log_quote_verification_summary(quote_verification_result: &QuoteVerificat
);
}

fn verify_signature(signature: &[u8], public_key: PublicKey, root_hash: H256) -> Result<bool> {
let signature = Signature::from_compact(signature)?;
let root_hash_msg = Message::from_digest_slice(&root_hash.0)?;
Ok(signature.verify(&root_hash_msg, &public_key).is_ok())
}

fn is_quote_matching_policy(
attestation_policy: &AttestationPolicyArgs,
quote_verification_result: &QuoteVerificationResult,
Expand Down
Loading
Loading