Livy Documentation

Verification

How to verify a TDX attestation independently — no Livy infrastructure, no TDX hardware, no network required.

Any party who receives an attestation can verify it without contacting Livy, Intel Trust Authority, or any external service. All verification steps are pure cryptographic operations over public data.

What you need

  • The original committed values (or their hashes if sensitive data was committed with commit_hashed).
  • The attestation fields: raw_quote, runtime_data, verifier_nonce_val, verifier_nonce_iat.
  • The expected MRTD — published by Livy from reproducible builds, or computed locally from the same source.

Using the Attestation struct

If you have an Attestation value (from finalize() or deserialized from storage):

use livy_tee::{Attestation, PublicValues};

// Full chain verification — the correct method for callers.
// Checks:
//   1. SHA-512(nonce_val ‖ nonce_iat ‖ runtime_data) == REPORTDATA in TDX quote
//   2. SHA-256(public_values buffer) == report_data.payload_hash
let ok = attestation.verify()?;
assert!(ok, "attestation is invalid");

// Read committed values back (in commit order).
let content_hash: [u8; 32] = attestation.public_values.read();
let identity_pubkey: String = attestation.public_values.read();
assert_eq!(content_hash, sha256(&original_bytes));

Standalone verify functions

If you have the raw fields (e.g. from a REST API response), use the standalone functions. Both require the ita-verify feature.

With a PublicValues buffer

use livy_tee::{verify_quote_with_public_values, PublicValues};

let public_values = PublicValues::from_base64(&record.public_values_b64)?;

let ok = verify_quote_with_public_values(
    &record.raw_quote,
    &record.runtime_data,
    &record.verifier_nonce_val,
    &record.verifier_nonce_iat,
    &public_values,
)?;
assert!(ok);

With a pre-computed payload_hash

Use this when you build the payload hash yourself (low-level usage):

use livy_tee::verify_quote;

// Build the expected payload_hash the same way the TEE did.
let expected_payload_hash: [u8; 32] = sha256_of_public_values_buffer;

let ok = verify_quote(
    &record.raw_quote,
    &record.runtime_data,
    &record.verifier_nonce_val,
    &record.verifier_nonce_iat,
    &expected_payload_hash,
)?;
assert!(ok);

Step-by-step manual verification

If you are writing a verifier in another language or want to perform verification without the livy-tee crate:

Step 1 — Decode inputs

raw_quote    = base64_decode(record.raw_quote)
runtime_data = base64_decode(record.runtime_data)   // 64 bytes
nonce_val    = base64_decode(record.verifier_nonce_val)
nonce_iat    = base64_decode(record.verifier_nonce_iat)

Step 2 — Verify the REPORTDATA binding

Extract bytes [568..632] from raw_quote — this is the 64-byte REPORTDATA field.

Recompute:

expected_reportdata = SHA-512(nonce_val ‖ nonce_iat ‖ runtime_data)

Assert expected_reportdata == raw_quote[568..632]. This confirms the quote is bound to exactly the runtime_data you have.

Step 3 — Parse runtime_data

payload_hash  = runtime_data[0..32]
build_id      = runtime_data[32..40]
version_code  = u32_be(runtime_data[40..44])
build_number  = u32_be(runtime_data[44..48])
nonce         = u64_be(runtime_data[48..56])

Step 4 — Verify payload_hash

Reconstruct the public values buffer from the record, then:

expected_payload_hash = SHA-256(public_values_buffer)

Assert expected_payload_hash == payload_hash. This confirms the committed values were not tampered with.

Step 5 — Verify build identity

expected_build_id = SHA-256(tee_binary_bytes)[0..8]

Assert expected_build_id == build_id. This confirms which binary ran.

Step 6 — Verify nonce

Assert nonce == expected_nonce_for_this_request. This prevents proof reuse across requests.

Step 7 — (Optional) Verify the ITA JWT

Fetch Intel Trust Authority's public keys and verify the JWT signature:

GET https://portal.trustauthority.intel.com/certs

Check tdx_mrtd in JWT claims against the expected MRTD. Check tcb_status == "UpToDate" (or your accepted threshold).

This step requires network access but is optional — steps 1–6 fully verify the data binding.

What each check proves

StepWhat it proves
REPORTDATA binding (step 2)The quote is bound to this exact runtime_data via the ITA nonce. A replayed quote from another session has a different REPORTDATA.
payload_hash (step 4)The public values buffer has not been tampered with. Every committed value is exactly as the TEE wrote it.
build_id (step 5)The binary that ran is the one at the published source commit. A different binary would produce a different hash.
nonce (step 6)This proof is for this specific request, not a replay of a previous one.
ITA JWT (step 7)The quote signature chain is valid and roots in Intel's hardware CA. The TCB is current.

What verification does not prove

  • That Livy's infrastructure is available (verification is fully offline after step 6).
  • That the TEE was not shut down before producing the quote (liveness is not attested).
  • That the nonce monotonicity is enforced externally — the verifier must track nonces themselves.

Verifying the ITA JWT directly

report_data_from_token extracts the structured ReportData from an ITA JWT without making any network calls. Useful for auditing stored tokens:

use livy_tee::report_data_from_token;

if let Some(rd) = report_data_from_token(&ita_token)? {
    println!("payload_hash: {}", hex::encode(rd.payload_hash));
    println!("nonce:        {}", rd.nonce);
    println!("build_id:     {}", hex::encode(rd.build_id));
}

Full JWT signature verification against ITA's JWKS endpoint is separate and requires a network call:

# Intel's JWKS endpoint
curl https://portal.trustauthority.intel.com/certs