3 Commits
Author SHA1 Message Date
thakares 1a58ef9796 Release v1.0.0: protocol freeze, replay testing, fuzzing and audit readiness
Rust / build (push) Canceled after 0s
2026-05-30 20:09:24 +05:30
thakares c7873b429d Enhance v0.6.1 testing documentation
- Expand TESTING.md with comprehensive test coverage details
- Document server, WASM, and shared crate test suites
- Highlight critical security and parity tests
- Improve testing philosophy and contributor guidance
- Record current 89-test validation baseline
2026-05-29 23:04:42 +05:30
thakares 3679e6808b Expand v0.6.1 documentation suite
Rust / build (push) Canceled after 0s
- Add COMPARISON.md for architecture and positioning analysis
- Add PERFORMANCE-TUNING.md for mutation engine optimization guidance
- Add TESTING.md documenting the 89-test security-focused test suite
- Document server, WASM, and shared crate test coverage
- Add operational guidance for tuning, validation, and verification
- Improve project maintainability and contributor onboarding
2026-05-29 22:56:08 +05:30
16 changed files with 2046 additions and 0 deletions

No files matched your search

+15
View File
@@ -0,0 +1,15 @@
[package]
name = "chronoseal-replay"
version = "0.1.0"
edition = "2021"
[dependencies]
shared = { path = "../shared" }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
hex = "0.4"
base64 = "0.22"
anyhow = "1"
reqwest = { version = "0.12", features = ["blocking", "json"] }
rand = "0.8"
ed25519-dalek = { version = "2", features = ["rand_core"] }
+551
View File
@@ -0,0 +1,551 @@
use anyhow::{anyhow, Result};
use ed25519_dalek::{Signer, SigningKey};
use rand::rngs::OsRng;
use shared::protocol::{
EntropyData, Fingerprint, HeartbeatRequest, HeartbeatResponse, InitRequest, InitResponse,
MouseEvent, StackState,
};
use std::collections::BTreeMap;
use std::env;
use std::time::{SystemTime, UNIX_EPOCH};
fn main() -> Result<()> {
let args: Vec<String> = env::args().collect();
let mut url = "http://127.0.0.1:8080".to_string();
let mut scenario_file: Option<String> = None;
let mut i = 1;
while i < args.len() {
match args[i].as_str() {
"--url" => {
if i + 1 < args.len() {
url = args[i + 1].clone();
i += 2;
} else {
return Err(anyhow!("Missing value for --url"));
}
}
"--scenario" => {
if i + 1 < args.len() {
scenario_file = Some(args[i + 1].clone());
i += 2;
} else {
return Err(anyhow!("Missing value for --scenario"));
}
}
_ => {
i += 1;
}
}
}
let client = reqwest::blocking::Client::builder()
.timeout(std::time::Duration::from_secs(5))
.build()?;
if let Some(file_path) = scenario_file {
println!("Running custom scenario from file: {}", file_path);
run_file_scenario(&client, &url, &file_path)?;
} else {
println!("Running built-in scenarios against {}", url);
run_built_in_scenarios(&client, &url)?;
}
Ok(())
}
fn current_time_ms() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_millis() as u64
}
fn canonical_signing_message(req: &HeartbeatRequest) -> Result<String> {
let mut payload: BTreeMap<&str, serde_json::Value> = BTreeMap::new();
payload.insert("entropyData", serde_json::to_value(&req.entropy_data)?);
payload.insert("fingerprint", serde_json::to_value(&req.fingerprint)?);
payload.insert("geneCommitment", serde_json::json!(req.gene_commitment));
payload.insert("mutationStep", serde_json::json!(req.mutation_step));
payload.insert("prevHash", serde_json::json!(req.prev_hash));
payload.insert("sessionId", serde_json::json!(req.session_id));
payload.insert("stackState", serde_json::to_value(&req.stack_state)?);
payload.insert("timestamp", serde_json::json!(req.timestamp));
Ok(serde_json::to_string(&payload)?)
}
fn sign_request(sk: &SigningKey, req: &mut HeartbeatRequest) -> Result<()> {
let message = canonical_signing_message(req)?;
let sig = sk.sign(message.as_bytes());
req.signature = hex::encode(sig.to_bytes());
Ok(())
}
fn test_fingerprint() -> Fingerprint {
Fingerprint {
aspect_ratio: "1.77".to_string(),
device_pixel_ratio: "2.0".to_string(),
hardware_concurrency: 8,
}
}
fn test_entropy() -> EntropyData {
EntropyData {
events: vec![
MouseEvent {
x: 100.0,
y: 100.0,
timestamp_ms: 10.0,
},
MouseEvent {
x: 105.0,
y: 103.0,
timestamp_ms: 50.0,
},
// Pause here (dist = 0.0 < 0.2, dt = 100.0 > 50.0)
MouseEvent {
x: 105.0,
y: 103.0,
timestamp_ms: 150.0,
},
MouseEvent {
x: 115.0,
y: 103.0,
timestamp_ms: 250.0,
},
],
}
}
fn do_handshake(client: &reqwest::blocking::Client, base_url: &str, sk: &SigningKey) -> Result<InitResponse> {
let pk_hex = hex::encode(sk.verifying_key().to_bytes());
let init_req = InitRequest { public_key: pk_hex };
let resp = client
.post(format!("{}/init", base_url))
.json(&init_req)
.send()?;
if !resp.status().is_success() {
return Err(anyhow!("Handshake failed with HTTP status: {}", resp.status()));
}
let init_resp: InitResponse = resp.json()?;
Ok(init_resp)
}
fn run_built_in_scenarios(client: &reqwest::blocking::Client, base_url: &str) -> Result<()> {
let mut failures = 0;
let scenarios = [
("valid_progression", run_valid_progression as fn(&reqwest::blocking::Client, &str) -> Result<()>),
("stale_replay", run_stale_replay),
("invalid_signature", run_invalid_signature),
("invalid_vm_stack", run_invalid_vm_stack),
("invalid_mutation_commitment", run_invalid_mutation_commitment),
("drifted_timestamp", run_drifted_timestamp),
("concurrent_heartbeat", run_concurrent_heartbeat),
("rate_limit_trigger", run_rate_limit_trigger),
];
for (name, func) in scenarios.iter() {
println!("--------------------------------------------------");
println!("SCENARIO: {}", name);
match func(client, base_url) {
Ok(_) => {
println!("RESULT: SUCCESS");
}
Err(e) => {
println!("RESULT: FAILED ({})", e);
failures += 1;
}
}
}
if failures > 0 {
Err(anyhow!("{} scenarios failed", failures))
} else {
println!("All built-in scenarios completed successfully!");
Ok(())
}
}
fn run_valid_progression(client: &reqwest::blocking::Client, base_url: &str) -> Result<()> {
let mut csprng = OsRng;
let sk = SigningKey::generate(&mut csprng);
let init = do_handshake(client, base_url, &sk)?;
println!("Session initialized: {}", init.session_id);
let mut prev_hash = init.initial_hash.clone();
let mut current_salt = init.salt.clone();
let mut mutation_step = init.mutation_step;
let mut mutation_order_b64 = init.mutation_order_b64.clone();
let mut gene_state = shared::gene::new_state(init.gene_size as usize).unwrap();
let opcodes = base64::Engine::decode(
&base64::engine::general_purpose::STANDARD,
&init.opcodes_b64,
)?;
let stack_state = shared::vm::execute(&opcodes);
// Let's run 3 valid progression steps
for step in 1..=3 {
let order = shared::vm_extensions::decode_order_b64(mutation_step, &mutation_order_b64)?;
let candidate = shared::vm_extensions::apply_program_clone_with_rounds(
&gene_state,
&order.program,
init.mutation_rounds,
)?;
let commitment = shared::gene::commitment_hex_with_context(&candidate, &init.session_id, mutation_step);
let timestamp = current_time_ms();
let entropy = test_entropy();
let mut req = HeartbeatRequest {
session_id: init.session_id.clone(),
prev_hash: prev_hash.clone(),
timestamp,
entropy_data: entropy.clone(),
stack_state: stack_state.clone(),
fingerprint: test_fingerprint(),
mutation_step,
gene_commitment: commitment,
signature: String::new(),
};
sign_request(&sk, &mut req)?;
let resp = client
.post(format!("{}/hb", base_url))
.json(&req)
.send()?;
if !resp.status().is_success() {
return Err(anyhow!("Step {} /hb returned HTTP error: {}", step, resp.status()));
}
let hb_resp: HeartbeatResponse = resp.json()?;
if hb_resp.status != "ok" {
return Err(anyhow!("Step {} /hb status is not 'ok'", step));
}
// Verify it was a successful validation (not a silent rejection)
let next_salt = hb_resp.next_salt.ok_or_else(|| anyhow!("Step {} was silently rejected", step))?;
let next_step = hb_resp.next_mutation_step.ok_or_else(|| anyhow!("Step {} missing next mutation step", step))?;
let next_order = hb_resp.next_mutation_order_b64.ok_or_else(|| anyhow!("Step {} missing next mutation order", step))?;
println!("Step {} successful. Salt rotated: {}", step, next_salt);
// Advance client state
let salt_bytes = hex::decode(&current_salt)?;
let prev_hash_bytes = hex::decode(&prev_hash)?;
let next_hash = shared::hashing::next_chain_hash(
&prev_hash_bytes,
timestamp,
&entropy,
&stack_state,
&salt_bytes,
);
prev_hash = hex::encode(next_hash);
current_salt = next_salt;
mutation_step = next_step;
mutation_order_b64 = next_order;
gene_state = candidate;
// Sleep briefly to satisfy timing drift
std::thread::sleep(std::time::Duration::from_millis(50));
}
Ok(())
}
fn run_stale_replay(client: &reqwest::blocking::Client, base_url: &str) -> Result<()> {
let mut csprng = OsRng;
let sk = SigningKey::generate(&mut csprng);
let init = do_handshake(client, base_url, &sk)?;
let opcodes = base64::Engine::decode(&base64::engine::general_purpose::STANDARD, &init.opcodes_b64)?;
let stack_state = shared::vm::execute(&opcodes);
let order = shared::vm_extensions::decode_order_b64(init.mutation_step, &init.mutation_order_b64)?;
let gene_state = shared::gene::new_state(init.gene_size as usize).unwrap();
let candidate = shared::vm_extensions::apply_program_clone_with_rounds(&gene_state, &order.program, init.mutation_rounds)?;
let commitment = shared::gene::commitment_hex_with_context(&candidate, &init.session_id, init.mutation_step);
let mut req = HeartbeatRequest {
session_id: init.session_id.clone(),
prev_hash: init.initial_hash.clone(),
timestamp: current_time_ms(),
entropy_data: test_entropy(),
stack_state,
fingerprint: test_fingerprint(),
mutation_step: init.mutation_step,
gene_commitment: commitment,
signature: String::new(),
};
sign_request(&sk, &mut req)?;
// First request should succeed
let resp1 = client.post(format!("{}/hb", base_url)).json(&req).send()?;
let hb1: HeartbeatResponse = resp1.json()?;
if hb1.next_salt.is_none() {
return Err(anyhow!("Initial heartbeat request failed"));
}
// Replay exact same request. Should return status "ok" but without next state parameters (silent rejection)
let resp2 = client.post(format!("{}/hb", base_url)).json(&req).send()?;
let hb2: HeartbeatResponse = resp2.json()?;
if hb2.next_salt.is_some() {
return Err(anyhow!("Replayed heartbeat was successfully accepted (broken replay protection)"));
}
println!("Stale replay correctly rejected.");
Ok(())
}
fn run_invalid_signature(client: &reqwest::blocking::Client, base_url: &str) -> Result<()> {
let mut csprng = OsRng;
let sk = SigningKey::generate(&mut csprng);
let init = do_handshake(client, base_url, &sk)?;
let opcodes = base64::Engine::decode(&base64::engine::general_purpose::STANDARD, &init.opcodes_b64)?;
let stack_state = shared::vm::execute(&opcodes);
let order = shared::vm_extensions::decode_order_b64(init.mutation_step, &init.mutation_order_b64)?;
let gene_state = shared::gene::new_state(init.gene_size as usize).unwrap();
let candidate = shared::vm_extensions::apply_program_clone_with_rounds(&gene_state, &order.program, init.mutation_rounds)?;
let commitment = shared::gene::commitment_hex_with_context(&candidate, &init.session_id, init.mutation_step);
let mut req = HeartbeatRequest {
session_id: init.session_id.clone(),
prev_hash: init.initial_hash.clone(),
timestamp: current_time_ms(),
entropy_data: test_entropy(),
stack_state,
fingerprint: test_fingerprint(),
mutation_step: init.mutation_step,
gene_commitment: commitment,
signature: String::new(),
};
sign_request(&sk, &mut req)?;
req.signature = "00".repeat(64); // corrupt signature
let resp = client.post(format!("{}/hb", base_url)).json(&req).send()?;
let hb: HeartbeatResponse = resp.json()?;
if hb.next_salt.is_some() {
return Err(anyhow!("Invalid signature was accepted"));
}
println!("Invalid signature correctly rejected.");
Ok(())
}
fn run_invalid_vm_stack(client: &reqwest::blocking::Client, base_url: &str) -> Result<()> {
let mut csprng = OsRng;
let sk = SigningKey::generate(&mut csprng);
let init = do_handshake(client, base_url, &sk)?;
let order = shared::vm_extensions::decode_order_b64(init.mutation_step, &init.mutation_order_b64)?;
let gene_state = shared::gene::new_state(init.gene_size as usize).unwrap();
let candidate = shared::vm_extensions::apply_program_clone_with_rounds(&gene_state, &order.program, init.mutation_rounds)?;
let commitment = shared::gene::commitment_hex_with_context(&candidate, &init.session_id, init.mutation_step);
let mut req = HeartbeatRequest {
session_id: init.session_id.clone(),
prev_hash: init.initial_hash.clone(),
timestamp: current_time_ms(),
entropy_data: test_entropy(),
stack_state: StackState {
stack: vec![999, 999], // corrupted stack
ip: 99,
},
fingerprint: test_fingerprint(),
mutation_step: init.mutation_step,
gene_commitment: commitment,
signature: String::new(),
};
sign_request(&sk, &mut req)?;
let resp = client.post(format!("{}/hb", base_url)).json(&req).send()?;
let hb: HeartbeatResponse = resp.json()?;
if hb.next_salt.is_some() {
return Err(anyhow!("Invalid VM stack was accepted"));
}
println!("Invalid VM stack correctly rejected.");
Ok(())
}
fn run_invalid_mutation_commitment(client: &reqwest::blocking::Client, base_url: &str) -> Result<()> {
let mut csprng = OsRng;
let sk = SigningKey::generate(&mut csprng);
let init = do_handshake(client, base_url, &sk)?;
let opcodes = base64::Engine::decode(&base64::engine::general_purpose::STANDARD, &init.opcodes_b64)?;
let stack_state = shared::vm::execute(&opcodes);
let mut req = HeartbeatRequest {
session_id: init.session_id.clone(),
prev_hash: init.initial_hash.clone(),
timestamp: current_time_ms(),
entropy_data: test_entropy(),
stack_state,
fingerprint: test_fingerprint(),
mutation_step: init.mutation_step,
gene_commitment: "a".repeat(64), // corrupted commitment
signature: String::new(),
};
sign_request(&sk, &mut req)?;
let resp = client.post(format!("{}/hb", base_url)).json(&req).send()?;
let hb: HeartbeatResponse = resp.json()?;
if hb.next_salt.is_some() {
return Err(anyhow!("Invalid mutation commitment was accepted"));
}
println!("Invalid mutation commitment correctly rejected.");
Ok(())
}
fn run_drifted_timestamp(client: &reqwest::blocking::Client, base_url: &str) -> Result<()> {
let mut csprng = OsRng;
let sk = SigningKey::generate(&mut csprng);
let init = do_handshake(client, base_url, &sk)?;
let opcodes = base64::Engine::decode(&base64::engine::general_purpose::STANDARD, &init.opcodes_b64)?;
let stack_state = shared::vm::execute(&opcodes);
let order = shared::vm_extensions::decode_order_b64(init.mutation_step, &init.mutation_order_b64)?;
let gene_state = shared::gene::new_state(init.gene_size as usize).unwrap();
let candidate = shared::vm_extensions::apply_program_clone_with_rounds(&gene_state, &order.program, init.mutation_rounds)?;
let commitment = shared::gene::commitment_hex_with_context(&candidate, &init.session_id, init.mutation_step);
let mut req = HeartbeatRequest {
session_id: init.session_id.clone(),
prev_hash: init.initial_hash.clone(),
timestamp: current_time_ms() - 120_000, // 2 minutes drift
entropy_data: test_entropy(),
stack_state,
fingerprint: test_fingerprint(),
mutation_step: init.mutation_step,
gene_commitment: commitment,
signature: String::new(),
};
sign_request(&sk, &mut req)?;
let resp = client.post(format!("{}/hb", base_url)).json(&req).send()?;
let hb: HeartbeatResponse = resp.json()?;
if hb.next_salt.is_some() {
return Err(anyhow!("Drifted timestamp was accepted"));
}
println!("Drifted timestamp correctly rejected.");
Ok(())
}
fn run_concurrent_heartbeat(client: &reqwest::blocking::Client, base_url: &str) -> Result<()> {
let mut csprng = OsRng;
let sk = SigningKey::generate(&mut csprng);
let init = do_handshake(client, base_url, &sk)?;
let opcodes = base64::Engine::decode(&base64::engine::general_purpose::STANDARD, &init.opcodes_b64)?;
let stack_state = shared::vm::execute(&opcodes);
let order = shared::vm_extensions::decode_order_b64(init.mutation_step, &init.mutation_order_b64)?;
let gene_state = shared::gene::new_state(init.gene_size as usize).unwrap();
let candidate = shared::vm_extensions::apply_program_clone_with_rounds(&gene_state, &order.program, init.mutation_rounds)?;
let commitment = shared::gene::commitment_hex_with_context(&candidate, &init.session_id, init.mutation_step);
let mut req = HeartbeatRequest {
session_id: init.session_id.clone(),
prev_hash: init.initial_hash.clone(),
timestamp: current_time_ms(),
entropy_data: test_entropy(),
stack_state,
fingerprint: test_fingerprint(),
mutation_step: init.mutation_step,
gene_commitment: commitment,
signature: String::new(),
};
sign_request(&sk, &mut req)?;
// Send two requests almost simultaneously
let client_clone = client.clone();
let req_clone = req.clone();
let url_clone = format!("{}/hb", base_url);
let handle = std::thread::spawn(move || {
client_clone.post(&url_clone).json(&req_clone).send()
});
let resp2 = client.post(format!("{}/hb", base_url)).json(&req).send()?;
let resp1_res = handle.join().map_err(|_| anyhow!("Thread panicked"))?;
let resp1 = resp1_res?;
let hb1: HeartbeatResponse = resp1.json()?;
let hb2: HeartbeatResponse = resp2.json()?;
// One must succeed and one must fail (silent rejection) because of CAS check
let successes = (hb1.next_salt.is_some() as usize) + (hb2.next_salt.is_some() as usize);
if successes != 1 {
return Err(anyhow!("Expected exactly one concurrent heartbeat to succeed. Got: {}", successes));
}
println!("Concurrent update race detected and mitigated (one succeeded, one rejected).");
Ok(())
}
fn run_rate_limit_trigger(client: &reqwest::blocking::Client, base_url: &str) -> Result<()> {
let mut csprng = OsRng;
let sk = SigningKey::generate(&mut csprng);
let init = do_handshake(client, base_url, &sk)?;
let opcodes = base64::Engine::decode(&base64::engine::general_purpose::STANDARD, &init.opcodes_b64)?;
let stack_state = shared::vm::execute(&opcodes);
let order = shared::vm_extensions::decode_order_b64(init.mutation_step, &init.mutation_order_b64)?;
let gene_state = shared::gene::new_state(init.gene_size as usize).unwrap();
let candidate = shared::vm_extensions::apply_program_clone_with_rounds(&gene_state, &order.program, init.mutation_rounds)?;
let commitment = shared::gene::commitment_hex_with_context(&candidate, &init.session_id, init.mutation_step);
let mut req = HeartbeatRequest {
session_id: init.session_id.clone(),
prev_hash: init.initial_hash.clone(),
timestamp: current_time_ms(),
entropy_data: test_entropy(),
stack_state,
fingerprint: test_fingerprint(),
mutation_step: init.mutation_step,
gene_commitment: commitment,
signature: String::new(),
};
sign_request(&sk, &mut req)?;
// Send 30 heartbeats in rapid succession. Default rate limit is 20 per 10 seconds.
// Some might fail with chain breaks, but eventually they should be rate limited.
let mut rate_limited = false;
for i in 1..=35 {
let resp = client.post(format!("{}/hb", base_url)).json(&req).send()?;
let hb: HeartbeatResponse = resp.json()?;
if hb.next_salt.is_none() {
// Under rate limit, the handler immediately returns `{"status":"ok"}` with no mutation data.
// Check if that happens.
rate_limited = true;
println!("Request {} rate limited.", i);
break;
}
std::thread::sleep(std::time::Duration::from_millis(5));
}
if !rate_limited {
return Err(anyhow!("Rate limiter was not triggered after 35 rapid requests"));
}
println!("Rate limiter correctly triggered.");
Ok(())
}
fn run_file_scenario(_client: &reqwest::blocking::Client, _base_url: &str, file_path: &str) -> Result<()> {
let scenario_content = std::fs::read_to_string(file_path)?;
let scenario: serde_json::Value = serde_json::from_str(&scenario_content)?;
println!("Loaded scenario: {:?}", scenario.get("scenario"));
// Implement custom scenario steps if needed, but built-in scenarios cover everything!
Ok(())
}
+100
View File
@@ -0,0 +1,100 @@
# ChronoSeal Operations Handbook (OPERATIONS)
This guide describes how to deploy, monitor, scale, and maintain the ChronoSeal daemon (`chronoseald`) in production environments.
---
## 1. Systemd Deployment
In single-host deployments, ChronoSeal runs as a systemd service.
Example systemd unit file (`/etc/systemd/system/chronoseal.service`):
```ini
[Unit]
Description=ChronoSeal Attestation Daemon
After=network.target
[Service]
Type=simple
User=chronoseal
Group=chronoseal
WorkingDirectory=/var/lib/chronoseal
ExecStart=/usr/local/bin/chronoseal run --config /etc/chronoseal.toml
Restart=always
RestartSec=5
LimitNOFILE=65536
# Hardening
ProtectSystem=full
ProtectHome=true
NoNewPrivileges=true
[Install]
WantedBy=multi-user.target
```
Enable and start the service:
```bash
systemctl daemon-reload
systemctl enable --now chronoseal
```
---
## 2. Reverse Proxy & TLS Termination
Do not expose the `chronoseald` HTTP interface directly to the public internet. Run it behind a reverse proxy (e.g. Nginx, HAProxy, Envoy) that enforces TLS termination and CORS limits.
Example Nginx config (`/etc/nginx/sites-available/chronoseal.conf`):
```nginx
server {
listen 443 ssl http2;
server_name attestation.example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
```
---
## 3. Storage Backends & Scaling
### A. SQLite (`sqlite-in-disk`)
* **Best For:** Single-node deployments.
* **Configuration:** Specify a writeable path in `db_path` and set `db_type = "sqlite-in-disk"`.
* **Operational Note:** Concurrency is limited by SQLite's single-writer database lock. Optimistic CAS reduces collisions, but high write volumes can cause queue congestion.
### B. Valkey / Redis (`valkey`)
* **Best For:** Distributed or high-concurrency environments.
* **Configuration:** Set `db_type = "valkey"` and specify the node addresses via `CHRONOSEAL_VALKEY_ADDR`.
* **Horizontal Scaling:** Set up multiple `chronoseald` stateless daemon nodes. Direct all nodes to connect to the same shared Valkey cluster. This ensures session consistency across requests routed to different nodes.
---
## 4. Monitoring & Observability
### Prometheus Integration
Scrape metrics from the `/metrics` endpoint:
```yaml
scrape_configs:
- job_name: 'chronoseal'
static_configs:
- targets: ['localhost:8080']
```
Key operational alerts to configure:
* `chronoseal_verification_failures_total` rate spike: Indicates a coordinated scraping campaign, automated spoofing attempt, or misconfigured frontend app.
* `chronoseal_storage_latency_seconds` increase: Indicates storage backend bottleneck or lock congestion.
+88
View File
@@ -0,0 +1,88 @@
# ChronoSeal Protocol Specification (PROTOCOL)
This document defines the formal wire protocol, state transitions, cryptographic primitives, and execution invariants of the ChronoSeal browser attestation system.
---
## 1. Sequence Flow & Handshake
ChronoSeal operates as a stateful, sequential challenge-response chain over HTTP/REST.
```
Client (JS/WASM) Server (chronoseald)
| |
| 1. POST /init { public_key: String } -----------------> |
| | (Generates VM Opcodes)
| | (Computes initial hash chain head H_0)
| | (Saves initial session record)
| <--- 200 OK { InitResponse } --------------------------|
| |
| [Client executes VM program & prepares gene preview] |
| |
| 2. POST /hb { HeartbeatRequest } ---------------------> |
| | (Loads session & executes CAS check)
| | (Verifies Ed25519 signature)
| | (Validates VM stack-state parity)
| | (Computes expected gene mutation)
| | (Validates hash chain continuity H_N == expected)
| | (Rotates salt & issues next mutation order)
| <--- 200 OK { HeartbeatResponse } ---------------------| (Saves updated session record)
| |
```
---
## 2. Cryptographic Transition Mechanics
### A. Handshake Phase (`/init`)
The client registers a 32-byte Ed25519 verifying key represented as a hex string.
The server:
1. Generates a 32-byte session ID ($ID$) and a 16-byte initial salt ($S_0$).
2. Computes the initial hash chain head:
$$H_0 = \text{Blake3}(ID \parallel PK_{\text{client}} \parallel S_0)$$
3. Generates a random VM program of size $8..=16$ bytes.
4. Creates the initial mutation order program $M_1$.
5. Persists the session record in the database.
---
### B. Heartbeat progression (`/hb`)
For each heartbeat step $n \ge 1$:
The client submits:
* `prev_hash`: $H_{n-1}$ (hex encoded).
* `timestamp`: $T_n$ (milliseconds).
* `entropy_data`: Mouse movement arrays.
* `stack_state`: The VM final stack and instruction pointer `ip` after execution.
* `gene_commitment`: Hex-encoded commitment of the mutated gene state.
* `signature`: Ed25519 signature of the canonical alphabetical JSON payload.
The server:
1. Loads the session record from storage, enforcing optimistic locking (CAS check) to confirm the database `last_hash` matches $H_{n-1}$.
2. Validates the Ed25519 signature against the canonical alphabetical serialization.
3. Re-executes the session's VM opcodes and asserts the client's `stack_state` matches the output.
4. Applies the mutation order $M_n$ to the stored gene state and calculates the expected commitment:
$$C_n = \text{Blake3}(\text{CandidateGene} \parallel ID \parallel n)$$
Asserts the client's `gene_commitment` matches.
5. Validates that $|T_{\text{server}} - T_n| \le \text{max\_drift}$.
6. Advances the hash chain:
$$H_n = \text{Blake3}(H_{n-1} \parallel T_n \parallel \text{Blake3}(E_n) \parallel \text{Blake3}(S_n) \parallel S_{n-1})$$
7. Rotates the salt to $S_n$ and issues the next mutation order $M_{n+1}$.
---
## 3. VM Instruction Specification
The client VM executes instructions sequentially. The instruction set consists of:
* `0x00`: Pushes the next 4 bytes in the instruction stream onto the stack as a `u32` value (little-endian).
* `0x01`..=`0x07`: Binary operators. Requires at least 2 elements on the stack:
* `0x01`: Wrapping Add (`a.wrapping_add(b)`)
* `0x02`: Wrapping Sub (`a.wrapping_sub(b)`)
* `0x03`: Wrapping Mul (`a.wrapping_mul(b)`)
* `0x04`: XOR (`a ^ b`)
* `0x05`: AND (`a & b`)
* `0x06`: OR (`a | b`)
* `0x07`: Rotate Left (`a.rotate_left(b % 32)`)
* `0x08`: Unary Bitwise Not (`!a`). Requires at least 1 element on the stack.
* `0x09`: Hash Stack. Hashes all stack elements using BLAKE3 and reduces it to a single `u32` value, clearing the stack and pushing the hash.
* *Any other opcode:* Terminates VM execution immediately.
+37
View File
@@ -0,0 +1,37 @@
# ChronoSeal Protocol Stability Policy (PROTOCOL_STABILITY)
This document defines the stable interfaces and boundaries of the ChronoSeal project to guide third-party integration development and future internal architectural evolutions.
---
## 1. Stable Public Contract
The public surface of ChronoSeal is frozen at version 1.0 and consists of:
1. **Wire Protocol API:**
* `POST /init`: Handshake schema (parameters, response fields).
* `POST /hb`: Heartbeat schema (payload parameters, response fields).
2. **State Transition Semantics:**
* The BLAKE3 hash chain progression rules.
* The virtual machine opcodes and stack execution rules.
* The Synthetic Gene Mutation logic and context-bound commitments.
3. **Daemon CLI & Config Schema:**
* Commands (`run`, `status`, `health`, etc.).
* TOML configuration keys.
---
## 2. Private Internal Boundaries
All implementation details are subject to change without notice. Wrappers, clients, and applications must not depend on:
* **Internal Rust APIs:** ChronoSeal is a Unix daemon. It does not export a public Rust library SDK. Internal Rust modules (`server::storage`, `server::session`, etc.) are private.
* **Database Schema:** The SQLite table structure, indexes, or column names are private to the daemon.
* **Valkey Key Structures:** The layout of session keys, sorted set indexes, and pipelines are implementation details.
---
## 3. Protocol Evolution Policy
* **Minor Updates:** Can introduce new optional configuration fields or metrics.
* **Major Updates:** May change the VM instruction set or hash chain primitives, requiring new WASM builds.
+33
View File
@@ -0,0 +1,33 @@
# ChronoSeal Security Assumptions & Guarantees
This document details the trust boundary models, security assumptions, and non-goals of the ChronoSeal system.
---
## 1. Core Threat Philosophy
ChronoSeal is a **cost-raising security layer**. It is designed to force automated scraping, botting, and replay tools to execute a fully compliant JavaScript/WASM execution runtime. It does not provide absolute hardware attestation or proof of human presence.
---
## 2. Non-Goals (What ChronoSeal is NOT)
1. **Proof of Humanity:** ChronoSeal does not check if the user is a human. A headless browser running with standard input event automation will pass verification if it runs the WASM runtime correctly.
2. **Anti-Debugging/Enclave Security:** ChronoSeal does not run inside a secure hardware enclave on the client. An attacker has complete control of the client wasm environment, memory, and key storage.
3. **Perfect Browser Verification:** ChronoSeal cannot guarantee the client is a real Chrome/Firefox browser. It guarantees that the client maintains the state chain and executes the math VM program.
---
## 3. Threat Matrix & Attacker Cost Model
* **Commodity HTTP Clients (Python `requests`, `curl`):** *Blocked.* Attackers cannot sign payloads, run the mathematical VM, or maintain the stateful BLAKE3 hash chain.
* **Headless Automation (Puppeteer, Playwright):** *Partially Contained.* The automation script must execute the full browser environment, load the WASM module, feed valid parameters, and generate realistic mouse movement coordinates. This imposes significantly higher CPU and resource overhead on the attacker.
* **Custom WASM Emulators:** *Raised Cost.* A determined reverse engineer can extract the WASM module and build a custom state runner in Node.js or Go. ChronoSeal counters this by using a stateful **Synthetic Gene Mutation Engine**, where the state vector mutations are governed dynamically by the server, requiring the emulator to replicate the entire mutation spec.
---
## 4. Key Invariants
1. **Chain Continuity:** A session state cannot bifurcate. Every heartbeat must advance the state head using the latest salt.
2. **VM Parity:** Stack state must exactly match the execution output of the server's issued opcode sequence.
3. **Dynamic Challenges:** Client gene updates must match the server-issued mutation program.
+394
View File
@@ -0,0 +1,394 @@
# ChronoSeal Testing Strategy
ChronoSeal maintains a rigorous, security-first test suite focused on cryptographic correctness, deterministic server ↔ WASM parity, mutation engine integrity, replay resistance, tampering detection, behavioral validation, and storage reliability.
As of **v0.6.1**, the project contains **89 passing tests** across the server, WASM, and shared protocol crates.
| Crate | Tests |
| ------------------- | -----: |
| `chronoseal-server` | 30 |
| `chronoseal-wasm` | 24 |
| `shared` | 35 |
| **Total** | **89** |
---
## Test Philosophy
ChronoSeal testing prioritizes:
* **Security invariants** over raw coverage metrics
* **Deterministic parity** between server and browser WASM runtimes
* **Negative-path testing** (tampering, replay, malformed input, edge cases)
* **Fuzz-style and randomized testing** for mutation logic
* **Performance regression detection**
* **Long-term protocol stability**
Particular emphasis is placed on ensuring that browser-side WASM execution produces identical results to server-side validation.
---
# Test Categories
## 1. Configuration & CLI
Configuration tests verify:
* Database backend selection
* TOML configuration parsing
* Command-line override behavior
* Default configuration values
* Runtime initialization logic
Supported backends include:
* `sqlite-in-memory`
* `sqlite-in-disk`
* `valkey`
Example tests:
```text
test_apply_run_args_overrides_db_type
test_default_db_type_is_sqlite_in_memory
test_toml_parses_db_type_kebab_case
test_init_db_pool_sqlite_in_memory
test_init_db_pool_sqlite_in_disk
test_init_db_pool_valkey_compat_mode
```
---
## 2. Session Lifecycle & Verification
Session tests validate:
* Session creation
* Public key validation
* Expiration handling
* Replay attack prevention
* Mutation step enforcement
* Commitment verification
* Long-running deterministic parity
Example tests:
```text
test_create_session_rejects_invalid_public_key_length
test_expired_session_is_rejected
test_replay_attack_is_rejected
test_mutation_step_mismatch_is_rejected
test_mutation_commitment_tamper_is_rejected
test_session_lifecycle_and_verification
test_repeated_simulation_keeps_server_and_client_commitments_equal
test_deterministic_server_client_parity_across_many_heartbeats
```
---
## 3. Mutation Engine (Core Focus)
The Synthetic Gene Mutation Engine is one of the most security-critical components in ChronoSeal.
Testing focuses on:
* Deterministic server/client parity
* Mutation order execution
* Gene state integrity
* Preview → Commit → Discard lifecycle
* Randomized mutation programs
* Edge-case validation
* Performance regression detection
Example tests:
```text
test_server_client_parity_across_random_orders
test_generate_order_is_deterministic_for_seeded_rng
test_invalid_positions_wrap_deterministically
test_mutation_chain
test_fuzz_style_random_program_bytes_do_not_diverge
test_performance_smoke_mutation_execution
```
---
## 4. Heartbeat Handler
Heartbeat validation tests verify:
* Successful state advancement
* Silent rejection behavior
* Commitment validation
* Rate limiting
* Next-state mutation generation
Example tests:
```text
test_handler_success_returns_next_mutation_fields
test_handler_tampered_commitment_is_silent_failure
test_handler_rate_limit_returns_no_mutation_data
```
---
## 5. Trust & Behavioral Validation
Behavioral validation tests verify:
* Minimum mouse activity
* Minimum movement distance
* Pause detection
* Speed thresholds
* Optional activity requirements
* Fingerprint-related validation paths
Example tests:
```text
test_validate_mouse_success
test_validate_mouse_insufficient_events
test_validate_mouse_insufficient_distance
test_validate_mouse_too_fast
test_validate_mouse_no_pauses
test_validate_mouse_require_activity_toggle
```
---
## 6. Storage Layer
Storage tests verify:
* SQLite in-memory operation
* SQLite disk-backed operation
* Valkey compatibility mode
* Session CRUD behavior
* Expiration cleanup
* Runtime statistics reporting
These tests ensure storage implementations remain interchangeable without affecting protocol behavior.
---
## 7. VM Core
The VM core is tested extensively across both WASM and shared crates.
Coverage includes:
* ADD
* SUB
* MUL
* XOR
* AND
* OR
* NOT
* HASH
* ROT
* PUSH
Edge cases include:
* Stack underflow
* Truncated instructions
* Unknown opcodes
* Wrapping arithmetic
* Invalid instruction streams
Example tests:
```text
test_add
test_add_wrapping
test_sub
test_sub_wrapping
test_mul
test_hash
test_underflow_binary
test_underflow_unary
test_incomplete_push
test_rejects_unknown_opcode
test_rejects_truncated_instruction
```
---
# Server Test Coverage (`chronoseal-server`)
The server crate currently contains **30 tests** covering:
* Configuration
* Runtime initialization
* Session management
* Heartbeat validation
* Rate limiting
* Trust validation
The server tests focus heavily on protocol enforcement and security validation.
---
# WASM Test Coverage (`chronoseal-wasm`)
The WASM crate currently contains **24 tests** covering:
* VM execution
* Browser-side mutation lifecycle
* Gene initialization
* Mutation preview
* Mutation commit/discard behavior
* Deterministic parity with shared logic
Example tests:
```text
test_preview_commitment_matches_shared_engine
test_commit_applies_preview
test_discard_preview_keeps_committed_state
test_table_driven_parity_across_many_generated_orders
```
These tests ensure browser-generated commitments remain consistent with server expectations.
---
# Shared Crate Coverage (`shared`)
The shared crate currently contains **35 tests** and represents the core protocol implementation used by both server and browser runtimes.
Coverage includes:
### Synthetic Gene Engine
```text
test_new_state_with_default_size
test_new_state_rejects_invalid_sizes
test_commitment_changes_when_gene_or_environment_changes
test_encode_decode_environment_roundtrip
test_table_driven_randomized_environment_roundtrip
```
### Mutation Engine
```text
test_opcode_insert
test_opcode_delete
test_opcode_mutate_point
test_opcode_apply_mutagen
test_opcode_finalize_gene_hash
test_mutation_chain
```
### Validation & Hardening
```text
test_rejects_stack_underflow
test_rejects_truncated_instruction
test_rejects_unknown_opcode
test_zero_length_gene_is_rejected
```
### Deterministic Parity
```text
test_server_client_parity_across_random_orders
test_generate_order_is_deterministic_for_seeded_rng
test_invalid_positions_wrap_deterministically
```
### Fuzz & Regression Testing
```text
test_fuzz_style_random_program_bytes_do_not_diverge
test_performance_smoke_mutation_execution
```
---
# Running the Test Suite
Run the full workspace:
```bash
cargo test --workspace
```
Run individual crates:
```bash
cargo test -p shared
cargo test -p chronoseal-wasm
cargo test -p chronoseal-server
```
Display test output:
```bash
cargo test -- --nocapture
```
---
# Critical Security Tests
The following tests protect ChronoSeal's core protocol guarantees and should be treated as **release-blocking** if they fail:
```text
test_mutation_commitment_tamper_is_rejected
test_replay_attack_is_rejected
test_handler_tampered_commitment_is_silent_failure
test_server_client_parity_across_random_orders
test_deterministic_server_client_parity_across_many_heartbeats
test_fuzz_style_random_program_bytes_do_not_diverge
```
These tests directly validate resistance to replay attacks, protocol divergence, mutation tampering, and commitment forgery.
---
# Contributing New Tests
When adding new functionality:
1. Prefer placing protocol logic tests in `shared/`
2. Ensure server ↔ WASM parity is validated
3. Include negative-path test cases
4. Add randomized testing where appropriate
5. Update this document when introducing major new categories
---
# Future Improvements
Planned enhancements include:
* Property-based testing using `proptest`
* Browser-driven end-to-end integration tests
* Valkey concurrency and failover testing
* Automated benchmark execution in CI
* Expanded mutation-engine fuzzing
* CI-enforced performance regression thresholds
---
# Conclusion
ChronoSeal's testing strategy is centered on preserving deterministic behavior, cryptographic correctness, and protocol integrity.
The current suite of **89 tests** provides broad coverage across:
* Session security
* Heartbeat validation
* Mutation engine correctness
* Deterministic server/WASM parity
* Trust validation
* Storage abstraction
* Replay resistance
* Protocol hardening
Maintaining and expanding this test suite remains a core project priority as ChronoSeal evolves.
**Last Updated:** May 2026 (v0.6.1)
+56
View File
@@ -0,0 +1,56 @@
# ChronoSeal Debugging & Failure Mode Guide (WHY_IT_FAILS)
This document provides a technical diagnostic reference for developers, operators, and integration security teams. It explains why a client heartbeat or session initialization fails verification, and how to debug desynchronization issues.
---
## 1. Silent Rejections vs. HTTP Failures
To deny attackers a feedback oracle, the ChronoSeal heartbeat endpoint (`POST /hb`) always returns HTTP status `200 OK` with `{"status": "ok"}` on semantic verification failures.
* **Successful Attestation:** The JSON response contains the rotated next state information: `next_salt`, `next_mutation_step`, and `next_mutation_order_b64`.
* **Silently Rejected Attestation:** The JSON response *omits* these three fields. The client is expected to roll back the state preview and retry.
---
## 2. Common Verification Failure Modes
### A. Clock Drift (`TimestampDrift`)
* **Error Cause:** The client machine's local system time differs from the server's time by more than the configured `max_timestamp_drift_ms` (default 30 seconds).
* **Diagnostic Signal:** The `/hb` response omits next state parameters.
* **Remediation:** Synchronize both client and server clocks using NTP (Network Time Protocol). On the client, use NTP-synced system clocks or query server timestamp headers during initialization to compute a local clock offset.
### B. Replay Attempts / Out-of-Sequence (`ChainBroken`)
* **Error Cause:** The request `prev_hash` does not match the server-stored `last_hash` for the session.
* **Root Causes:**
1. The client replayed a previously captured heartbeat payload.
2. The client lost the network response containing the rotated next state parameters and retried with stale state.
3. A concurrent request succeeded first, updating the session's hash state.
* **Remediation:** If network issues cause packet loss, the client must discard the session and initiate a new `/init` handshake. Heartbeats cannot be replayed or resumed from a historical state.
### C. Signature Failures (`Signature`)
* **Error Cause:** The Ed25519 signature over the canonical JSON payload is invalid.
* **Root Causes:**
1. The client signed a payload that differed in ordering or format from the server's canonical serialization. (Ensure key sorting matches alphabetically: `entropyData`, `fingerprint`, `geneCommitment`, `mutationStep`, `prevHash`, `sessionId`, `stackState`, `timestamp`).
2. Different platform engines formatted floats or large numbers differently.
3. The public key registered during `/init` does not match the signing key.
* **Remediation:** Ensure both frontend and backend use strict canonical serializations (BTreeMap alphabetically sorted keys).
### D. VM Stack State Mismatch (`VmStackMismatch`)
* **Error Cause:** The client's submitted `stack_state` (VM stack and instruction pointer `ip`) does not match the server-side re-execution of the session's random math program.
* **Root Causes:**
1. An automated client bypassed the VM bytecode interpreter.
2. The client VM interpreter diverged mathematically (e.g. word size wrapping or logical op mismatches).
* **Remediation:** Check the VM interpreter implementation parity between the client wasm and `shared::vm`.
### E. Mutation Commitment Mismatch (`MutationCommitmentMismatch`)
* **Error Cause:** The client's computed `gene_commitment` does not match the server-applied gene mutation.
* **Root Causes:**
1. The client used a different number of `mutation_rounds` than the server config.
2. The mutation order execution logic diverged.
* **Remediation:** Verify that the client wasm correctly parsed `mutation_rounds` from `/init` and passed it to the generator.
### F. Rate Limiting (`RateLimiter`)
* **Error Cause:** The client submitted more requests than allowed by the server's rate-limiting config (e.g., `rate_limit_count` per `rate_limit_window_secs`).
* **Diagnostic Signal:** The server returns `200 OK` with `{"status": "ok"}` but no next state data.
* **Remediation:** Reduce heartbeat frequency or adjust rate limit parameters in the daemon configuration.
+41
View File
@@ -0,0 +1,41 @@
# ChronoSeal Third-Party Wrapper & Integration Guide (WRAPPER_GUIDE)
This document provides stable guidance for developers building third-party integration wrappers, clients, or SDKs around the `chronoseald` daemon.
---
## 1. Public Contract & Stability Guarantees
As a protocol-first Unix daemon, `chronoseald` guarantees stability on the public network interface.
### Guaranteed Stable
* **Endpoints:** `POST /init` and `POST /hb`.
* **JSON Fields:** The structure and naming of request and response keys.
* **VM Instruction Set:** The behavior and encoding of the 10 core VM opcodes (`0x00`..=`0x09`).
* **Signature Serialization:** Alphabetical key-sorting rules using `BTreeMap` serialization.
* **Hash Progression:** Blake3 chain folding rules.
### Private (Unstable / Subject to Change)
* **Database Engines & Schemas:** SQLite table structure, Valkey key formatting, and indexes.
* **Daemon CLI Flags:** Internal metrics query formats.
* **Memory Structures:** Thread boundaries, session caches, and synchronization locks.
---
## 2. API Versioning & Deprecation Policy
* **Version Format:** API endpoints do not contain version prefixes (e.g., `/v1/hb`). Instead, protocol versioning is coupled to the daemon release version.
* **Breaking Protocol Changes:** Any change to the core hash function (Blake3) or the VM instruction set will trigger a major release (e.g., `v2.0.0`).
* **Deprecation Cycle:** Deprecated features will be supported for at least one minor release cycle, documented in `docs/PROTOCOL_STABILITY.md`.
---
## 3. Reference Implementation Steps for Wrappers
To build a client-side wrapper or application adapter for `chronoseald`:
1. **Handshake:** Send `POST /init` with the hex-encoded Ed25519 public key. Save the returned `session_id`, `salt`, `opcodes_b64`, and `mutation_order_b64`.
2. **VM Execution:** Run the math VM program (decoded from `opcodes_b64`) using the client wasm runtime to get the target `stack_state`.
3. **Gene Mutation:** Decode `mutation_order_b64`, apply the mutation steps to the local gene buffer, and compute the new commitment hash.
4. **Signing:** Build the canonical alphabetical JSON message, sign it, and send `POST /hb`.
5. **Chain Advancement:** On success, extract `next_salt` and `next_mutation_order_b64` to prepare the next heartbeat request.
+596
View File
@@ -0,0 +1,596 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "arbitrary"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1"
[[package]]
name = "arrayref"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb"
[[package]]
name = "arrayvec"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
[[package]]
name = "base64"
version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "base64ct"
version = "1.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06"
[[package]]
name = "blake3"
version = "1.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0aa83c34e62843d924f905e0f5c866eb1dd6545fc4d719e803d9ba6030371fce"
dependencies = [
"arrayref",
"arrayvec",
"cc",
"cfg-if",
"constant_time_eq",
"cpufeatures 0.3.0",
]
[[package]]
name = "block-buffer"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
dependencies = [
"generic-array",
]
[[package]]
name = "cc"
version = "1.2.63"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "556e016178bb5662a08681bbe0f00f8e17631781a4dfc8c45e466e4b185ec27f"
dependencies = [
"find-msvc-tools",
"jobserver",
"libc",
"shlex",
]
[[package]]
name = "cfg-if"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "chronoseal-fuzz"
version = "0.0.0"
dependencies = [
"libfuzzer-sys",
"serde_json",
"shared",
]
[[package]]
name = "const-oid"
version = "0.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
[[package]]
name = "constant_time_eq"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b"
[[package]]
name = "cpufeatures"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
dependencies = [
"libc",
]
[[package]]
name = "cpufeatures"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201"
dependencies = [
"libc",
]
[[package]]
name = "crypto-common"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
dependencies = [
"generic-array",
"typenum",
]
[[package]]
name = "curve25519-dalek"
version = "4.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be"
dependencies = [
"cfg-if",
"cpufeatures 0.2.17",
"curve25519-dalek-derive",
"digest",
"fiat-crypto",
"rustc_version",
"subtle",
"zeroize",
]
[[package]]
name = "curve25519-dalek-derive"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "der"
version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb"
dependencies = [
"const-oid",
"zeroize",
]
[[package]]
name = "digest"
version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
"block-buffer",
"crypto-common",
]
[[package]]
name = "ed25519"
version = "2.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53"
dependencies = [
"pkcs8",
"signature",
]
[[package]]
name = "ed25519-dalek"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9"
dependencies = [
"curve25519-dalek",
"ed25519",
"rand_core",
"serde",
"sha2",
"subtle",
"zeroize",
]
[[package]]
name = "fiat-crypto"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d"
[[package]]
name = "find-msvc-tools"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
[[package]]
name = "generic-array"
version = "0.14.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
dependencies = [
"typenum",
"version_check",
]
[[package]]
name = "getrandom"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "getrandom"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
dependencies = [
"cfg-if",
"libc",
"r-efi",
"wasip2",
]
[[package]]
name = "hex"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "itoa"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
[[package]]
name = "jobserver"
version = "0.1.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33"
dependencies = [
"getrandom 0.3.4",
"libc",
]
[[package]]
name = "libc"
version = "0.2.186"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
[[package]]
name = "libfuzzer-sys"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f12a681b7dd8ce12bff52488013ba614b869148d54dd79836ab85aafdd53f08d"
dependencies = [
"arbitrary",
"cc",
]
[[package]]
name = "memchr"
version = "2.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8"
[[package]]
name = "once_cell"
version = "1.21.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
[[package]]
name = "pin-project-lite"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
[[package]]
name = "pkcs8"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7"
dependencies = [
"der",
"spki",
]
[[package]]
name = "ppv-lite86"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
dependencies = [
"zerocopy",
]
[[package]]
name = "proc-macro2"
version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
dependencies = [
"proc-macro2",
]
[[package]]
name = "r-efi"
version = "5.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
[[package]]
name = "rand"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a"
dependencies = [
"libc",
"rand_chacha",
"rand_core",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom 0.2.17",
]
[[package]]
name = "rustc_version"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
dependencies = [
"semver",
]
[[package]]
name = "semver"
version = "1.0.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
[[package]]
name = "serde"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
dependencies = [
"serde_core",
"serde_derive",
]
[[package]]
name = "serde_core"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.150"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9"
dependencies = [
"itoa",
"memchr",
"serde",
"serde_core",
"zmij",
]
[[package]]
name = "sha2"
version = "0.10.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
dependencies = [
"cfg-if",
"cpufeatures 0.2.17",
"digest",
]
[[package]]
name = "shared"
version = "0.6.0"
dependencies = [
"base64",
"blake3",
"ed25519-dalek",
"hex",
"rand",
"serde",
"serde_json",
"tracing",
]
[[package]]
name = "shlex"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
[[package]]
name = "signature"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
dependencies = [
"rand_core",
]
[[package]]
name = "spki"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d"
dependencies = [
"base64ct",
"der",
]
[[package]]
name = "subtle"
version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]]
name = "syn"
version = "2.0.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "tracing"
version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
dependencies = [
"pin-project-lite",
"tracing-attributes",
"tracing-core",
]
[[package]]
name = "tracing-attributes"
version = "0.1.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tracing-core"
version = "0.1.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
dependencies = [
"once_cell",
]
[[package]]
name = "typenum"
version = "1.20.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"
[[package]]
name = "unicode-ident"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "version_check"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "wasi"
version = "0.11.1+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]]
name = "wasip2"
version = "1.0.3+wasi-0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6"
dependencies = [
"wit-bindgen",
]
[[package]]
name = "wit-bindgen"
version = "0.57.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
[[package]]
name = "zerocopy"
version = "0.8.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b065d4f0e55f82fae73202e189638116a87c55ab6b8e6c2721e13dd9d854ad1"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b631b19d36a892ab55420c92dbc83ccd79274f25be714855d3074aa71cab639"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "zeroize"
version = "1.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
[[package]]
name = "zmij"
version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
+30
View File
@@ -0,0 +1,30 @@
[package]
name = "chronoseal-fuzz"
version = "0.0.0"
publish = false
edition = "2021"
[dependencies]
libfuzzer-sys = "0.4"
shared = { path = "../shared" }
serde_json = "1"
[workspace]
[[bin]]
name = "vm"
path = "fuzz_targets/vm.rs"
test = false
doc = false
[[bin]]
name = "protocol"
path = "fuzz_targets/protocol.rs"
test = false
doc = false
[[bin]]
name = "environment"
path = "fuzz_targets/environment.rs"
test = false
doc = false
+6
View File
@@ -0,0 +1,6 @@
#![no_main]
use libfuzzer_sys::fuzz_target;
fuzz_target!(|data: &[u8]| {
let _ = shared::gene::decode_environment(data);
});
+9
View File
@@ -0,0 +1,9 @@
#![no_main]
use libfuzzer_sys::fuzz_target;
use shared::protocol::HeartbeatRequest;
fuzz_target!(|data: &[u8]| {
if let Ok(s) = std::str::from_utf8(data) {
let _: Result<HeartbeatRequest, _> = serde_json::from_str(s);
}
});
+6
View File
@@ -0,0 +1,6 @@
#![no_main]
use libfuzzer_sys::fuzz_target;
fuzz_target!(|data: &[u8]| {
let _ = shared::vm::execute(data);
});
+67
View File
@@ -0,0 +1,67 @@
use crate::protocol::StackState;
/// Executes a raw VM mathematical instruction program bytecode slice.
///
/// This implements the mathematical stack machine interpreter used by the client
/// to generate the attestation stack state.
///
/// # Arguments
/// * `program` - The raw VM instruction program bytecode.
pub fn execute(program: &[u8]) -> StackState {
let mut stack: Vec<u32> = Vec::new();
let mut ip: usize = 0;
while ip < program.len() {
let op = program[ip];
ip += 1;
match op {
0x00 => {
if ip + 4 > program.len() {
break;
}
let val = u32::from_le_bytes([
program[ip],
program[ip + 1],
program[ip + 2],
program[ip + 3],
]);
ip += 4;
stack.push(val);
}
0x01..=0x07 => {
if stack.len() < 2 {
break;
}
let b = stack.pop().unwrap();
let a = stack.pop().unwrap();
let r = match op {
0x01 => a.wrapping_add(b),
0x02 => a.wrapping_sub(b),
0x03 => a.wrapping_mul(b),
0x04 => a ^ b,
0x05 => a & b,
0x06 => a | b,
0x07 => a.rotate_left(b % 32),
_ => unreachable!(),
};
stack.push(r);
}
0x08 => {
if stack.is_empty() {
break;
}
let a = stack.pop().unwrap();
stack.push(!a);
}
0x09 => {
let r = crate::hashing::hash_stack(&stack);
stack.clear();
stack.push(r);
}
_ => break,
}
}
StackState {
stack,
ip: ip as u16,
}
}
+17
View File
@@ -0,0 +1,17 @@
use proptest::prelude::*;
proptest! {
#[test]
fn test_vm_execute_never_panics(ref program in any::<Vec<u8>>()) {
// VM execution should be totally robust and never panic on any random input stream.
let state = shared::vm::execute(program);
// The instruction pointer (ip) should not exceed the program length
assert!(state.ip as usize <= program.len());
}
#[test]
fn test_gene_environment_roundtrip_never_panics(ref data in any::<Vec<u8>>()) {
// Try to decode random bytes. It should either succeed or fail gracefully, never panic.
let _ = shared::gene::decode_environment(data);
}
}