Skip to main content

RFC 9102: Verified Event Sequence Trust (VEST) Protocol

1. INTRODUCTION

1.1 Purpose

The Verified Event Sequence Trust (VEST) Protocol is a cryptographic audit layer that provides:
  • Non-repudiation: Digital signatures ensure authors cannot deny their operations
  • Tamper-evidence: Merkle proofs detect any modification to the audit trail
  • Verifiability: Anyone can independently verify document history integrity
  • Compliance: Cryptographic evidence admissible in legal proceedings
VEST wraps TNP operations with signatures and Merkle proofs, creating an immutable, publicly verifiable audit trail.

1.2 Scope

This RFC specifies:
  1. Cryptographic Primitives: Ed25519, Blake3, Roughtime
  2. Signature Data Types: SignatureProof, AuditSignature, SignatureChain
  3. Merkle Proof System: RFC 6962 consistency proofs
  4. Audit Trail Semantics: Immutable append-only log
  5. Verification Algorithms: Signature validation, proof verification, chain integrity
  6. Non-repudiation Guarantees: Binding operation author to action
  7. Compliance Mappings: SOC 2, HIPAA, GDPR audit requirements

1.3 Terminology

TermDefinition
SignatureEd25519 digital signature proving operation author
AuditEntry(OperationID, OperationData, Signature, Timestamp)
MerkleProofRFC 6962 proof proving inclusion in timeline tree
SignatureChainLinked proof: Op1→Op2→Op3, where each signs previous hash
Non-repudiationAuthor cannot deny creating operation (signature binding)
Tamper-evidenceMerkle tree root changes if any operation modified

2. PROTOCOL MODEL

2.1 Signature Architecture

TNP Operation (from RFC 9100)

[VEST Wraps & Signs]

SignedOperation {
  operation:      Operation,      // From TNP
  signature:      Ed25519Sig,     // Signs operation bytes
  public_key:     [u8; 32],       // Author's public key
  timestamp:      RoughtimeStamp, // Non-backdatable timestamp
  signature_hash: [u8; 32],       // Blake3(signature)
}

AuditEntry (immutable log)

Merkle Tree

Root Hash (cryptographic commitment to entire timeline)

2.2 Signature Chain Linking

Each operation in a timeline signs the hash of the previous operation:
Op[0]: sign(operation_bytes)
       signature_hash[0] = Blake3(signature[0])

Op[1]: sign(operation_bytes || signature_hash[0])
       signature_hash[1] = Blake3(signature[1])

Op[2]: sign(operation_bytes || signature_hash[1])
       signature_hash[2] = Blake3(signature[2])

Chain: Op[0] → (hash) → Op[1] → (hash) → Op[2]
Property: Breaking any signature in the chain breaks all subsequent signatures.

2.3 Merkle Tree Structure (RFC 6962)

Operations form a balanced Merkle tree:
           Root Hash

       ┌──────┴──────┐
    Hash1            Hash2
       │              │
     ┌─┴─┐          ┌─┴─┐
   Op0  Op1        Op2  Op3

Leaf[i] = Blake3(serialize_cbor(audit_entry[i]))
Parent = Blake3(left_child || right_child)
Properties:
  • Deterministic: Same operations always produce same root
  • Tamper-detecting: Changing any operation changes root
  • Efficient proofs: O(log N) proof size

3. DATA TYPES

3.1 SignatureProof

SignatureProof {
  signer_id:      String,         // Author peer identifier
  signature:      [u8; 64],       // Ed25519 signature bytes
  public_key:     [u8; 32],       // Ed25519 public key (verifies signature)
  timestamp_ms:   u64,            // Unix milliseconds (Roughtime)
}
Invariants:
  • signature is 64 bytes (Ed25519 fixed size)
  • public_key is 32 bytes (Ed25519 fixed size)
  • timestamp must be within ±1 minute of current time (replay protection)

3.2 AuditSignature

