3 Commits
Author SHA1 Message Date
thakares d965451d4f Add missing proptest dev dependency
Rust / build (push) Canceled after 0s
2026-05-30 20:48:46 +05:30
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
19 changed files with 2871 additions and 173 deletions

No files matched your search

Generated
+1036 -23
View File
File diff suppressed because it is too large. Load diff
+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.
+179 -150
View File
@@ -1,12 +1,8 @@
# ChronoSeal Testing Strategy and Suite # ChronoSeal Testing Strategy
This document describes the testing strategy for ChronoSeal and summarizes the test coverage included in v0.6.1. 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.
ChronoSeal is a security-focused system. Testing therefore prioritizes cryptographic correctness, deterministic execution, protocol integrity, and resistance to replay or tampering rather than simple line coverage. As of **v0.6.1**, the project contains **89 passing tests** across the server, WASM, and shared protocol crates.
## Overview
As of v0.6.1, the ChronoSeal workspace contains:
| Crate | Tests | | Crate | Tests |
| ------------------- | -----: | | ------------------- | -----: |
@@ -15,73 +11,67 @@ As of v0.6.1, the ChronoSeal workspace contains:
| `shared` | 35 | | `shared` | 35 |
| **Total** | **89** | | **Total** | **89** |
All tests pass successfully on the reference development environment. ---
## Testing Philosophy ## Test Philosophy
ChronoSeal testing focuses on: ChronoSeal testing prioritizes:
1. Cryptographic correctness * **Security invariants** over raw coverage metrics
2. Deterministic server ↔ WASM parity * **Deterministic parity** between server and browser WASM runtimes
3. Replay and tampering resistance * **Negative-path testing** (tampering, replay, malformed input, edge cases)
4. Mutation engine integrity * **Fuzz-style and randomized testing** for mutation logic
5. Negative-path validation * **Performance regression detection**
6. Storage reliability * **Long-term protocol stability**
7. Performance regression detection
Particular emphasis is placed on ensuring that browser-side WASM execution produces identical results to server-side validation. Particular emphasis is placed on ensuring that browser-side WASM execution produces identical results to server-side validation.
--- ---
# Server Test Coverage (`chronoseal-server`) # Test Categories
The server crate contains 30 tests covering configuration, runtime initialization, session lifecycle management, heartbeat validation, rate limiting, and behavioral trust checks. ## 1. Configuration & CLI
## Configuration
Configuration tests verify: Configuration tests verify:
* database type parsing * Database backend selection
* TOML configuration loading * TOML configuration parsing
* default value handling * Command-line override behavior
* command-line override behavior * Default configuration values
* Runtime initialization logic
Examples: Supported backends include:
* `sqlite-in-memory`
* `sqlite-in-disk`
* `valkey`
Example tests:
```text ```text
test_apply_run_args_overrides_db_type test_apply_run_args_overrides_db_type
test_default_db_type_is_sqlite_in_memory test_default_db_type_is_sqlite_in_memory
test_toml_parses_db_type_kebab_case test_toml_parses_db_type_kebab_case
```
## Runtime Initialization
Backend initialization tests verify:
* SQLite in-memory mode
* SQLite disk-backed mode
* Valkey compatibility mode
Examples:
```text
test_init_db_pool_sqlite_in_memory test_init_db_pool_sqlite_in_memory
test_init_db_pool_sqlite_in_disk test_init_db_pool_sqlite_in_disk
test_init_db_pool_valkey_compat_mode test_init_db_pool_valkey_compat_mode
``` ```
## Session Lifecycle and Security ---
## 2. Session Lifecycle & Verification
Session tests validate: Session tests validate:
* public key validation * Session creation
* session expiration * Public key validation
* replay attack prevention * Expiration handling
* mutation step enforcement * Replay attack prevention
* commitment verification * Mutation step enforcement
* long-running deterministic parity * Commitment verification
* Long-running deterministic parity
Examples: Example tests:
```text ```text
test_create_session_rejects_invalid_public_key_length test_create_session_rejects_invalid_public_key_length
@@ -90,18 +80,50 @@ test_replay_attack_is_rejected
test_mutation_step_mismatch_is_rejected test_mutation_step_mismatch_is_rejected
test_mutation_commitment_tamper_is_rejected test_mutation_commitment_tamper_is_rejected
test_session_lifecycle_and_verification test_session_lifecycle_and_verification
test_repeated_simulation_keeps_server_and_client_commitments_equal
test_deterministic_server_client_parity_across_many_heartbeats test_deterministic_server_client_parity_across_many_heartbeats
``` ```
## Heartbeat Validation ---
Heartbeat tests verify: ## 3. Mutation Engine (Core Focus)
* successful state advancement The Synthetic Gene Mutation Engine is one of the most security-critical components in ChronoSeal.
* silent rejection behavior
* rate limiting
Examples: 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 ```text
test_handler_success_returns_next_mutation_fields test_handler_success_returns_next_mutation_fields
@@ -109,17 +131,20 @@ test_handler_tampered_commitment_is_silent_failure
test_handler_rate_limit_returns_no_mutation_data test_handler_rate_limit_returns_no_mutation_data
``` ```
## Behavioral Trust Validation ---
Trust checks verify: ## 5. Trust & Behavioral Validation
* minimum mouse activity Behavioral validation tests verify:
* minimum distance traveled
* pause detection
* speed thresholds
* optional activity requirements
Examples: * Minimum mouse activity
* Minimum movement distance
* Pause detection
* Speed thresholds
* Optional activity requirements
* Fingerprint-related validation paths
Example tests:
```text ```text
test_validate_mouse_success test_validate_mouse_success
@@ -127,26 +152,31 @@ test_validate_mouse_insufficient_events
test_validate_mouse_insufficient_distance test_validate_mouse_insufficient_distance
test_validate_mouse_too_fast test_validate_mouse_too_fast
test_validate_mouse_no_pauses test_validate_mouse_no_pauses
``` test_validate_mouse_require_activity_toggle
## Rate Limiting
Examples:
```text
test_rate_limiter
test_rate_limiter_eviction
``` ```
--- ---
# WASM Test Coverage (`chronoseal-wasm`) ## 6. Storage Layer
The WASM crate contains 24 tests covering both the virtual machine and browser-side mutation lifecycle. Storage tests verify:
## Virtual Machine * SQLite in-memory operation
* SQLite disk-backed operation
* Valkey compatibility mode
* Session CRUD behavior
* Expiration cleanup
* Runtime statistics reporting
Opcode correctness is validated for: 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 * ADD
* SUB * SUB
@@ -161,11 +191,13 @@ Opcode correctness is validated for:
Edge cases include: Edge cases include:
* stack underflow * Stack underflow
* truncated instructions * Truncated instructions
* wrapping arithmetic * Unknown opcodes
* Wrapping arithmetic
* Invalid instruction streams
Examples: Example tests:
```text ```text
test_add test_add
@@ -177,33 +209,44 @@ test_hash
test_underflow_binary test_underflow_binary
test_underflow_unary test_underflow_unary
test_incomplete_push test_incomplete_push
test_rejects_unknown_opcode
test_rejects_truncated_instruction
``` ```
## Browser Mutation Lifecycle ---
The browser runtime tests: # Server Test Coverage (`chronoseal-server`)
* gene initialization The server crate currently contains **30 tests** covering:
* mutation preview
* mutation commit
* mutation discard
* commitment parity
Examples: * 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 ```text
test_init_gene_state_success
test_init_gene_state_rejects_zero
test_preview_commitment_matches_shared_engine test_preview_commitment_matches_shared_engine
test_commit_applies_preview test_commit_applies_preview
test_discard_preview_keeps_committed_state test_discard_preview_keeps_committed_state
```
## Deterministic Parity
Examples:
```text
test_table_driven_parity_across_many_generated_orders test_table_driven_parity_across_many_generated_orders
``` ```
@@ -213,21 +256,11 @@ These tests ensure browser-generated commitments remain consistent with server e
# Shared Crate Coverage (`shared`) # Shared Crate Coverage (`shared`)
The shared crate contains 35 tests and represents the core security-critical logic of ChronoSeal. The shared crate currently contains **35 tests** and represents the core protocol implementation used by both server and browser runtimes.
This crate receives the heaviest protocol-focused testing because it is shared by both server and WASM runtimes. Coverage includes:
## Synthetic Gene Engine ### Synthetic Gene Engine
Gene state tests validate:
* initialization rules
* environment encoding
* environment decoding
* commitment generation
* quantity management
Examples:
```text ```text
test_new_state_with_default_size test_new_state_with_default_size
@@ -237,30 +270,18 @@ test_encode_decode_environment_roundtrip
test_table_driven_randomized_environment_roundtrip test_table_driven_randomized_environment_roundtrip
``` ```
## Mutation Engine ### Mutation Engine
Mutation engine tests verify:
* deterministic execution
* mutation chains
* opcode correctness
* stack handling
* instruction validation
Examples:
```text ```text
test_mutation_chain
test_opcode_insert test_opcode_insert
test_opcode_delete test_opcode_delete
test_opcode_mutate_point test_opcode_mutate_point
test_opcode_apply_mutagen test_opcode_apply_mutagen
test_opcode_finalize_gene_hash test_opcode_finalize_gene_hash
test_mutation_chain
``` ```
## Validation and Hardening ### Validation & Hardening
Defensive validation tests include:
```text ```text
test_rejects_stack_underflow test_rejects_stack_underflow
@@ -269,9 +290,7 @@ test_rejects_unknown_opcode
test_zero_length_gene_is_rejected test_zero_length_gene_is_rejected
``` ```
## Deterministic Server ↔ WASM Parity ### Deterministic Parity
These are among the most important tests in the project:
```text ```text
test_server_client_parity_across_random_orders test_server_client_parity_across_random_orders
@@ -279,17 +298,13 @@ test_generate_order_is_deterministic_for_seeded_rng
test_invalid_positions_wrap_deterministically test_invalid_positions_wrap_deterministically
``` ```
## Fuzz and Regression Testing ### Fuzz & Regression Testing
Examples:
```text ```text
test_fuzz_style_random_program_bytes_do_not_diverge test_fuzz_style_random_program_bytes_do_not_diverge
test_performance_smoke_mutation_execution test_performance_smoke_mutation_execution
``` ```
These tests help detect behavioral divergence and unintended performance regressions.
--- ---
# Running the Test Suite # Running the Test Suite
@@ -303,12 +318,12 @@ cargo test --workspace
Run individual crates: Run individual crates:
```bash ```bash
cargo test -p chronoseal-server
cargo test -p chronoseal-wasm
cargo test -p shared cargo test -p shared
cargo test -p chronoseal-wasm
cargo test -p chronoseal-server
``` ```
Show test output: Display test output:
```bash ```bash
cargo test -- --nocapture cargo test -- --nocapture
@@ -318,7 +333,7 @@ cargo test -- --nocapture
# Critical Security Tests # Critical Security Tests
The following tests protect core ChronoSeal security guarantees: The following tests protect ChronoSeal's core protocol guarantees and should be treated as **release-blocking** if they fail:
```text ```text
test_mutation_commitment_tamper_is_rejected test_mutation_commitment_tamper_is_rejected
@@ -329,7 +344,19 @@ test_deterministic_server_client_parity_across_many_heartbeats
test_fuzz_style_random_program_bytes_do_not_diverge test_fuzz_style_random_program_bytes_do_not_diverge
``` ```
Any failure in these areas should be treated as a release-blocking issue. 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
--- ---
@@ -337,11 +364,11 @@ Any failure in these areas should be treated as a release-blocking issue.
Planned enhancements include: Planned enhancements include:
* property-based testing using `proptest` * Property-based testing using `proptest`
* browser-driven end-to-end integration tests * Browser-driven end-to-end integration tests
* Valkey concurrency testing * Valkey concurrency and failover testing
* automated benchmark execution * Automated benchmark execution in CI
* expanded mutation-engine fuzzing * Expanded mutation-engine fuzzing
* CI-enforced performance regression thresholds * CI-enforced performance regression thresholds
--- ---
@@ -350,16 +377,18 @@ Planned enhancements include:
ChronoSeal's testing strategy is centered on preserving deterministic behavior, cryptographic correctness, and protocol integrity. 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: The current suite of **89 tests** provides broad coverage across:
* session security * Session security
* heartbeat validation * Heartbeat validation
* mutation engine correctness * Mutation engine correctness
* deterministic server/WASM parity * Deterministic server/WASM parity
* trust validation * Trust validation
* storage abstraction * Storage abstraction
* replay resistance * Replay resistance
* Protocol hardening
As ChronoSeal evolves, expanding and strengthening this test suite remains a core project priority. Maintaining and expanding this test suite remains a core project priority as ChronoSeal evolves.
**Last Updated:** May 2026 (v0.6.1) **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);
});
+3
View File
@@ -12,3 +12,6 @@ base64 = "0.22"
rand = "0.8" rand = "0.8"
ed25519-dalek = { version = "2", features = ["rand_core"] } ed25519-dalek = { version = "2", features = ["rand_core"] }
tracing = "0.1" tracing = "0.1"
[dev-dependencies]
proptest = "1"
+1
View File
@@ -2,4 +2,5 @@ pub mod constants;
pub mod gene; pub mod gene;
pub mod hashing; pub mod hashing;
pub mod protocol; pub mod protocol;
pub mod vm;
pub mod vm_extensions; pub mod vm_extensions;
+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);
}
}