pqrascv-cli 1.0.0-rc.8

Command-line interface for the PQ-RASCV hardware attestation protocol
Documentation

pqrascv-core

Post-Quantum Remote Attestation & Supply-Chain Verification

Crates.io docs.rs License: MIT OR Apache-2.0 no_std CI

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 provenance predicate binding a device's firmware identity to its build pipeline — on bare-metal Cortex-M4, RISC-V, WASM, or Linux.

Why PQ-RASCV?

Traditional attestation proves what is running but not how it was built or who signed it off. Classical signatures (RSA, ECDSA) are also broken by Shor's algorithm. PQ-RASCV fixes both:

  • Post-quantum by default — ML-DSA-65 signatures, no RSA or ECDSA in the protocol
  • Supply-chain provenance — SLSA v1 predicate + SBOM hash inside every signed quote
  • One API everywhere — Cortex-M4, RISC-V, WASM, Linux, all using the same code
  • no_std + alloc — works on bare-metal without an OS

Add to your project

# std (default)
pqrascv-core = "1.0.0-rc.8"

# bare-metal / no_std
pqrascv-core = { version = "1.0.0-rc.8", default-features = false, features = ["alloc"] }

CLI — fastest way to try it

cargo install pqrascv-cli

# Generate a keypair
pqrascv keygen --out-seed seed.bin --out-vk vk.bin

# Generate an attestation quote (prover side)
pqrascv attest \
  --seed seed.bin --vk vk.bin \
  --firmware firmware.bin \
  --slsa-level 2 \
  --out quote.cbor \
  --software-rot-acknowledged

# Verify the quote (verifier side) — use the nonce printed by attest
pqrascv verify \
  --vk vk.bin \
  --quote quote.cbor \
  --nonce <64-HEX-NONCE> \
  --json

Library usage

Prover (device side)

use pqrascv_core::{
    crypto::{generate_ml_dsa_keypair, MlDsaBackend},
    measurement::SoftwareRoT,
    provenance::SlsaPredicateBuilder,
    quote::{generate_quote, QuoteTimestamp},
};

let (sk, vk) = generate_ml_dsa_keypair().unwrap();

// SoftwareRoT is for testing only. Use TpmRoT or DiceRoT in production.
let rot = SoftwareRoT::new(b"my-firmware-image", None, 1);

let provenance = SlsaPredicateBuilder::new("https://ci.example.com/pipeline/42")
    .add_subject("firmware.bin", &[0xabu8; 32])
    .with_slsa_level(2)
    .build()
    .unwrap();

let nonce = [0x42u8; 32]; // received from the verifier

let quote = generate_quote(
    &rot, &MlDsaBackend, sk.as_bytes(), &vk,
    &nonce, provenance, QuoteTimestamp::Rtc(1_700_000_000),
).unwrap();

let cbor_bytes = quote.to_cbor().unwrap(); // send to verifier

Verifier (server side)

use pqrascv_verifier::Verifier;
use pqrascv_core::config::PolicyConfig;

let verifier = Verifier::new(PolicyConfig {
    min_slsa_level: 2,
    max_quote_age_secs: 300,
    require_firmware_hash: true,
    ..Default::default()
});

match verifier.verify_cbor(&cbor_bytes, &trusted_vk, &nonce, now_secs) {
    Ok(r)  => println!("Verified — SLSA {}", r.slsa_level()),
    Err(e) => eprintln!("Rejected: {e}"),
}

Python

# Build the extension
cd crates/pqrascv-python && maturin develop --features extension-module
import pqrascv_python

key = pqrascv_python.MlDsaKey.generate()
vk  = key.verifying_key_bytes()           # 1952 bytes
sig = key.sign(b"hello attestation")      # 3309 bytes
assert pqrascv_python.MlDsaKey.verify(vk, b"hello attestation", sig)

C / Embedded

#include "include/pqrascv.h"