AuditSignature {
  proof:          SignatureProof,   // Signature + metadata
  previous_hash:  [u8; 32],         // Hash of previous signature
  chain_depth:    u32,              // Position in chain (starts at 1)
}
Chain Semantics:
  • depth=1: Genesis operation (no previous)
  • depth>1: Linked operation (previous_hash must match parent’s hash)
  • Verify chain: For each Op[i], verify signature_chain[i].previous_hash == Blake3(signature_chain[i-1])

3.3 TimelineProof

TimelineProof {
  timeline_id:    TimelineID,       // Which timeline (fork)
  root_hash:      [u8; 32],         // Merkle tree root
  tree_size:      u64,              // Number of operations
  leaf_hashes:    Vec<[u8; 32]>,    // All leaf hashes (for proof generation)
}

3.4 MerkleProof (RFC 6962)

MerkleProof {
  index:          u64,              // Operation index (0-based)
  path:           Vec<[u8; 32]>,    // Hash path from leaf to root
  tree_size:      u64,              // Size of tree when proof generated
}
Verification Algorithm:
fn verify_merkle_proof(
  leaf: AuditEntry,
  proof: MerkleProof,
  expected_root: [u8; 32]
) -> bool {
  // Compute leaf hash
  current = Blake3(serialize_cbor(leaf))

  // Walk up tree using proof path
  index = proof.index
  for hash in proof.path:
    if index % 2 == 0:
      current = Blake3(current || hash)  // current is left sibling
    else:
      current = Blake3(hash || current)  // current is right sibling
    index = index / 2

  // Root should match expected
  return current == expected_root
}

4. SIGNATURE ALGORITHM

4.1 Create Signature

Input: operation (Operation), private_key ([u8; 32])
Output: AuditSignature
Algorithm CreateSignature:
  1. Serialize operation: op_bytes = serialize_cbor(operation)
  2. Sign with Ed25519: signature = Ed25519::sign(private_key, op_bytes)
  3. Derive public key: public_key = Ed25519::public_from_private(private_key)
  4. Get timestamp: timestamp = Roughtime::now()

  5. Create SignatureProof:
     proof = SignatureProof {
       signer_id: peer_id,
       signature: signature,
       public_key: public_key,
       timestamp_ms: timestamp
     }

  6. Fetch previous signature hash:
     prev_hash = get_previous_signature_hash() OR [0u8; 32] if genesis
     chain_depth = get_chain_depth() + 1

  7. Create AuditSignature:
     return AuditSignature {
       proof: proof,
       previous_hash: prev_hash,
       chain_depth: chain_depth
     }
Determinism Property:
  • Same operation + private_key + previous_hash → same signature
  • Ed25519 is deterministic (RFC 8032)

4.2 Verify Signature

Input: operation (Operation), audit_sig (AuditSignature)
Output: bool (valid or invalid)
Algorithm VerifySignature:
  1. Extract signature: sig = audit_sig.proof.signature
  2. Extract public key: pub = audit_sig.proof.public_key
  3. Serialize operation: op_bytes = serialize_cbor(operation)

  4. Verify signature:
     valid = Ed25519::verify(pub, op_bytes, sig)

  5. Check timestamp freshness:
     now = Roughtime::now()
     age_ms = abs(now.as_ms() - audit_sig.proof.timestamp_ms)
     if age_ms > 60000:  // 1 minute
       return false

  6. Verify chain linking:
     prev_hash = Blake3(audit_sig.proof.signature)
     if audit_sig.chain_depth > 1:
       if prev_hash != audit_sig.previous_hash:
         return false

  7. return valid
Security Properties:
  • Existential unforgeability: Cannot forge Ed25519 signature
  • Non-repudiation: Signer cannot deny creating operation
  • Authenticity: Signature links operation to specific peer

5. AUDIT TRAIL & IMMUTABLE LOG

5.1 AuditEntry

AuditEntry {
  operation_id:   OpID,             // Content-addressed operation ID
  operation_data: Vec<u8>,          // Full operation bytes
  signature:      AuditSignature,   // Cryptographic signature
  entry_index:    u64,              // Position in log (0-based)
}
Invariants:
  • entry_index values are sequential (0, 1, 2, …)
  • Once written, cannot be modified (immutable)
  • Signature must be valid (verified at append time)

