pqrascv-core
Post-Quantum Remote Attestation & Supply-Chain Verification (PQ-RASCV)
Hardware-rooted · supply-chain-verified · post-quantum signed — everywhere Rust runs.
pqrascv-core is a no_std + alloc Rust library for issuing and verifying tamper-evident device
attestation quotes. Every quote is signed with ML-DSA-65 (FIPS 204) and carries a
SLSA v1 / in-toto provenance predicate binding a device's firmware identity to its build
pipeline in a single compact CBOR message — on bare-metal Cortex-M4, RISC-V, WASM, or Linux.
Why PQ-RASCV?
Two converging threats are making classical attestation obsolete:
Supply-chain attacks are accelerating. SolarWinds, XZ Utils, and dozens of lesser-known incidents show that firmware can be compromised at build time. Existing attestation stacks (TPM 2.0, DICE, TDX) prove what is running — but carry no cryptographic proof of how it was built or who signed it off.
Post-quantum migration is overdue. RSA and ECDSA underpin today's attestation chains and are broken by Shor's algorithm. NIST finalised ML-DSA (FIPS 204) and ML-KEM (FIPS 203) in 2024. Devices deployed today may still be in service when cryptographically-relevant quantum computers arrive.
PQ-RASCV addresses both in a single embedded-first library. Every attestation quote is post-quantum signed and supply-chain provenance-linked.
Features
- Post-quantum by default — ML-DSA-65 signatures; no RSA or ECDSA anywhere
- Supply-chain provenance — SLSA v1 predicates + SBOM hash inside every signed quote
- Device PKI — CBOR-native certificate chains (Root CA → Intermediate → Device), CRL revocation, and trust anchor lifecycle management
- Three measurement backends — Software SHA3-256, hardware TPM 2.0, DICE CDI derivation
no_std + alloc— one API across Cortex-M4, RISC-V, WASM, and Linux- Allocation-free measurement path —
RoT::measure()never touches the heap - Replay protection — verifier-supplied 32-byte nonce bound inside the signature
- Constant-time PQ ops — RustCrypto crates; key material is
Zeroize-on-drop - Compact wire format — CBOR (RFC 8949), ~3.7 KB total quote including signature
- Bitcoin anchoring SDK —
pqrascv-bitcoin-anchorbuilds OP_RETURN payloads and verifies RFC 6962 Merkle + SPV inclusion proofs; requires operator-provided Bitcoin node for broadcast and confirmation
Quick Start
# std (default)
= "1.0.0-rc.5"
# bare-metal — bring your own allocator
= { = "1.0.0-rc.5", = false, = ["alloc"] }
CLI
The fastest way to test attestation is the pqrascv CLI:
# 1. Generate a post-quantum keypair
# 2. Generate an attestation quote (prover side)
# --software-rot-acknowledged is required when using the software backend
# (no real TPM/DICE/TDX hardware). The nonce is generated automatically
# and printed — copy the hex string for use in step 3.
# 3. Verify the quote (verifier side)
# --nonce must be the 64-hex-char value printed by the attest step above.
Prover — device side
use ;
let = generate_ml_dsa_keypair.unwrap;
// SoftwareRoT is for testing only. Use TpmRoT or DiceRoT in production.
let rot = new;
let provenance = new
.add_subject
.with_slsa_level
.build
.unwrap;
let nonce = ; // received from the verifier's Challenge
let quote = generate_quote
.unwrap;
let cbor_bytes = quote.to_cbor.unwrap; // send to verifier
Verifier — server side
Simple key-based verification:
use Verifier;
use PolicyConfig;
let verifier = new;
match verifier.verify_cbor
PKI-based verification with a certificate chain:
use Verifier;
use ;
let root_ca = CaPublicKey ;
let anchor = new;
let result = verifier.verify_cbor_with_pki?;
println!;
println!;
CA rollover with TrustStore:
use TrustStore;
let store = new
.with_rollover; // old and new CAs coexist during migration
let result = verifier.verify_cbor_with_trust_store?;
How It Works
PQ-RASCV is a challenge–response protocol. The verifier drives; the prover measures, attests, and signs:
Verifier Prover (device)
│ │
│──── Challenge { nonce: [u8; 32] } ──────► │
│ ├── measure() → PCRs, fw_hash
│ ├── provenance → SLSA v1 predicate
│ └── sign body → ML-DSA-65 sig
│ │
│ ◄──── AttestationQuote (CBOR) ─────────── │
│ │
├── verify ML-DSA-65 signature
├── check nonce match + pub_key_id fingerprint
├── optionally: validate cert chain → root CA + CRL
└── evaluate PolicyConfig → accept / reject
Signed payload fields (QuoteBody)
| Field | Content |
|---|---|
version |
Protocol version |
timestamp |
Unix epoch seconds (or NoRtc for embedded targets without a clock) |
nonce |
32-byte replay-protection token |
measurements.pcrs |
8 × 32-byte PCR-style hash bank |
measurements.firmware_hash |
SHA3-256 of firmware image |
measurements.ai_model_hash |
SHA3-256 of AI model weights (optional) |
provenance |
SLSA v1 predicate — builder ID, subjects, SBOM hash |
pub_key_id |
SHA3-256 fingerprint of signer's ML-DSA-65 verifying key |
signature |
3 309-byte ML-DSA-65 signature over CBOR-encoded body |
Architecture
╔══════════════════════════════════════════════════════╗
║ generate_quote() ← public entry point ║
╚══════╤═══════════════╤══════════════╤════════════════╝
│ │ │
┌────▼─────┐ ┌─────▼──────┐ ┌───▼──────────────────┐
│ RoT │ │ Crypto │ │ Provenance │
│ trait │ │ Backend │ │ SlsaPredicateBuilder │
│ measure()│ │ ML-DSA-65 │ │ (v1: self-asserted) │
└────┬─────┘ └─────┬──────┘ └───┬──────────────────┘
│ │ │
┌────▼───────────────▼──────────────▼───────────────┐
│ AttestationQuote (CBOR · ML-DSA signed) │
└───────────────────────────────────────────────────┘
│ │
┌────────▼────────┐ ┌───────────▼─────────────┐
│ PKI / CertChain│ │ Bitcoin Anchor │
│ TrustStore │ │ OP_RETURN + Merkle tree │
└─────────────────┘ └─────────────────────────┘
| Layer | Module | Heap? |
|---|---|---|
Measurement (RoT trait) |
measurement, backends/ |
No |
Cryptography (CryptoBackend trait) |
crypto |
No |
| Provenance builder | provenance |
Yes — alloc |
| Quote assembly | quote |
Yes — alloc |
| PKI / certificate chains | pki |
Yes — alloc |
| Policy evaluation | config, policy |
No |
Device PKI
PQ-RASCV v2 replaces arbitrary key trust with a full CBOR-native PKI.
Certificate hierarchy
Offline Root CA (air-gapped)
└── Manufacturer Intermediate CA (HSM-protected)
└── DeviceCertificate
├── subject_key: ML-DSA-65 verifying key (1 952 bytes)
├── hardware_id: TPM EK cert hash / DICE UDS fingerprint
└── fw_policy: allowed firmware hash set (optional)
Certificates are CBOR-native (DeviceCertificate), not X.509, to avoid ASN.1 parsing on
embedded targets.
Trust anchor lifecycle
CaPublicKey includes not_before and not_after timestamps. validate_chain enforces the
trust anchor's temporal validity window before any signature work:
If the anchor is outside its validity window, validate_chain returns
PqRascvError::TrustAnchorExpired immediately, without attempting any signature verification.
CA rollover with TrustStore
TrustStore holds multiple trust anchors for staged CA migration:
let store = new
.with_rollover;
// Returns the first valid anchor that successfully validates the chain.
// Returns TrustAnchorExpired if no anchors are in their validity window.
let chain = validate_chain_with_store?;
PkiVerificationResult exposes audit fields for the anchor that accepted the chain:
result.trust_anchor_id // ca_id of the accepting anchor
result.trust_anchor_fingerprint // SHA3-256 of its verifying key
result.trust_anchor_valid_until // not_after of the accepting anchor
Revocation
CRLs are signed CBOR lists (RevocationList). VerifiedRevocationList wraps a CRL whose
signature has been checked — is_revoked() is only accessible after signature verification,
preventing callers from bypassing the check.
End-to-end PKI walkthrough
The following example shows the complete flow from CA setup through quote verification. In production the CA operations would run on an air-gapped HSM and ship only the signed certificates; the prover and verifier sides never share private key material.
use ;
use Verifier;
use ;
// ── 1. CA setup (offline / HSM in production) ───────────────────────────────
let = generate_ml_dsa_keypair?;
let trust_anchor = new?;
// ── 2. Device provisioning (factory / secure enclave) ───────────────────────
let = generate_ml_dsa_keypair?;
let mut device_cert = DeviceCertificate ;
// CA signs the device certificate (runs on HSM in production)
let tbs = device_cert.tbs_cbor?;
let sig = MlDsaBackend.sign?;
device_cert.issuer_signature = sig.as_ref.to_vec;
// ── 3. Quote generation (prover / device) ───────────────────────────────────
let firmware: & = b"firmware image bytes";
let nonce = ; // supplied by the verifier in a real flow
let fw_hash: = digest.into;
let rot = new; // use hardware backend in prod
let prov = new
.add_subject
.with_slsa_level
.build?;
let dev_vk_array: = dev_vk.try_into.unwrap;
let quote = generate_quote?;
let cbor = quote.to_cbor?;
// ── 4. Quote verification (verifier / server) ───────────────────────────────
let verifier = new;
let result = verifier.verify_cbor_with_pki?;
println!;
println!;
This example uses SoftwareRoT (testing only). Replace it with TpmRoT, DiceRoT, or the
hardware TDX/SEV-SNP backends for production deployments where PolicyEngineV2::production()
is active.
Supported Backends
Software RoT (development/testing — features = ["software-rot-unsafe"])
Hashes memory regions with SHA3-256. Never use in production — the PolicyEngineV2
rejects this backend by default.
let rot = new;
TPM 2.0 — features = ["hardware-tpm"]
Reads the SHA-256 PCR bank (PCRs 0–7) from a hardware or simulated TPM via
tss-esapi (TCG TSS2 ESAPI). Linux only.
= { = "1.0.0-rc.5", = ["hardware-tpm"] }
// Set TPM2TOOLS_TCTI=device:/dev/tpm0 or swtpm:path=/tmp/swtpm.sock
let rot = new;
let m = rot.measure.expect;
DICE RoT — features = ["dice"]
TCG DICE Architecture §6 CDI derivation in pure Rust — no OS, no heap, bare-metal ready.
CDI_attestation = SHA3-256( CDI ‖ "DICE-attest" ‖ SHA3-256(firmware) )
= { = "1.0.0-rc.5", = ["dice"] }
// Two-layer chain: bootloader → application
let m0 = new.measure.unwrap;
let cdi1 = m0.pcrs.0;
let m1 = new.measure.unwrap;
Bitcoin Anchoring
pqrascv-bitcoin-anchor is a library SDK for building and verifying Bitcoin OP_RETURN
anchoring payloads. It provides all the cryptographic machinery:
- OP_RETURN payload encoding/decoding — 40-byte format:
"PQRASCV" || 0x02 || merkle_root - RFC 6962 Merkle tree — second-preimage resistant (leaf prefix
0x00, internal0x01) - SPV inclusion proof verification — offline-verifiable without a full node
OP_RETURN <magic: 7 bytes "PQRASCV"> <version: 1 byte 0x02> <merkle_root: 32 bytes>
Total payload: 40 bytes (well within the 80-byte OP_RETURN limit)
Integration note: The SDK constructs and verifies payloads, but broadcasting transactions
and retrieving confirmation proofs requires an operator-provided Bitcoin node or Electrum server.
The pqrascv verify CLI confirms ML-DSA-65 signature validity; Bitcoin anchoring verification
is a separate step performed against your node using the SDK.
Cryptographic Primitives
| Role | Algorithm | Standard | Sizes |
|---|---|---|---|
| Signatures | ML-DSA-65 | FIPS 204 | seed 32 B · vk 1 952 B · sig 3 309 B |
| Key encapsulation | ML-KEM-768 | FIPS 203 | Used in pqrascv-hardware PQ transport key type |
| Hashing | SHA3-256 | FIPS 202 | 32 B digest |
| Wire encoding | CBOR | RFC 8949 | ~3.7 KB total quote |
All PQ operations are constant-time (RustCrypto guarantee). SigningKeySeed implements
Zeroize and is wiped on drop.
Domain separation contexts are used for all ML-DSA-65 signing operations so that signatures produced in different protocol roles cannot be cross-context replayed.
Performance
| Target | Flash | Stack peak |
|---|---|---|
Cortex-M4 thumbv7em-none-eabi |
< 64 KB | ~12 KB |
RISC-V riscv32imac-unknown-none-elf |
< 68 KB | ~12 KB |
| Linux x86-64 | — | ~16 KB |
Release profile: lto = true, codegen-units = 1, opt-level = 3.
Measurement latency on Cortex-M4 @ 168 MHz: < 1 ms (Software RoT, 64 KB firmware).
Security Considerations
Key storage — SigningKeySeed is 32 bytes and zeroizes on drop. On real hardware, store it
in a hardware-protected keystore (TPM NV, TrustZone, eFuse OTP). Never log or transmit the seed.
Nonce freshness — reusing a nonce breaks replay protection. Generate a fresh 32-byte nonce per request and verify it matches the returned quote exactly.
Trust anchor validity — always pass the current wall-clock time to validate_chain and
validate_chain_with_store. If your trust anchor's not_after has passed, the function returns
TrustAnchorExpired — rotate your root CA before this deadline.
CA key storage — the offline root CA private key must never touch a networked machine. Use an HSM or air-gapped ceremony. A compromised root CA invalidates all device certificates.
CRL freshness — always check the certificate revocation list. VerifiedRevocationList wraps
a CRL whose signature has been verified; do not accept an unverified RevocationList directly.
Verifying key trust — when not using PKI, the caller supplies a trusted verifying key. A compromised key invalidates all quotes signed with it.
DICE CDI confidentiality — the cdi field in DiceRoT is the hardware root secret. It
must never leave the device. Only the one-way cdi_attestation appears in quotes.
Transport layer — ML-DSA-65 protects the signature. If your transport (TLS 1.2, classical ECDH) is not post-quantum, a "harvest now, decrypt later" attacker can record and later decrypt the channel. Pair with a PQ transport (Noise_PQX, planned).
SoftwareRoT — the software-rot-unsafe feature must never be compiled into production
firmware. The PolicyEngineV2 rejects SoftwareRoT by default.
Quote age — set PolicyConfig::max_quote_age_secs to 60–300 s to bound the validity window
of captured quotes.
Status
v1.0.0-rc.5 — API stabilizing. 382 tests pass.
| Implemented ✅ | Preview / Experimental 🔬 | Planned 🗺 |
|---|---|---|
| ML-DSA-65 sign / verify | Sigstore bundle verification (verify_all conditions 1–5) |
Noise_PQX post-quantum transport |
| ML-KEM-768 encapsulation (PQ transport key type) | Fulcio chain + Rekor inclusion proof parsing | CBOR COSE signatures (RFC 9052) |
| Software / TPM 2.0 / DICE backends | Intel TDX backend (intel-tdx feature) |
Python SDK (PyO3 bindings) |
| SLSA v1 provenance + SBOM hash | AMD SEV-SNP backend (amd-sev-snp feature) |
OP-TEE / TrustZone backend |
| CBOR-native PKI (Root CA → Device) | Stable 1.0 API (pending ml-dsa crate GA) |
|
CRL revocation (VerifiedRevocationList) |
||
Trust anchor lifecycle (not_before/not_after) |
||
TrustStore CA rollover |
||
PolicyEngineV2 composable rule engine |
||
| Hardware-identity cross-validation (TDX/SEV-SNP → PCR[0]) | ||
| Bitcoin OP_RETURN anchoring SDK (40-byte payload) | ||
| RFC 6962 Merkle tree + SPV proofs | ||
| ML-DSA-65 domain separation contexts | ||
| CLI prover + verifier binary |
Backend Maturity
| Backend | Feature flag | Maturity | Notes |
|---|---|---|---|
SoftwareRoT |
software-rot-unsafe |
Test / demo only | No hardware boundary; measurements are caller-supplied. Requires --software-rot-acknowledged in CLI. |
| TPM 2.0 | hardware-tpm |
Production | Requires tpm2-tss system library. |
| DICE CDI | dice |
Production | Pure-Rust; suitable for MCU boot chains. |
| Intel TDX | intel-tdx |
Experimental | Linux kernel ≥ 6.5 with /dev/tdx_guest. Community testing welcome. |
| AMD SEV-SNP | amd-sev-snp |
Experimental | Linux kernel ≥ 6.5 with /dev/sev-guest. Community testing welcome. |
Contributing
Issues, PRs, and feedback are welcome at github.com/comwanga/pqrascv-core.
Areas where contributions are especially valuable:
- Platform backends — SEV-SNP, TDX, OP-TEE, Apple Secure Enclave
- Transport — Noise_PQX integration, COSE/CBOR signing
- Provenance — Sigstore / Rekor / Fulcio client integration
- Tooling — Hardware provisioning scripts, key management daemons
- Verification —
kaniharnesses for the crypto paths, fuzzing
See docs/ARCHITECTURE.md for the full system design.
License
Licensed under either of MIT or Apache 2.0 at your option.
Contributions are dual-licensed under the same terms unless explicitly stated otherwise.