unsigned char sk[32], vk[1952], sig[3309];
size_t sk_len = sizeof(sk), vk_len = sizeof(vk), sig_len = sizeof(sig);

pqrascv_generate_keypair(sk, &sk_len, vk, &vk_len);
pqrascv_sign(sk, sk_len, (uint8_t*)"msg", 3, sig, &sig_len);
assert(pqrascv_verify(vk, vk_len, (uint8_t*)"msg", 3, sig, sig_len) == PQRASCV_OK);
cargo build -p pqrascv-ffi   # generates include/pqrascv.h + libpqrascv_ffi.{so,a}

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 + pub_key_id fingerprint
   └── evaluate PolicyConfig → accept / reject

Each quote carries: protocol version, timestamp, nonce, 8 × SHA3-256 PCRs, firmware hash, SLSA v1 predicate, and a 3 309-byte ML-DSA-65 signature. Wire size: ~3.7 KB CBOR.


Feature flags

Feature Default What it enables
std yes std::error::Error on the error type
alloc yes Quote and provenance assembly
hardware-tpm no TPM 2.0 PCR reads via tss-esapi (Linux)
dice no DICE CDI derivation — pure Rust, no OS required
software-rot-unsafe no Software SHA3-256 RoT — testing only

Measurement backends

Backend Feature Status
SoftwareRoT software-rot-unsafe Testing / demo only — rejected by PolicyEngineV2::production()
TpmRoT hardware-tpm Production — requires tpm2-tss system library (Linux)
DiceRoT dice Production — pure Rust, suitable for MCU boot chains
TdxRoT intel-tdx Experimental — Linux kernel ≥ 5.19, /dev/tdx_guest
SevSnpRoT amd-sev-snp Experimental — Linux kernel ≥ 5.19, /dev/sev-guest

Security considerations

  • Keep the seed secret. Store it in a TPM NV slot, eFuse, or TrustZone keystore — not in flash.
  • Use a fresh nonce every request. Reusing a nonce breaks replay protection.
  • Set max_quote_age_secs to 60–300 s to bound the validity window of captured quotes.
  • SoftwareRoT is test-only. PolicyEngineV2::production() rejects it; the CLI requires --software-rot-acknowledged.
  • Post-quantum transport. ML-DSA-65 protects the signature. Pair with a PQ transport (Noise_PQX / COSE Sign1) to close the channel against "harvest now, decrypt later".

See SECURITY.md for the vulnerability reporting policy.


Status — v1.0.0-rc.8

API is stabilizing. The ml-dsa crate is pinned to =0.1.0-rc.8 (FIPS-final algorithm, pre-stable Rust crate). The stable 1.0 release will follow the first stable ml-dsa0.1.0.

Ready Experimental
ML-DSA-65 sign / verify Intel TDX backend
Software / TPM 2.0 / DICE backends AMD SEV-SNP backend
SLSA v1 provenance + SBOM hash Sigstore verify_all (1-hop Fulcio + SET-based Rekor)
CBOR-native PKI (Root CA → Device) OP-TEE, Apple Secure Enclave backends
CRL revocation, CA rollover (TrustStore)
PolicyEngineV2 composable rule engine
Bitcoin OP_RETURN anchoring + Merkle / SPV proofs
COSE Sign1 (RFC 9052) + Noise_PQX transport
CLI (pqrascv attest / pqrascv verify)
Python bindings (PyO3 0.24)
C FFI + include/pqrascv.h
Kani formal verification (4 harnesses)
Fuzz targets (7)

Contributing

git clone https://github.com/comwanga/pqrascv-core.git && cd pqrascv-core
cargo test --workspace
cargo clippy --workspace -- -D warnings
cargo fmt --all -- --check

See CONTRIBUTING.md for coding standards and the review process. Issues and PRs are welcome at github.com/comwanga/pqrascv-core.


License

MIT OR Apache-2.0 at your option.