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(×tamp_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
| Method | Description |
|---|---|
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 |