Skip to main content
Erst can generate signed audit logs that provide cryptographic proof of simulation results. This is essential for compliance, forensics, and maintaining tamper-evident records of transaction analysis.

Overview

Audit log signing creates a cryptographically signed record containing:
  • Transaction hash
  • Simulation results (events, logs, traces)
  • Timestamp
  • Digital signature
  • Public key for verification
  • Optional hardware attestation
Audit logs provide:
  • Tamper evidence: Any modification invalidates the signature
  • Non-repudiation: Proof that a specific key signed the data
  • Compliance: Auditable trail for regulatory requirements
  • Hardware attestation: Proof that signing keys are HSM-protected

Signing methods

Erst supports two signing methods:

Software signing

Uses Ed25519 private keys stored in PEM format
  • Fast and convenient
  • Suitable for development
  • Keys can be backed up

HSM signing

Uses hardware security modules via PKCS#11
  • Production-grade security
  • Keys never leave hardware
  • Compliance-ready

Software signing

Sign audit logs using an Ed25519 private key.

Generate a signing key

Create an Ed25519 key pair:
# Generate private key
openssl genpkey -algorithm ed25519 -out ed25519-private-key.pem

# Extract public key
openssl pkey -in ed25519-private-key.pem -pubout -out ed25519-public-key.pem
Protect your private key file with restricted permissions:
chmod 600 ed25519-private-key.pem

Sign with environment variable

Provide the private key via environment variable:
export ERST_AUDIT_PRIVATE_KEY_PEM="$(cat ./ed25519-private-key.pem)"

node dist/index.js audit:sign \
  --payload '{"input":{},"state":{},"events":[],"timestamp":"2026-01-01T00:00:00.000Z"}'

Sign with CLI flag

Provide the private key directly:
node dist/index.js audit:sign \
  --payload '{"input":{},"state":{},"events":[],"timestamp":"2026-01-01T00:00:00.000Z"}' \
  --software-private-key "$(cat ./ed25519-private-key.pem)"
The audit:sign command is part of the TypeScript bindings. For Go CLI integration, see the implementation in internal/cmd/audit.go.

