From dc010aca98bdb1da5bc9d41cbfe2b1991cf879bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patryk=20B=C4=99za?= Date: Fri, 20 Dec 2024 12:19:19 +0100 Subject: [PATCH] feat(tee-key-preexec): add support for Solidity-compatible pubkey in report_data This PR is part of the effort to implement on-chain TEE proof verification. This PR goes hand in hand with https://github.com/matter-labs/zksync-era/pull/3414. --- Cargo.lock | 5 + Cargo.toml | 1 + bin/tee-key-preexec/Cargo.toml | 1 + bin/tee-key-preexec/src/main.rs | 33 ++- bin/verify-attestation/Cargo.toml | 1 + bin/verify-attestation/src/main.rs | 32 ++- .../src/verification.rs | 78 ++++--- crates/teepot/Cargo.toml | 4 +- crates/teepot/src/ethereum/mod.rs | 86 ++++++++ crates/teepot/src/lib.rs | 2 + crates/teepot/src/prover/mod.rs | 6 + crates/teepot/src/prover/reportdata.rs | 192 ++++++++++++++++++ 12 files changed, 400 insertions(+), 41 deletions(-) create mode 100644 crates/teepot/src/ethereum/mod.rs create mode 100644 crates/teepot/src/prover/mod.rs create mode 100644 crates/teepot/src/prover/reportdata.rs diff --git a/Cargo.lock b/Cargo.lock index c447712..b770552 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5214,6 +5214,7 @@ version = "0.3.0" dependencies = [ "anyhow", "clap 4.5.23", + "hex", "rand", "secp256k1 0.29.1", "teepot", @@ -5324,10 +5325,12 @@ dependencies = [ "rand", "rsa", "rustls", + "secp256k1 0.29.1", "serde", "serde_json", "serde_with 3.11.0", "sha2", + "sha3", "signature 2.2.0", "tdx-attest-rs", "teepot-tee-quote-verification-rs", @@ -5341,6 +5344,7 @@ dependencies = [ "webpki-roots", "x509-cert", "zeroize", + "zksync_basic_types", ] [[package]] @@ -6031,6 +6035,7 @@ dependencies = [ "clap 4.5.23", "hex", "secp256k1 0.29.1", + "sha3", "teepot", "zksync_basic_types", ] diff --git a/Cargo.toml b/Cargo.toml index 31bbed1..54564d4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,6 +49,7 @@ serde = { version = "1", features = ["derive", "rc"] } serde_json = "1" serde_with = { version = "3.8", features = ["base64", "hex"] } sha2 = "0.10.8" +sha3 = "0.10.8" signature = "2.2.0" tdx-attest-rs = { version = "0.1.2", git = "https://github.com/intel/SGXDataCenterAttestationPrimitives.git", rev = "aa239d25a437a28f3f4de92c38f5b6809faac842" } teepot = { path = "crates/teepot" } diff --git a/bin/tee-key-preexec/Cargo.toml b/bin/tee-key-preexec/Cargo.toml index e5a62cc..20d3e37 100644 --- a/bin/tee-key-preexec/Cargo.toml +++ b/bin/tee-key-preexec/Cargo.toml @@ -12,6 +12,7 @@ repository.workspace = true [dependencies] anyhow.workspace = true clap.workspace = true +hex.workspace = true rand.workspace = true secp256k1.workspace = true teepot.workspace = true diff --git a/bin/tee-key-preexec/src/main.rs b/bin/tee-key-preexec/src/main.rs index 70be9c4..ac38432 100644 --- a/bin/tee-key-preexec/src/main.rs +++ b/bin/tee-key-preexec/src/main.rs @@ -8,8 +8,9 @@ use anyhow::{Context, Result}; use clap::Parser; -use secp256k1::{rand, Keypair, PublicKey, Secp256k1, SecretKey}; +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; @@ -37,14 +38,11 @@ fn main_with_error() -> Result<()> { tracing::subscriber::set_global_default(subscriber).context("Failed to set logger")?; let args = Args::parse(); - let mut rng = rand::thread_rng(); let secp = Secp256k1::new(); - let keypair = Keypair::new(&secp, &mut rng); - let signing_key = SecretKey::from_keypair(&keypair); - let verifying_key = PublicKey::from_keypair(&keypair); - let verifying_key_bytes = verifying_key.serialize(); - let tee_type = match get_quote(verifying_key_bytes.as_ref()) { + let (signing_key, verifying_key) = secp.generate_keypair(&mut rng); + 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 std::fs::write(TEE_QUOTE_FILE, quote)?; @@ -85,3 +83,24 @@ fn main() -> Result<()> { } ret } + +#[cfg(test)] +mod tests { + use secp256k1::{PublicKey, Secp256k1, SecretKey}; + + use super::*; + + #[test] + fn test_public_key_to_address() { + let secp = Secp256k1::new(); + let secret_key_bytes = + hex::decode("c87509a1c067bbde78beb793e6fa76530b6382a4c0241e5e4a9ec0a0f44dc0d3") + .unwrap(); + 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_ethereum_address(&public_key); + + assert_eq!(address, expected_address.as_slice()); + } +} diff --git a/bin/verify-attestation/Cargo.toml b/bin/verify-attestation/Cargo.toml index ff473d9..5a7c276 100644 --- a/bin/verify-attestation/Cargo.toml +++ b/bin/verify-attestation/Cargo.toml @@ -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 diff --git a/bin/verify-attestation/src/main.rs b/bin/verify-attestation/src/main.rs index d799462..8ba0c2b 100644 --- a/bin/verify-attestation/src/main.rs +++ b/bin/verify-attestation/src/main.rs @@ -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; @@ -87,14 +90,25 @@ fn verify_signature( let reportdata = "e_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 = "e_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(()) } diff --git a/bin/verify-era-proof-attestation/src/verification.rs b/bin/verify-era-proof-attestation/src/verification.rs index b389500..3b18053 100644 --- a/bin/verify-era-proof-attestation/src/verification.rs +++ b/bin/verify-era-proof-attestation/src/verification.rs @@ -2,11 +2,13 @@ // Copyright (c) 2023-2024 Matter Labs use crate::{args::AttestationPolicyArgs, client::JsonRpcClient}; -use anyhow::{Context, Result}; +use anyhow::{anyhow, Context, Result}; use hex::encode; -use secp256k1::{constants::PUBLIC_KEY_SIZE, ecdsa::Signature, Message, PublicKey}; +use secp256k1::{ecdsa::Signature, Message}; use teepot::{ client::TcbLevel, + ethereum::recover_signer, + prover::reportdata::ReportData, quote::{ error::QuoteContext, tee_qv_get_collateral, verify_quote_with_collateral, QuoteVerificationResult, Report, @@ -15,6 +17,51 @@ use teepot::{ use tracing::{debug, info, warn}; use zksync_basic_types::{L1BatchNumber, H256}; +struct TeeProof { + report: ReportData, + root_hash: H256, + signature: Vec, +} + +impl TeeProof { + pub fn new(report: ReportData, root_hash: H256, signature: Vec) -> Self { + Self { + report, + root_hash, + signature, + } + } + + pub fn verify(&self) -> Result { + match &self.report { + ReportData::V0(report) => { + let signature = Signature::from_compact(&self.signature)?; + let root_hash_msg = Message::from_digest_slice(&self.root_hash.0)?; + Ok(signature.verify(&root_hash_msg, &report.pubkey).is_ok()) + } + ReportData::V1(report) => { + let ethereum_address_from_report = report.ethereum_addr; + let root_hash_msg = Message::from_digest_slice(self.root_hash.as_bytes())?; + let signature_bytes: [u8; 65] = self + .signature + .clone() + .try_into() + .map_err(|e| anyhow!("{:?}", e))?; + let ethereum_address_from_signature = + recover_signer(&signature_bytes, &root_hash_msg)?; + debug!( + "Root hash: {}. Ethereum address from the attestation quote: {}. Ethereum address from the signature: {}.", + self.root_hash, + encode(ethereum_address_from_report), + encode(ethereum_address_from_signature), + ); + Ok(ethereum_address_from_signature == ethereum_address_from_report) + } + ReportData::Unknown(_) => Ok(false), + } + } +} + pub async fn verify_batch_proof( quote_verification_result: &QuoteVerificationResult, attestation_policy: &AttestationPolicyArgs, @@ -26,23 +73,12 @@ pub async fn verify_batch_proof( return Ok(false); } - let batch_no = batch_number.0; - - let public_key = PublicKey::from_slice( - "e_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 is_verified = verify_signature(signature, public_key, root_hash)?; - if is_verified { - info!(batch_no, signature = %encode(signature), "Signature verified successfully."); - } else { - warn!(batch_no, signature = %encode(signature), "Failed to verify signature!"); - } - Ok(is_verified) + let report_data_bytes = quote_verification_result.quote.get_report_data(); + let report_data = ReportData::try_from(report_data_bytes)?; + let tee_proof = TeeProof::new(report_data, root_hash, signature.to_vec()); + let verification_successful = tee_proof.verify().is_ok(); + Ok(verification_successful) } pub fn verify_attestation_quote(attestation_quote_bytes: &[u8]) -> Result { @@ -85,12 +121,6 @@ pub fn log_quote_verification_summary(quote_verification_result: &QuoteVerificat ); } -fn verify_signature(signature: &[u8], public_key: PublicKey, root_hash: H256) -> Result { - 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, diff --git a/crates/teepot/Cargo.toml b/crates/teepot/Cargo.toml index df20004..3b3b6b8 100644 --- a/crates/teepot/Cargo.toml +++ b/crates/teepot/Cargo.toml @@ -32,10 +32,12 @@ pkcs8.workspace = true rand.workspace = true rsa.workspace = true rustls.workspace = true +secp256k1 = { workspace = true, features = ["recovery"] } serde.workspace = true serde_json.workspace = true serde_with.workspace = true sha2.workspace = true +sha3.workspace = true signature.workspace = true tdx-attest-rs.workspace = true thiserror.workspace = true @@ -47,8 +49,8 @@ x509-cert.workspace = true zeroize.workspace = true [dev-dependencies] -anyhow.workspace = true base64.workspace = true testaso.workspace = true tokio.workspace = true tracing-test.workspace = true +zksync_basic_types.workspace = true diff --git a/crates/teepot/src/ethereum/mod.rs b/crates/teepot/src/ethereum/mod.rs new file mode 100644 index 0000000..a8ab878 --- /dev/null +++ b/crates/teepot/src/ethereum/mod.rs @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2023-2024 Matter Labs + +//! Ethereum-specific helper functions for on-chain verification of Intel SGX attestation. + +use anyhow::Result; +use secp256k1::{ + ecdsa::{RecoverableSignature, RecoveryId}, + Message, PublicKey, SECP256K1, +}; +use sha3::{Digest, Keccak256}; + +/// Equivalent to the ecrecover precompile, ensuring that the signatures we produce off-chain +/// can be recovered on-chain. +pub fn recover_signer(sig: &[u8; 65], root_hash: &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(root_hash, &sig)?; + Ok(public_key_to_ethereum_address(&public)) +} + +/// Converts a public key into an Ethereum address by hashing the encoded public key with Keccak256. +pub fn public_key_to_ethereum_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 +} + +#[cfg(test)] +mod tests { + use secp256k1::{Secp256k1, SecretKey}; + use zksync_basic_types::H256; + + use super::*; + + /// Signs the message in Ethereum-compatible format for on-chain verification. + fn sign_message(sec: &SecretKey, message: Message) -> Result<[u8; 65]> { + 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) + } + + #[test] + fn recover() { + // Decode the sample secret key, generate the public key, and derive the Ethereum address + // from the public key + let secp = Secp256k1::new(); + let secret_key_bytes = + hex::decode("c87509a1c067bbde78beb793e6fa76530b6382a4c0241e5e4a9ec0a0f44dc0d3") + .unwrap(); + 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_ethereum_address(&public_key); + + assert_eq!(address, expected_address.as_slice()); + + // Generate a random root hash, create a message from the hash, and sign the message using + // the secret key + let root_hash = H256::random(); + let root_hash_bytes = root_hash.as_bytes(); + let msg_to_sign = Message::from_digest_slice(root_hash_bytes).unwrap(); + let signature = sign_message(&secret_key, msg_to_sign).unwrap(); + + // Recover the signer's Ethereum address from the signature and the message, and verify it + // matches the expected address + let proof_addr = recover_signer(&signature, &msg_to_sign).unwrap(); + + assert_eq!(proof_addr, expected_address.as_slice()); + } +} diff --git a/crates/teepot/src/lib.rs b/crates/teepot/src/lib.rs index 2d9f7cc..8f73489 100644 --- a/crates/teepot/src/lib.rs +++ b/crates/teepot/src/lib.rs @@ -7,8 +7,10 @@ #![deny(clippy::all)] pub mod client; +pub mod ethereum; pub mod json; pub mod log; +pub mod prover; pub mod quote; pub mod server; pub mod sgx; diff --git a/crates/teepot/src/prover/mod.rs b/crates/teepot/src/prover/mod.rs new file mode 100644 index 0000000..d7b3e63 --- /dev/null +++ b/crates/teepot/src/prover/mod.rs @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2023 Matter Labs + +//! Common functionality for TEE provers and verifiers. + +pub mod reportdata; diff --git a/crates/teepot/src/prover/reportdata.rs b/crates/teepot/src/prover/reportdata.rs new file mode 100644 index 0000000..182b869 --- /dev/null +++ b/crates/teepot/src/prover/reportdata.rs @@ -0,0 +1,192 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2024 Matter Labs + +//! Versioning of Intel SGX/TDX quote's report data for TEE prover and verifier. + +use core::convert::Into; + +use anyhow::{anyhow, Result}; +use secp256k1::{constants::PUBLIC_KEY_SIZE, PublicKey}; + +/// Report data length for Intel SGX/TDX. +const REPORT_DATA_LENGTH: usize = 64; + +/// Ethereum address length. +const ETHEREUM_ADDR_LENGTH: usize = 20; + +/// Report data version. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum ReportData { + /// Legacy version of report data that was initially not intended to be versioned. + /// The report_data was on-chain incompatible and consisted of a compressed ECDSA public key. + /// + /// +-------------------------------------+--------------------------------+------------------+ + /// | compressed ECDSA pubkey (33 bytes) | zeroes (30 bytes) | version (1 byte) | + /// +-------------------------------------+--------------------------------+------------------+ + V0(ReportDataV0), + /// Latest version of report data compatible with on-chain verification. + /// + /// +--------------------------+-------------------------------------------+------------------+ + /// | Ethereum addr (20 bytes) | zeros (43 bytes) | version (1 byte) | + /// +--------------------------+-------------------------------------------+------------------+ + V1(ReportDataV1), + /// Unknown version of report data. + Unknown(Vec), +} + +impl TryFrom<&[u8]> for ReportData { + type Error = anyhow::Error; + + fn try_from(report_data_bytes: &[u8]) -> Result { + if report_data_bytes.len() != REPORT_DATA_LENGTH { + return Err(anyhow!("Invalid byte slice length")); + } + let version = report_data_bytes[REPORT_DATA_LENGTH - 1]; + match version { + 0 => Ok(Self::V0(ReportDataV0::try_from(report_data_bytes)?)), + 1 => Ok(Self::V1(ReportDataV1::try_from(report_data_bytes)?)), + _ => Ok(Self::Unknown(report_data_bytes.into())), + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +#[allow(missing_docs)] +pub struct ReportDataV0 { + pub pubkey: PublicKey, +} + +impl TryFrom<&[u8]> for ReportDataV0 { + type Error = anyhow::Error; + + fn try_from(report_data_bytes: &[u8]) -> Result { + if report_data_bytes.len() != REPORT_DATA_LENGTH { + return Err(anyhow!("Invalid byte slice length")); + } + let pubkey = PublicKey::from_slice(&report_data_bytes[..PUBLIC_KEY_SIZE])?; + Ok(Self { pubkey }) + } +} + +impl Into<[u8; REPORT_DATA_LENGTH]> for ReportDataV0 { + fn into(self) -> [u8; REPORT_DATA_LENGTH] { + let mut bytes = [0u8; REPORT_DATA_LENGTH]; + bytes[..PUBLIC_KEY_SIZE].copy_from_slice(&self.pubkey.serialize()); + bytes + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +#[allow(missing_docs)] +pub struct ReportDataV1 { + pub ethereum_addr: [u8; ETHEREUM_ADDR_LENGTH], +} + +impl TryFrom<&[u8]> for ReportDataV1 { + type Error = anyhow::Error; + + fn try_from(report_data_bytes: &[u8]) -> Result { + if report_data_bytes.len() != REPORT_DATA_LENGTH { + return Err(anyhow!("Invalid byte slice length")); + } + let mut ethereum_addr = [0u8; ETHEREUM_ADDR_LENGTH]; + ethereum_addr.copy_from_slice(&report_data_bytes[..ETHEREUM_ADDR_LENGTH]); + Ok(Self { ethereum_addr }) + } +} + +impl Into<[u8; REPORT_DATA_LENGTH]> for ReportDataV1 { + fn into(self) -> [u8; REPORT_DATA_LENGTH] { + let mut bytes = [0u8; REPORT_DATA_LENGTH]; + bytes[..ETHEREUM_ADDR_LENGTH].copy_from_slice(&self.ethereum_addr); + bytes[REPORT_DATA_LENGTH - 1] = 1; + bytes + } +} + +#[cfg(test)] +mod tests { + use super::*; + use hex; + use secp256k1::{Secp256k1, SecretKey}; + + const ETHEREUM_ADDR: [u8; ETHEREUM_ADDR_LENGTH] = [ + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, + ]; + + fn generate_test_report_data(version_byte: u8) -> [u8; REPORT_DATA_LENGTH] { + let mut data = [0u8; REPORT_DATA_LENGTH]; + data[..ETHEREUM_ADDR.len()].copy_from_slice(ÐEREUM_ADDR); + data[REPORT_DATA_LENGTH - 1] = version_byte; + data + } + + fn generate_test_pubkey() -> PublicKey { + let secp = Secp256k1::new(); + let secret_key_bytes = + hex::decode("c87509a1c067bbde78beb793e6fa76530b6382a4c0241e5e4a9ec0a0f44dc0d3") + .unwrap(); + let secret_key = SecretKey::from_slice(&secret_key_bytes).unwrap(); + PublicKey::from_secret_key(&secp, &secret_key) + } + + fn generate_test_report_data_v0(pubkey: PublicKey) -> [u8; REPORT_DATA_LENGTH] { + let pubkey_bytes = pubkey.serialize(); + let mut report_data_bytes = [0u8; REPORT_DATA_LENGTH]; + report_data_bytes[..PUBLIC_KEY_SIZE].copy_from_slice(&pubkey_bytes); + report_data_bytes + } + + #[test] + fn test_from_bytes_v0() { + let pubkey = generate_test_pubkey(); + let report_data_bytes = generate_test_report_data_v0(pubkey); + let report_data = ReportData::try_from(report_data_bytes.as_ref()).unwrap(); + assert_eq!(report_data, ReportData::V0(ReportDataV0 { pubkey })); + } + + #[test] + fn report_data_from_bytes_v1() { + let data = generate_test_report_data(1); + let report_data = ReportData::try_from(data.as_ref()).unwrap(); + assert_eq!( + report_data, + ReportData::V1(ReportDataV1 { + ethereum_addr: ETHEREUM_ADDR + }) + ); + } + + #[test] + fn report_data_from_bytes_unknown() { + let report_data_bytes = generate_test_report_data(99); + let report_data = ReportData::try_from(report_data_bytes.as_ref()).unwrap(); + assert_eq!(report_data, ReportData::Unknown(report_data_bytes.into())); + } + + #[test] + fn report_data_to_bytes_v0() { + let pubkey = generate_test_pubkey(); + let report_data = ReportDataV0 { pubkey }; + let report_data: [u8; REPORT_DATA_LENGTH] = report_data.into(); + assert_eq!(&report_data[..PUBLIC_KEY_SIZE], pubkey.serialize().as_ref()); + assert_eq!(report_data[REPORT_DATA_LENGTH - 1], 0); + assert!(report_data[PUBLIC_KEY_SIZE..REPORT_DATA_LENGTH - 1] + .iter() + .all(|&byte| byte == 0)); + } + + #[test] + fn report_data_to_bytes_v1() { + let report_data = ReportDataV1 { + ethereum_addr: ETHEREUM_ADDR, + }; + let report_data: [u8; REPORT_DATA_LENGTH] = report_data.into(); + assert_eq!(&report_data[..ETHEREUM_ADDR_LENGTH], ÐEREUM_ADDR); + assert_eq!(report_data[REPORT_DATA_LENGTH - 1], 1); + assert!(report_data[ETHEREUM_ADDR_LENGTH..REPORT_DATA_LENGTH - 1] + .iter() + .all(|&byte| byte == 0)); + } +}