5.2 AuditLog (Abstract Storage)

trait AuditLog: Send + Sync {
  fn append(&mut self, entry: AuditEntry) -> Result<u64, AuditError>;
  fn get(&self, op_id: &OpID) -> Option<AuditEntry>;
  fn get_at_index(&self, index: u64) -> Option<AuditEntry>;
  fn range(&self, start: u64, end: u64) -> Vec<AuditEntry>;
  fn size(&self) -> u64;
  fn root_hash(&self) -> [u8; 32];
}
Append Operation:
fn append(entry: AuditEntry) -> Result<u64, Error>:
  1. Verify entry.signature is valid
  2. Verify operation_id = Blake3(operation_data)
  3. If size > 0:
       Verify signature chain: entry.signature.previous_hash == Blake3(prev_signature)
  4. Store entry
  5. Recompute Merkle tree root
  6. return entry_index

6. MERKLE TREE PROOFS (RFC 6962)

6.1 Building Merkle Tree

Operations: Op[0], Op[1], Op[2], Op[3]

Step 1: Compute leaves
  leaf[0] = Blake3(Op[0])
  leaf[1] = Blake3(Op[1])
  leaf[2] = Blake3(Op[2])
  leaf[3] = Blake3(Op[3])

Step 2: Build tree (bottom-up)
  node[0,0] = leaf[0]
  node[0,1] = leaf[1]
  node[1,0] = Blake3(node[0,0] || node[0,1]) = Blake3(leaf[0] || leaf[1])
  node[1,1] = Blake3(node[0,2] || node[0,3]) = Blake3(leaf[2] || leaf[3])
  root = Blake3(node[1,0] || node[1,1])

Result:
             root
             /  \
         n[1,0] n[1,1]
          /  \    /  \
      leaf0 leaf1 leaf2 leaf3

6.2 Consistency Proof (RFC 6962)

Proof that tree T2 extends tree T1 without modification:
Given:
  tree1_root, tree1_size=2
  tree2_root, tree2_size=4

Proof path that shows tree1's leaf nodes appear in tree2 with same values:
  path = [hash_at_2, hash_at_3]

Verification:
  1. Recompute tree1_root from leaves 0,1 + proof[0]
  2. Recompute tree2_root from tree1_result + proof[1]
  3. Assert both match expected roots

7. VERIFICATION OPERATIONS

7.1 Verify Single Operation

fn verify_operation(op: Operation, sig: AuditSignature) -> bool:
  1. Verify Ed25519 signature is valid
  2. Verify signature chain (if not genesis)
  3. Verify timestamp freshness
  4. return all_pass

7.2 Verify Complete Timeline

fn verify_timeline(timeline: Timeline) -> bool:
  1. For each operation in timeline:
       if not verify_operation(op):
         return false
  2. Verify Merkle tree root
  3. Verify signatures form continuous chain
  4. return true

7.3 Verify Merkle Inclusion Proof

fn verify_inclusion_proof(entry: AuditEntry, proof: MerkleProof) -> bool:
  1. Compute leaf = Blake3(entry)
  2. Reconstruct root from leaf + proof.path
  3. Compare to expected root_hash
  4. return root_matches

8. SECURITY PROPERTIES

8.1 Non-repudiation Guarantee

Claim: Author cannot deny creating operation. Proof:
  • Ed25519 is unforgeable: Only possessor of private_key can create signature
  • Signature binds operation bytes + timestamp to peer_id
  • Public key verifiably derives from private key
  • Therefore: Signature proves author created operation
Threat Model:
  • Out of scope: Key compromise, system breach
  • In scope: Author claims they didn’t create operation (refuted by signature)

8.2 Tamper-evidence Guarantee

Claim: Any modification to audit trail breaks Merkle tree root. Proof:
  • Merkle tree: Each leaf hash depends on operation bytes
  • Modifying any operation → changes leaf hash → changes all ancestors → changes root
  • Root is publicly known (distributed to peers)
  • Any tampering detected by mismatch to expected root
