-
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcryptoverif.rs
More file actions
292 lines (266 loc) · 9.81 KB
/
Copy pathcryptoverif.rs
File metadata and controls
292 lines (266 loc) · 9.81 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
// SPDX-FileCopyrightText: 2026 ECHIDNA Project Team
// SPDX-License-Identifier: MPL-2.0
//! CryptoVerif backend — automatic prover for cryptographic protocols
//! in the computational model.
//!
//! CryptoVerif is the **automatic** counterpart to EasyCrypt's
//! interactive proof assistant: it produces concrete-security proofs
//! by automated game-hopping using a built-in indistinguishability
//! library. It was used to prove the formal security of TLS 1.3,
//! Signal, WireGuard, and Kerberos.
//!
//! ## Why this backend exists
//!
//! EasyCrypt produces interactive proofs with concrete-security
//! bounds. CryptoVerif produces *automated* proofs in the same model.
//! Together they cover the computational-crypto verification space.
//! ECHIDNA already has Tamarin and ProVerif (symbolic) — adding both
//! computational tools gives us the four-corner crypto-verification
//! matrix: symbolic-automatic (ProVerif), symbolic-interactive
//! (Tamarin), computational-interactive (EasyCrypt),
//! computational-automatic (CryptoVerif).
//!
//! ## Input format
//!
//! `.ocv` — CryptoVerif source (recommended modern syntax).
//! `.cv` — legacy format (still supported by upstream).
//!
//! ## CLI invocation
//!
//! `cryptoverif -in <ocv-file> -out <result-dir>` for full automation.
//! `cryptoverif -interactive` for hand-driven game-hopping.
//!
//! ## Output parsing
//!
//! CryptoVerif emits per-game status with a final verdict line:
//!
//! - `RESULT Proved <event-or-property>`
//! - `RESULT Could not prove ...`
//! - `Cannot prove ...` (failure mid-script)
//!
//! ## Integration tier
//!
//! Tier-5l / Phase-4. Trust tier 3 (small game-hopping engine,
//! peer-reviewed, used for TLS 1.3 / Signal proofs). Complexity 4
//! (probabilistic process calculi + indistinguishability library +
//! cryptographic-game search).
#![allow(dead_code)]
use anyhow::{Context, Result};
use async_trait::async_trait;
use std::path::PathBuf;
use std::process::Stdio;
use tokio::io::AsyncWriteExt;
use tokio::process::Command;
use super::{ProverBackend, ProverConfig, ProverKind};
use crate::core::{Goal, ProofState, Tactic, TacticResult, Term};
/// CryptoVerif backend for automated computational crypto proofs.
pub struct CryptoVerifBackend {
config: ProverConfig,
}
impl CryptoVerifBackend {
pub fn new(config: ProverConfig) -> Self {
CryptoVerifBackend { config }
}
/// Render a `ProofState` into a minimal CryptoVerif `.ocv` file.
///
/// Each axiom is emitted as a `def` declaration; the goal is
/// emitted as the `query` line that drives the prover.
fn to_ocv(state: &ProofState) -> String {
let mut ocv = String::new();
ocv.push_str("(* SPDX-License-Identifier: MPL-2.0 *)\n");
ocv.push_str("(* CryptoVerif input synthesised by ECHIDNA *)\n\n");
for axiom in &state.context.axioms {
ocv.push_str(&format!("def {}.\n", axiom));
}
if let Some(goal) = state.goals.first() {
ocv.push_str(&format!("\nquery {}.\n", goal.target));
}
ocv.push_str("\nprocess 0\n");
ocv
}
/// Parse CryptoVerif's stdout to a boolean verdict.
fn parse_result(output: &str) -> Result<bool> {
let lower = output.to_ascii_lowercase();
let positive = [
"result proved",
"all queries proved",
"successfully proved",
"[ok] proof complete",
];
let negative = [
"could not prove",
"cannot prove",
"result not proved",
"result rand_failure",
"[error]",
];
if positive.iter().any(|m| lower.contains(m)) {
return Ok(true);
}
if negative.iter().any(|m| lower.contains(m)) {
return Ok(false);
}
Err(anyhow::anyhow!(
"CryptoVerif output inconclusive: {}",
output.lines().take(10).collect::<Vec<_>>().join("\n")
))
}
}
#[async_trait]
impl ProverBackend for CryptoVerifBackend {
fn kind(&self) -> ProverKind {
ProverKind::CryptoVerif
}
async fn version(&self) -> Result<String> {
let output = Command::new(&self.config.executable)
.arg("-version")
.output()
.await
.context("Failed to run cryptoverif -version")?;
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
let version = if !stdout.is_empty() {
stdout.lines().next().unwrap_or("cryptoverif").to_string()
} else {
stderr.lines().next().unwrap_or("cryptoverif").to_string()
};
Ok(version.trim().to_string())
}
async fn parse_file(&self, path: PathBuf) -> Result<ProofState> {
let content = super::bounded_read_proof_file(&path)
.await
.context("Failed to read .ocv file")?;
let mut state = self.parse_string(&content).await?;
state.metadata.insert(
"source_path".to_string(),
serde_json::Value::String(path.to_string_lossy().into_owned()),
);
Ok(state)
}
async fn parse_string(&self, content: &str) -> Result<ProofState> {
let mut state = ProofState::default();
state.metadata.insert(
"ocv_source".to_string(),
serde_json::Value::String(content.to_string()),
);
for line in content.lines() {
let trimmed = line.trim();
if trimmed.is_empty() || trimmed.starts_with("(*") {
continue;
}
if let Some(rest) = trimmed.strip_prefix("def ") {
let body = rest.trim_end_matches('.').trim();
if !body.is_empty() {
state.context.axioms.push(body.to_string());
}
} else if let Some(rest) = trimmed.strip_prefix("query ") {
let body = rest.trim_end_matches('.').trim();
state.goals.push(Goal {
id: format!("goal_{}", state.goals.len()),
target: Term::Const(body.to_string()),
hypotheses: vec![],
});
}
}
Ok(state)
}
async fn apply_tactic(&self, _state: &ProofState, _tactic: &Tactic) -> Result<TacticResult> {
Err(anyhow::anyhow!(
"CryptoVerif is automation-first; interactive game-hopping is out of band"
))
}
async fn verify_proof(&self, state: &ProofState) -> Result<bool> {
let ocv = Self::to_ocv(state);
let mut child = Command::new(&self.config.executable)
.arg("-in")
.arg("/dev/stdin")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.context("Failed to spawn cryptoverif")?;
{
let stdin = child
.stdin
.as_mut()
.ok_or_else(|| anyhow::anyhow!("Failed to open cryptoverif stdin"))?;
stdin
.write_all(ocv.as_bytes())
.await
.context("Failed to write to cryptoverif stdin")?;
}
let output = child
.wait_with_output()
.await
.context("Failed to wait for cryptoverif")?;
let combined = format!(
"{}\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
Self::parse_result(&combined)
}
async fn export(&self, state: &ProofState) -> Result<String> {
Ok(Self::to_ocv(state))
}
async fn suggest_tactics(&self, state: &ProofState, limit: usize) -> Result<Vec<Tactic>> {
Ok(crate::provers::gnn_augment_tactics(&self.config, state, "cryptoverif", vec![], limit).await)
}
async fn search_theorems(&self, _pattern: &str) -> Result<Vec<String>> {
Ok(vec![])
}
fn config(&self) -> &ProverConfig {
&self.config
}
fn set_config(&mut self, config: ProverConfig) {
self.config = config;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cryptoverif_kind() {
let config = ProverConfig::default();
let backend = CryptoVerifBackend::new(config);
assert_eq!(backend.kind(), ProverKind::CryptoVerif);
}
#[test]
fn test_cryptoverif_to_ocv_emits_query() {
let mut state = ProofState::default();
state.context.axioms.push("MAC_secure".to_string());
state.goals.push(Goal {
id: "goal_0".to_string(),
target: Term::Const("event(authenticated(m)) ==> exists s. signed(s, m)".to_string()),
hypotheses: vec![],
});
let ocv = CryptoVerifBackend::to_ocv(&state);
assert!(ocv.contains("def MAC_secure"));
assert!(ocv.contains("query event(authenticated(m))"));
assert!(ocv.contains("process 0"));
}
#[test]
fn test_cryptoverif_parse_result_proved() {
assert!(CryptoVerifBackend::parse_result("RESULT Proved authentication").expect("parse"));
}
#[test]
fn test_cryptoverif_parse_result_could_not_prove() {
assert!(
!CryptoVerifBackend::parse_result("RESULT Could not prove confidentiality")
.expect("parse")
);
}
#[test]
fn test_cryptoverif_parse_result_silence_errors() {
assert!(CryptoVerifBackend::parse_result("warning: nothing").is_err());
}
#[tokio::test]
async fn test_cryptoverif_parse_string_extracts_def_and_query() {
let config = ProverConfig::default();
let backend = CryptoVerifBackend::new(config);
let ocv = "def MAC_secure.\nquery event(auth(m)) ==> sender(m).\n";
let state = backend.parse_string(ocv).await.expect("parse_string");
assert_eq!(state.context.axioms.len(), 1);
assert_eq!(state.goals.len(), 1);
}
}