Livy Documentation

ReportData

The 64-byte REPORTDATA field embedded in every TDX quote — wire layout, fields, and verification.

ReportData is the 64-byte field that Livy embeds inside every TDX DCAP quote. It is the structured commit from your application to the hardware: the quote's REPORTDATA field contains exactly these bytes (wrapped in a SHA-512 hash together with the ITA nonce).

Wire layout

All multi-byte integers are big-endian.

Bytes    Size  Field         Description
─────    ────  ─────         ───────────
00..32    32   payload_hash  SHA-256(public_values buffer).
                             Commits to all values your program chose to make public.

32..40     8   build_id      SHA-256(server binary)[0..8].
                             Short, human-verifiable build fingerprint.
                             Anyone who can reproduce the build can verify this.

40..44     4   version_code  u32 BE — schema version (currently 1).
                             Increment when the wire layout changes.

44..48     4   build_number  u32 BE — CI build counter.
                             0 in development, CI-assigned in production.

48..56     8   nonce         u64 BE — monotonic request counter.
                             Replay protection: prevents a valid proof from being
                             reused across different requests.

56..64     8   reserved      Zero-filled. Reserved for future fields.

The REPORTDATA field sits at bytes [568..632] of the DCAP quote, but it is not stored there directly — the ITA protocol replaces it with:

REPORTDATA in quote = SHA-512( nonce.val ‖ nonce.iat ‖ runtime_data )

where runtime_data is the 64-byte ReportData struct above, and nonce.val / nonce.iat are the ITA verifier nonce fields. The original runtime_data travels alongside the quote so verifiers can reconstruct this hash.

Using ReportData directly

For the high-level API, ReportData is constructed automatically by AttestBuilder::finalize. Use it directly when you need the low-level API:

use livy_tee::report::{ReportData, REPORT_DATA_VERSION, build_id_from_hash_hex};
use livy_tee::generate::binary_hash;
use sha2::{Digest, Sha256};

// Compute payload_hash from your inputs manually.
let input_hash: [u8; 32] = Sha256::digest(b"input bytes").into();
let output_hash: [u8; 32] = Sha256::digest(b"output bytes").into();
let mut combined = Sha256::new();
combined.update(input_hash);
combined.update(output_hash);
let payload_hash: [u8; 32] = combined.finalize().into();

// Derive build_id from the running binary.
let binary_hash_hex = binary_hash()?;
let build_id = build_id_from_hash_hex(&binary_hash_hex);

// Construct ReportData.
let rd = ReportData::new(
    payload_hash,
    build_id,
    REPORT_DATA_VERSION, // currently 1
    0,                   // build_number (0 in development)
    nonce_counter,       // u64 monotonic counter
);

// Serialize to 64-byte wire format.
let bytes: [u8; 64] = rd.to_bytes();

Serialization and parsing

use livy_tee::report::ReportData;

// Serialize to 64-byte canonical wire format.
let bytes: [u8; 64] = rd.to_bytes();

// Deserialize from 64-byte canonical wire format.
let rd2 = ReportData::from_bytes(&bytes);

// Hex-encode the full 64 bytes (128 hex characters).
let hex_str = rd.to_hex();

Verification

use livy_tee::report::{ReportData, build_id_from_hash_hex};

// Parse from the 64-byte runtime_data field (received alongside a quote).
let rd = ReportData::from_bytes(&runtime_data_bytes);

// 1. Verify payload_hash matches the expected value.
let expected: [u8; 32] = sha256(sha256(input) || sha256(output));
assert!(rd.verify_payload(&expected));

// 2. Verify build_id matches the published binary.
let expected_build_id = build_id_from_hash_hex(&published_tee_binary_hash);
assert_eq!(rd.build_id, expected_build_id);

// 3. Verify nonce matches the expected counter value for this request.
assert_eq!(rd.nonce, expected_nonce);

// 4. Verify schema version is supported.
assert_eq!(rd.version_code, livy_tee::report::REPORT_DATA_VERSION);

Build ID helpers

build_id is the first 8 bytes of SHA-256(binary). Two helpers derive it:

use livy_tee::report::{build_id_from_binary, build_id_from_hash_hex};

// From raw binary bytes (e.g. from fs::read)
let id: [u8; 8] = build_id_from_binary(&binary_bytes);

// From an already-computed SHA-256 hex string
let id: [u8; 8] = build_id_from_hash_hex(&binary_hash_hex);

// Both produce identical results for the same binary.

The full binary hash is available from livy_tee::generate::binary_hash() which hashes the currently running executable.

Fields reference

FieldBytesTypeDescription
payload_hash[0..32][u8; 32]SHA-256 of the public_values buffer
build_id[32..40][u8; 8]First 8 bytes of SHA-256(binary)
version_code[40..44]u32 BESchema version — currently 1
build_number[44..48]u32 BECI build counter — 0 in dev
nonce[48..56]u64 BEMonotonic counter for replay protection
(reserved)[56..64]Zero-filled, reserved for future fields