Example:
Original: Op[1] = "add user"
          leaf[1] = Blake3(Op[1])
          root = Blake3(...leaf[1]...)

Tampered: Op[1] = "add admin"  (changed!)
          leaf[1]' = Blake3(Op[1]')  ≠ leaf[1]
          root' ≠ root  (detected!)

8.3 Signature Chain Integrity

Claim: Breaking signature in chain breaks all descendants. Proof:
  • Each signature signs: operation_bytes || previous_signature_hash
  • Modifying Op[i] or Sig[i] → changes Blake3(Sig[i]) → breaks Sig[i+1]
  • Detecting the break requires validating the chain

9. CONFORMANCE REQUIREMENTS

Implementations MUST:
  1. ✅ Use Ed25519 for all signatures (RFC 8032)
  2. ✅ Verify signatures before appending to audit log
  3. ✅ Maintain signature chain linking (each signs previous)
  4. ✅ Support Merkle proofs (RFC 6962 format)
  5. ✅ Enforce append-only log (no modification/deletion)
  6. ✅ Provide root hash (publicly available)
  7. ✅ Reject operations with invalid signatures
  8. ✅ Maintain timestamp freshness (±1 minute)
Implementations MAY:
  • Use different Merkle tree building strategies (balanced, skewed)
  • Provide additional metadata (who verified, when)
  • Implement signature caching for performance
  • Support batch signature verification

10. EXAMPLES

10.1 Signing an Operation

Operation:
  {
    id: 0x1234...
    timeline: canonical
    author: peer_1
    op_type: Insert
    payload: b"Hello"
    parent: null
    lamport: 1
    timestamp: 1701864000000000
  }

Private Key: [seed bytes]
Previous Signature Hash: null (genesis)

Step 1: Serialize CBOR
  op_bytes = [0x88, 0x58, 0x20, ...]  (100 bytes)

Step 2: Sign with Ed25519
  signature = Ed25519::sign(private_key, op_bytes)
  = [0x01, 0x02, ..., 0x40]  (64 bytes)

Step 3: Create SignatureProof
  proof = {
    signer_id: "peer_1",
    signature: [64 bytes],
    public_key: [32 bytes],
    timestamp_ms: 1701864000000
  }

Step 4: Create AuditSignature
  audit_sig = {
    proof: proof,
    previous_hash: [0u8; 32],  (null for genesis)
    chain_depth: 1
  }

Result: Signed operation is (operation, audit_sig)

10.2 Verifying Signature Chain

Timeline: [Op1 signed by Alice, Op2 signed by Bob, Op3 signed by Alice]

Verification:
  1. Verify Op1's Ed25519 signature with Alice's public key ✓
  2. Compute Blake3(Op1.signature) = hash1

  3. Verify Op2's Ed25519 signature with Bob's public key ✓
  4. Check Op2.previous_hash == hash1 ✓
  5. Compute Blake3(Op2.signature) = hash2

  6. Verify Op3's Ed25519 signature with Alice's public key ✓
  7. Check Op3.previous_hash == hash2 ✓

Result: Chain is intact and verifiable

11. INTEROPERABILITY

11.1 Compliance Mappings

StandardVEST Implementation
HIPAAAudit trail with signatures satisfies access logging
GDPRNon-repudiation + timestamps enable “accountability”
SOC 2Merkle proofs provide immutability + integrity
PCI-DSSDigital signatures satisfy cardholder data protection

11.2 Multi-language Binding

All implementations must support:
  • ✅ Ed25519 signature generation/verification (RFC 8032)
  • ✅ Blake3 hashing (deterministic)
  • ✅ Roughtime timestamp verification (NTP consensus)
  • ✅ Merkle proof validation (RFC 6962)
  • ✅ Deterministic CBOR serialization
Supported languages: Rust, Go, Python, JavaScript, TypeScript, C

12. REFERENCES

  • [RFC 8032] Edwards-Curve Digital Signature Algorithm (EdDSA)
  • [RFC 6962] Certificate Transparency
  • [RFC 7049] CBOR (Concise Binary Object Representation)
  • [Blake3] BLAKE3 cryptographic hash function

END OF RFC 9102