HSM signing (PKCS#11)

Sign audit logs using a hardware security module.

Prerequisites

Install PKCS#11 provider:
# Install YubiKey manager
sudo apt-get install yubikey-manager
# or on macOS:
brew install ykman

# Verify PKCS#11 module
ls /usr/lib/x86_64-linux-gnu/libykcs11.so

Configure environment

Set required environment variables:
# PKCS#11 module path
export ERST_PKCS11_MODULE=/usr/lib/x86_64-linux-gnu/libykcs11.so

# PIN for accessing the HSM
export ERST_PKCS11_PIN=123456

# Key identifier (choose ONE method):
# Option 1: Key label
export ERST_PKCS11_KEY_LABEL=erst-audit-ed25519

# Option 2: Key ID (hex)
export ERST_PKCS11_KEY_ID=01

# Option 3: YubiKey PIV slot
export ERST_PKCS11_PIV_SLOT=9a  # 9a, 9c, 9d, 9e, 82-95, f9

# Public key for verification (SPKI PEM format)
export ERST_PKCS11_PUBLIC_KEY_PEM="$(cat ./ed25519-public-key-spki.pem)"
# Token label (alternative to slot index)
export ERST_PKCS11_TOKEN_LABEL="YubiKey PIV"

# Slot index (numeric)
export ERST_PKCS11_SLOT=0

Sign with HSM

Generate a signed audit log:
node dist/index.js audit:sign \
  --hsm-provider pkcs11 \
  --payload '{"input":{},"state":{},"events":[],"timestamp":"2026-01-01T00:00:00.000Z"}'
The command outputs signed JSON to stdout:
{
  "version": "1.1.0",
  "timestamp": "2026-03-03T10:30:45.123Z",
  "transaction_hash": "abc123...",
  "trace_hash": "def456...",
  "signature": "789abc...",
  "public_key": "012def...",
  "payload": {
    "envelope_xdr": "...",
    "result_meta_xdr": "...",
    "events": [...],
    "logs": [...]
  },
  "hardware_attestation": {
    "certificates": [...],
    "token_info": "YubiKey PIV",
    "key_non_exportable": true,
    "retrieved_at": "2026-03-03T10:30:45.123Z"
  }
}

Hardware attestation

HSM signing can include hardware attestation proving the signing key is hardware-protected.

Attestation data

When signing with an HSM, the audit log includes:
  • Certificates: X.509 certificate chain (leaf to root)
  • Token info: HSM device information
  • Key non-exportable: Confirmation that the key cannot be extracted
  • Retrieved timestamp: When attestation was captured

Verification

The attestation is included in the signature hash, so:
  • Removing attestation invalidates the signature
  • Modifying attestation invalidates the signature
  • Attestation proves the key is HSM-protected
Hardware attestation is optional but recommended for high-security environments where you need cryptographic proof that keys are hardware-protected.

Audit log structure

Version 1.1.0 schema

interface AuditLog {
  version: string;                          // Schema version ("1.1.0")
  timestamp: string;                        // ISO 8601 timestamp
  transaction_hash: string;                 // Transaction hash (hex)
  trace_hash: string;                       // SHA256 hash of payload (hex)
  signature: string;                        // Ed25519 signature (hex)
  public_key: string;                       // Ed25519 public key (hex)
  payload: Payload;                         // Simulation results
  hardware_attestation?: HardwareAttestation; // Optional HSM attestation
}

interface Payload {
  envelope_xdr: string;     // Base64 transaction envelope
  result_meta_xdr: string;  // Base64 result metadata
  events: string[];         // Diagnostic events
  logs: string[];           // Execution logs
}

interface HardwareAttestation {
  certificates: Certificate[];  // X.509 chain (leaf to root)
  token_info: string;           // HSM device info
  key_non_exportable: boolean;  // Key protection status
  retrieved_at: string;         // ISO 8601 timestamp
}

Hash computation

The trace_hash is computed as:
trace_hash = SHA256(JSON.stringify({
  payload: <payload>,
  hardware_attestation: <attestation>  // If present
}))
The signature is:
signature = Ed25519.sign(private_key, trace_hash)

Verification

Verify an audit log’s integrity and signature:

Programmatic verification

import { VerifyAuditLog } from './audit';

const auditLog: AuditLog = JSON.parse(auditLogJson);
const isValid = await VerifyAuditLog(auditLog);

if (isValid) {
  console.log('✓ Audit log signature is valid');
} else {
  console.log('✗ Audit log signature is INVALID');
}

Manual verification

1
Extract public key
2
echo "<public_key_hex>" | xxd -r -p > public_key.bin
3
Reconstruct trace hash
4
# Extract payload + attestation
jq '{payload: .payload, hardware_attestation: .hardware_attestation}' audit-log.json \
  | openssl dgst -sha256 -hex
5
Verify signature
6
openssl pkeyutl \
  -verify \
  -pubin -inkey public_key.pem \
  -sigfile signature.bin \
  -in trace_hash.bin

Verification results

  • Valid: Hash matches and signature verifies
  • Invalid: Payload was modified, signature is wrong, or key is incorrect
  • Attestation removed: Hash won’t match if attestation was stripped

YubiKey PIV integration

Use YubiKey devices for HSM signing.

PIV slots

YubiKey PIV supports multiple key slots:
SlotPurposeUse Case
9aPIV AuthenticationGeneral signing
9cDigital SignatureDocument signing
9dKey ManagementEncryption
9eCard AuthenticationLow-security auth
82-95Retired SlotsKey rotation
f9AttestationKey attestation
Recommended for audit logs: Slot 9c (Digital Signature)

Generate key on YubiKey

# Generate Ed25519 key in slot 9c
yubico-piv-tool -a generate -s 9c -o public_key.pem -A ECCP256

# Set slot to require PIN
yubico-piv-tool -a set-mgm-key --new-key <new-management-key>

Configure for Erst

export ERST_PKCS11_MODULE=/usr/lib/x86_64-linux-gnu/libykcs11.so
export ERST_PKCS11_PIN=123456
export ERST_PKCS11_PIV_SLOT=9c
export ERST_PKCS11_PUBLIC_KEY_PEM="$(cat ./public_key.pem)"

node dist/index.js audit:sign --hsm-provider pkcs11 --payload '...'
See docs/pkcs11-yubikey.md for detailed YubiKey setup.

Common workflows

Generate audit log after debugging

1
Debug transaction
2
erst debug abc123...def --network mainnet
3
Extract simulation results
4
Simulation results are stored in the active session.
5
Generate signed audit log
6
# Using software key
export ERST_AUDIT_PRIVATE_KEY_PEM="$(cat ./key.pem)"
node dist/index.js audit:sign \
  --payload "$(erst session export-payload)" \
  > audit-log.json
7
Store audit log
8
# Save to compliance archive
cp audit-log.json /archives/2026-03-03-abc123.json

Batch audit log generation

Generate audit logs for multiple transactions:
#!/bin/bash
# batch-audit.sh

export ERST_AUDIT_PRIVATE_KEY_PEM="$(cat ./key.pem)"

for tx in "abc123..." "def456..." "789abc..."; do
  echo "Processing $tx..."
  
  # Debug transaction
  erst debug "$tx" --network mainnet
  
  # Generate audit log
  node dist/index.js audit:sign \
    --payload "$(erst session export-payload)" \
    > "audit-log-$tx.json"
    
  echo "✓ Audit log saved: audit-log-$tx.json"
done

Verify archived audit logs

Check integrity of stored logs:
#!/bin/bash
# verify-archives.sh

for log in /archives/*.json; do
  echo "Verifying $log..."
  
  if node dist/index.js audit:verify --log "$log"; then
    echo "✓ Valid"
  else
    echo "✗ INVALID"
    exit 1
  fi
done

echo "All audit logs verified successfully"

Security best practices

For software keys:
  • Store with restricted permissions: chmod 600 key.pem
  • Never commit to version control
  • Use environment variables, not hardcoded paths
  • Rotate keys periodically
  • Back up securely (encrypted storage)
For HSM keys:
  • Never export private keys
  • Use strong PINs (not default PINs)
  • Enable PIN retry limits
  • Physical security for HSM devices
  • Audit HSM access logs
Software keys are convenient for development, but production audit logs should use HSM:
  • Keys cannot be extracted
  • Tamper-evident hardware
  • Compliance-ready (FIPS 140-2)
  • Hardware attestation available
  • Centralized key management
When using HSM, always include attestation:
  • Proves keys are hardware-protected
  • Cannot be forged
  • Required for high-assurance compliance
  • Covered by signature (tamper-evident)
  • Store in append-only storage
  • Use write-once media for immutability
  • Encrypt at rest
  • Replicate to multiple locations
  • Test restoration procedures
  • Document retention policies

Troubleshooting

Invalid signature

Error: audit log signature verification failed
Solutions:
  • Verify you’re using the correct public key
  • Check audit log wasn’t modified
  • Ensure payload format matches schema version
  • Re-generate audit log if corrupted

HSM not detected

Error: failed to initialize PKCS#11 module
Solutions:
  • Check PKCS#11 module path: ls $ERST_PKCS11_MODULE
  • Install HSM drivers/software
  • Verify HSM is connected: yubico-piv-tool -a status
  • Check permissions: sudo usermod -a -G plugdev $USER

PIN incorrect

Error: CKR_PIN_INCORRECT
Solutions:
  • Verify PIN is correct
  • Check PIN hasn’t been locked (retry limit)
  • Reset PIN if needed (requires PUK)
  • Use correct PIN for the token

Key not found in HSM

Error: private key not found in HSM
Solutions:
  • List available keys: yubico-piv-tool -a list
  • Verify key label or ID is correct
  • Check you’re using the right slot
  • Generate key if it doesn’t exist

Implementation details

Source files

Audit log signing is implemented in:
  • internal/cmd/audit.go - Go implementation
  • internal/signer/ - Signer interface
  • internal/signer/memory.go - Software signer
  • internal/signer/pkcs11.go - HSM signer

Key functions

Audit log generation:
  • Generate() - Legacy function (software key)
  • GenerateWithSigner() - Generic signer interface
  • VerifyAuditLog() - Verify signature and hash
Location: internal/cmd/audit.go:73, internal/cmd/audit.go:84, internal/cmd/audit.go:143

Signer interface

type Signer interface {
    Sign(data []byte) ([]byte, error)
    PublicKey() ([]byte, error)
}
Implementations:
  • InMemorySigner - Ed25519 software signing
  • Pkcs11Signer - PKCS#11 HSM signing

Next steps