Livy Documentation

Public Values

The commit/read model for binding typed outputs to a TDX attestation.

PublicValues is the bridge between your program's outputs and the TDX hardware quote. It lets you commit any typed values during execution; verifiers read them back in the same order and confirm they are cryptographically bound to the hardware attestation.

The design is inspired by zkVM journal semantics (e.g. SP1's env::commit). The trust model is different — hardware attestation rather than mathematical proof — but the ergonomics are identical.

Commit model (TEE side)

Values are written sequentially with commit. Order matters: verifiers read them back in the same order.

use livy_tee::PublicValues;

let mut pv = PublicValues::new();

// Commit typed values in any order.
pv.commit(&content_hash);      // [u8; 32]
pv.commit(&identity_pubkey);   // String
pv.commit(&timestamp_ms);      // i64
pv.commit(&metadata);          // any Serialize type

// The commitment hash goes into REPORTDATA[0..32].
let hash: [u8; 32] = pv.commitment_hash();

commitment_hash() computes SHA-256(buffer). This is the value embedded in ReportData.payload_hash and ultimately in the TDX hardware quote.

Committing sensitive values

Values committed with commit are stored in plain text and readable by anyone who receives the attestation. For sensitive data, commit a hash instead:

// Commits SHA-256(serialized(value)) — 32 bytes, no raw content.
builder.commit_hashed(&private_key_material);

// Or manually:
use sha2::{Digest, Sha256};
let hash: [u8; 32] = Sha256::digest(serde_json::to_vec(&value)?).into();
pv.commit_raw(&hash);

The verifier independently hashes the same value and checks the 32-byte commitment matches.

Read model (verifier side)

Read values back in commit order. The verifier does not need to know the wire format — read<T>() handles deserialization.

use livy_tee::PublicValues;

// Reconstruct from the bytes received alongside the attestation.
let pv = PublicValues::from_bytes(proof_bytes);

// Read in the same order they were committed.
let content_hash: [u8; 32] = pv.read();
let pubkey: String          = pv.read();
let timestamp: i64          = pv.read();

// Verify the buffer matches what's in the TDX quote.
assert_eq!(pv.commitment_hash(), report_data.payload_hash);

try_read is the fallible version — useful when you are not sure how many values were committed:

while let Ok(entry) = pv.try_read::<serde_json::Value>() {
    println!("{:?}", entry);
}

Wire format

The buffer is a sequence of length-prefixed JSON-encoded entries:

[len: u32 LE] [json bytes ...] [len: u32 LE] [json bytes ...] ...

commitment_hash() = SHA-256(entire buffer). The full buffer travels alongside the attestation so any verifier can recompute the commitment without knowing the schema.

Cross-language verifiers that only want to check the commitment do not need to parse individual entries — they compute SHA-256(buffer_bytes) and compare against REPORTDATA[0..32].

Base64 transport

The buffer serializes to base64 for transport (e.g. in JSON API responses):

// Sender (TEE side)
let b64 = pv.to_base64();

// Receiver (verifier side)
let pv = PublicValues::from_base64(&b64)?;
let value: String = pv.read();
assert!(pv.verify_commitment(&expected_hash));

Commitment verification

// Check the buffer against an expected hash (e.g. from a stored ProvenanceRecord).
let ok = pv.verify_commitment(&record.payload_hash);
assert!(ok, "public values were tampered with");

This is a local self-consistency check. For full chain verification — confirming the hash is bound to the TDX hardware quote — use Attestation::verify or verify_quote_with_public_values.

API reference

MethodDescription
PublicValues::new()Create an empty buffer for committing
PublicValues::from_bytes(bytes)Reconstruct from raw bytes
PublicValues::from_base64(b64)Reconstruct from base64
pv.commit(&value)Commit a typed value (serialized as JSON)
pv.commit_raw(bytes)Commit raw bytes (no serialization wrapper)
pv.read::<T>()Read the next value, panics on failure
pv.try_read::<T>()Read the next value, returns Err on failure
pv.read_raw()Read the next value as raw bytes
pv.reset_cursor()Reset the read cursor to the beginning
pv.commitment_hash()SHA-256(buffer) — the REPORTDATA payload
pv.verify_commitment(&hash)Check buffer matches expected hash
pv.to_base64()Serialize buffer to base64
pv.as_bytes()Reference to the raw buffer
pv.len()Number of bytes in the buffer