Livy Documentation

High-Level API

Livy, AttestBuilder, and Attestation — the primary integration point for application developers.

The high-level API wraps quote generation, ITA nonce fetching, and verification into three types: Livy (the client), AttestBuilder (the builder), and Attestation (the result). It requires the ita-verify feature flag.

livy-tee = { path = "../livy-tee", features = ["ita-verify"] }

Livy — the client

Livy reads your ITA API key and vends AttestBuilder instances.

use livy_tee::Livy;

// From environment variable ITA_API_KEY
let livy = Livy::from_env()?;

// Or explicit key
let livy = Livy::new("your-ita-api-key");

// Or from a full ItaConfig
use livy_tee::ItaConfig;
let livy = Livy::with_config(ItaConfig {
    api_key: "your-key".to_string(),
    ..ItaConfig::default()
});

AttestBuilder — committing values

livy.attest() returns an AttestBuilder. Use it to commit public values, set a nonce, and then finalize.

let mut builder = livy.attest();

// Commit public typed values in any order.
// Values are readable by anyone who receives the attestation.
builder.commit(&content_hash);       // [u8; 32]
builder.commit(&identity_pubkey);    // String
builder.commit(&capture_timestamp);  // i64

// For sensitive data, commit a hash instead of the raw value.
builder.commit_hashed(&private_input);  // commits SHA-256(serialized(value))

// Replay protection — use a monotonically increasing counter.
builder.nonce(request_counter);

// Generate the TDX quote and get an ITA attestation.
let attestation = builder.finalize().await?;

commit and nonce return &mut Self, so you can chain them:

let attestation = livy.attest()
    .commit(&content_hash)
    .commit(&identity_pubkey)
    .nonce(counter)
    .finalize()
    .await?;

Commit vs commit_hashed

MethodStoresVerifier can see
commit(&value)JSON-serialized value in plain textFull value
commit_hashed(&value)SHA-256 of JSON-serialized value (32 bytes)Hash only
commit_raw(bytes)Raw bytesRaw bytes

Only commit data you intend to make public. For sensitive inputs, use commit_hashed — the verifier confirms the hash matches their copy of the data without you revealing the data itself.

Attestation — the result

finalize().await returns an Attestation containing all fields needed for verification.

let attestation = livy.attest()
    .commit(&my_input)
    .commit(&my_output)
    .nonce(42)
    .finalize()
    .await?;

// Fields available on every Attestation
println!("ita_token:    {}", attestation.ita_token);
println!("mrtd:         {}", attestation.mrtd);
println!("tcb_status:   {}", attestation.tcb_status);
println!("payload_hash: {}", attestation.payload_hash_hex());

Attestation fields

FieldTypeDescription
ita_tokenStringITA-signed JWT. Verifiable against Intel's JWKS endpoint. Contains MRTD, REPORTDATA hash, and TCB status.
mrtdStringHex-encoded 48-byte measurement of the TEE binary (96 hex chars).
tcb_statusString"UpToDate" / "OutOfDate" / "Revoked". OutOfDate still attests correctly.
raw_quoteStringBase64-encoded raw DCAP quote (~8 KB).
runtime_dataStringBase64-encoded 64-byte ReportData struct.
verifier_nonce_valStringBase64-encoded ITA verifier nonce value.
verifier_nonce_iatStringBase64-encoded ITA verifier nonce issued-at.
report_dataReportDataStructured REPORTDATA parsed from runtime_data.
public_valuesPublicValuesAll committed values — read with .public_values.read::<T>().

Reading committed values back

The verifier reads values from public_values in commit order:

// TEE side committed these in order:
// builder.commit(&content_hash).commit(&identity_pubkey)

// Verifier reads them back in the same order:
let content_hash: [u8; 32] = attestation.public_values.read();
let pubkey: String          = attestation.public_values.read();

assert_eq!(content_hash, sha256(&original_file));

Verification

Full chain verification

// Checks:
// 1. SHA-512(nonce_val ‖ nonce_iat ‖ runtime_data) == REPORTDATA in the TDX quote
// 2. SHA-256(public_values buffer) == report_data.payload_hash
//
// No TDX hardware. No network. Pure software.
let ok = attestation.verify()?;
assert!(ok);

Local self-consistency check only

// Only checks: SHA-256(public_values) == report_data.payload_hash
// Does NOT verify the TDX quote binding.
// Use verify() for full chain verification.
let ok = attestation.verify_public_values_commitment();

Standalone verify functions

If you have the raw fields (e.g. received over an API) rather than an Attestation struct:

use livy_tee::{verify_quote, verify_quote_with_public_values};

// With a pre-computed payload_hash
let ok = verify_quote(
    &raw_quote_b64,
    &runtime_data_b64,
    &nonce_val_b64,
    &nonce_iat_b64,
    &expected_payload_hash,
)?;

// With a PublicValues buffer (hash computed automatically)
let ok = verify_quote_with_public_values(
    &raw_quote_b64,
    &runtime_data_b64,
    &nonce_val_b64,
    &nonce_iat_b64,
    &public_values,
)?;

Complete example

use livy_tee::{Livy, PublicValues};
use sha2::{Digest, Sha256};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let livy = Livy::from_env()?;

    // Your program's inputs and outputs.
    let content_hash: [u8; 32] = Sha256::digest(b"photo bytes").into();
    let identity_pubkey = "did:key:z6MkjoRhExample".to_string();
    let capture_ts: i64 = 1_717_171_717_000;
    let counter: u64 = 42;

    // Commit and attest.
    let attestation = livy.attest()
        .commit(&content_hash)
        .commit(&identity_pubkey)
        .commit(&capture_ts)
        .nonce(counter)
        .finalize()
        .await?;

    // Verify locally.
    assert!(attestation.verify()?);
    println!("tcb_status: {}", attestation.tcb_status);
    println!("payload_hash: {}", attestation.payload_hash_hex());

    // On the verifier side — read values back.
    let hash: [u8; 32] = attestation.public_values.read();
    let pubkey: String  = attestation.public_values.read();
    let ts: i64         = attestation.public_values.read();

    assert_eq!(hash, content_hash);
    assert_eq!(pubkey, identity_pubkey);
    assert_eq!(ts, capture_ts);

    Ok(())
}