From 1ea8d4a82916b208cd5f12b2c96f8b36d530f349 Mon Sep 17 00:00:00 2001 From: RSoulatIOHK Date: Thu, 1 Oct 2026 08:53:02 +0200 Subject: [PATCH 1/7] feat: add Lean-blaster proof blocks and backend --- .gitignore | 3 + .sampo/changesets/lean-blaster-proofs.md | 19 ++ Cargo.lock | 16 ++ SPEC.md | 10 + crates/nash-ast/src/lib.rs | 23 +- crates/nash-can/src/environment.rs | 2 + crates/nash-can/src/error.rs | 6 + crates/nash-can/src/module.rs | 57 +++- crates/nash-cli/Cargo.toml | 1 + crates/nash-cli/src/cmd/mod.rs | 5 + crates/nash-cli/src/cmd/proof.rs | 211 +++++++++++++++ crates/nash-cli/src/cmd/test.rs | 2 +- crates/nash-codegen/Cargo.toml | 1 + .../src/build/tests/source/builtins.rs | 12 +- crates/nash-codegen/src/demand.rs | 1 + crates/nash-codegen/src/lib.rs | 1 + crates/nash-codegen/src/proofs.rs | 166 ++++++++++++ crates/nash-constrain/src/module.rs | 1 + crates/nash-driver/Cargo.toml | 1 + crates/nash-driver/base/src/Proof.nash | 58 ++++ crates/nash-driver/src/build.rs | 34 +++ crates/nash-driver/src/compile.rs | 2 + crates/nash-driver/tests/proofs.rs | 179 +++++++++++++ crates/nash-fmt/src/declarations.rs | 9 +- crates/nash-fmt/src/tests/semantic.rs | 1 + crates/nash-nitpick/src/check.rs | 2 +- crates/nash-parse/src/keyword.rs | 5 + crates/nash-parse/src/module.rs | 20 +- crates/nash-parse/src/tests_block.rs | 41 ++- crates/nash-proof/Cargo.toml | 19 ++ crates/nash-proof/src/lean.rs | 208 +++++++++++++++ crates/nash-proof/src/lib.rs | 52 ++++ crates/nash-proof/src/runner.rs | 249 ++++++++++++++++++ crates/nash-report/src/canonicalize.rs | 20 ++ crates/nash-solve/src/solve.rs | 38 +-- crates/nash-solve/src/solve/infer.rs | 21 +- crates/nash-source/src/lib.rs | 25 +- docs/overview.md | 7 + docs/proofs.md | 171 ++++++++++++ docs/syntax.md | 9 + examples/proofs/nash.jsonc | 1 + examples/proofs/src/Main.nash | 26 ++ 42 files changed, 1693 insertions(+), 42 deletions(-) create mode 100644 .sampo/changesets/lean-blaster-proofs.md create mode 100644 crates/nash-cli/src/cmd/proof.rs create mode 100644 crates/nash-codegen/src/proofs.rs create mode 100644 crates/nash-driver/base/src/Proof.nash create mode 100644 crates/nash-driver/tests/proofs.rs create mode 100644 crates/nash-proof/Cargo.toml create mode 100644 crates/nash-proof/src/lean.rs create mode 100644 crates/nash-proof/src/lib.rs create mode 100644 crates/nash-proof/src/runner.rs create mode 100644 docs/proofs.md create mode 100644 examples/proofs/nash.jsonc create mode 100644 examples/proofs/src/Main.nash diff --git a/.gitignore b/.gitignore index 2b163510..98d695a0 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,6 @@ target/ elm/ polarity/ scratch/ + +# Generated proof projects and solver transcripts +examples/proofs/build/ diff --git a/.sampo/changesets/lean-blaster-proofs.md b/.sampo/changesets/lean-blaster-proofs.md new file mode 100644 index 00000000..2fe06a84 --- /dev/null +++ b/.sampo/changesets/lean-blaster-proofs.md @@ -0,0 +1,19 @@ +--- +cargo/nash-source: minor +cargo/nash-ast: minor +cargo/nash-parse: minor +cargo/nash-can: minor +cargo/nash-constrain: minor +cargo/nash-solve: minor +cargo/nash-nitpick: patch +cargo/nash-report: minor +cargo/nash-fmt: minor +cargo/nash-codegen: minor +cargo/nash-driver: minor +cargo/nash-cli: minor +cargo/nash-proof: minor +--- + +Add module-local proof blocks and a Lean-blaster proof command over compiled UPLC, +with symbolic primitive and Cardano ledger domains, portable pinned Lean projects, +structured results, explicit SMT trust, and CEK execution limits with exhaustion treated as rejection. diff --git a/Cargo.lock b/Cargo.lock index c73bc07b..37536200 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1783,6 +1783,7 @@ dependencies = [ "nash-fmt", "nash-language-server", "nash-plutus", + "nash-proof", "nash-region", "nash-report", "nash-test", @@ -1814,6 +1815,7 @@ dependencies = [ "nash-nitpick", "nash-parse", "nash-plutus", + "nash-proof", "nash-region", "nash-solve", "nash-test", @@ -1889,6 +1891,7 @@ dependencies = [ "nash-nitpick", "nash-parse", "nash-plutus", + "nash-proof", "nash-region", "nash-report", "nash-solve", @@ -1982,6 +1985,19 @@ dependencies = [ "thiserror 2.0.21", ] +[[package]] +name = "nash-proof" +version = "0.1.0" +dependencies = [ + "hex", + "libc", + "nash-config", + "nash-source", + "serde", + "serde_json", + "thiserror 2.0.21", +] + [[package]] name = "nash-region" version = "0.3.0" diff --git a/SPEC.md b/SPEC.md index 5f040b6e..fa88d2dc 100644 --- a/SPEC.md +++ b/SPEC.md @@ -98,3 +98,13 @@ cargo fmt --all cargo clippy --all-targets --all-features -- -D warnings cargo insta test ``` + +## Optional Lean-blaster proof backend + +- [x] Private proof blocks with typed symbolic primitive and ledger domains +- [x] Compile property bodies through the standard UPLC backend +- [x] Portable pinned Lean project export and structured result protocol +- [x] Execution-limit rejection semantics and existential rejection witnesses +- [ ] Generic symbolic list/product/custom ADT domains +- [ ] Decode and replay SMT counterexamples in Nash +- [ ] Kernel-checkable reconstructed proofs diff --git a/crates/nash-ast/src/lib.rs b/crates/nash-ast/src/lib.rs index 123fd909..87c588a4 100644 --- a/crates/nash-ast/src/lib.rs +++ b/crates/nash-ast/src/lib.rs @@ -68,9 +68,9 @@ pub struct ViaBinder<'a> { pub generator: &'a Located>, } -#[derive(Debug)] pub struct Module<'a> { pub tests: &'a [Test<'a>], + pub proofs: &'a [Test<'a>], pub traits: &'a [&'a Located>], pub impls: &'a [&'a Located>], pub kind: ModuleKind, @@ -923,3 +923,24 @@ mod record_tests { assert!(transparent.record_fields().is_none()); } } + +impl std::fmt::Debug for Module<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut debug = f.debug_struct("Module"); + debug.field("tests", &self.tests); + if !self.proofs.is_empty() { + debug.field("proofs", &self.proofs); + } + debug.field("traits", &self.traits); + debug.field("impls", &self.impls); + debug.field("kind", &self.kind); + debug.field("name", &self.name); + debug.field("exports", &self.exports); + debug.field("docs", &self.docs); + debug.field("decls", &self.decls); + debug.field("unions", &self.unions); + debug.field("aliases", &self.aliases); + debug.field("binops", &self.binops); + debug.finish() + } +} diff --git a/crates/nash-can/src/environment.rs b/crates/nash-can/src/environment.rs index dd97c3a3..a8c47a5e 100644 --- a/crates/nash-can/src/environment.rs +++ b/crates/nash-can/src/environment.rs @@ -60,6 +60,8 @@ pub struct Tables<'a> { pub fields: BTreeMap, LabeledUnion<'a>>, /// Additional constructor visibility granted only by tests-block imports. pub test_fields: BTreeMap, LabeledUnion<'a>>, + /// Constructor visibility granted only by proof-block imports. + pub proof_fields: BTreeMap, LabeledUnion<'a>>, pub kinds: crate::kinds::KindEnv<'a>, pub traits: BTreeMap, &'a TraitInfo<'a>>, pub impls: ImplTable<'a>, diff --git a/crates/nash-can/src/error.rs b/crates/nash-can/src/error.rs index 03075ecc..be3f570f 100644 --- a/crates/nash-can/src/error.rs +++ b/crates/nash-can/src/error.rs @@ -35,6 +35,12 @@ pub struct PossibleNames<'a> { #[derive(Clone, Debug)] pub enum Error<'a> { + ProofDomain { + region: Region, + }, + ProofBudget { + region: Region, + }, DuplicateTest { name: &'a str, first: Region, diff --git a/crates/nash-can/src/module.rs b/crates/nash-can/src/module.rs index 41c458b6..47689700 100644 --- a/crates/nash-can/src/module.rs +++ b/crates/nash-can/src/module.rs @@ -155,7 +155,43 @@ pub fn canonicalize<'a>( } } } - let tests = canonicalize_tests(bump, &test_env, module.tests, &mut warnings)?; + let tests = canonicalize_tests(bump, &test_env, module.tests, false, &mut warnings)?; + let mut proof_env = env.clone(); + if let Some(tests) = module.proofs { + let defaults = crate::defaults::test_imports(bump, home, context.interfaces); + environment::foreign::add_imports(bump, &mut proof_env, context.interfaces, &defaults)?; + environment::foreign::add_imports(bump, &mut proof_env, context.interfaces, tests.imports)?; + // Local declarations take precedence over imported unqualified names, + // just as they do when the enclosing module's environment is built. + for (name, info) in &env.types { + if matches!(info, environment::Info::Specific(owner, _) if *owner == home) { + proof_env.types.insert(name, info.clone()); + } + } + for (name, info) in &env.ctors { + if matches!(info, environment::Info::Specific(owner, _) if *owner == home) { + proof_env.ctors.insert(name, info.clone()); + } + } + for (name, info) in &env.traits { + if matches!(info, environment::Info::Specific(owner, _) if *owner == home) { + proof_env.traits.insert(name, info.clone()); + } + } + for (name, var) in &env.vars { + if matches!( + var, + environment::Var::TopLevel(_) + | environment::Var::Method { + local_region: Some(_), + .. + } + ) { + proof_env.vars.insert(name, var.clone()); + } + } + } + let proofs = canonicalize_tests(bump, &proof_env, module.proofs, true, &mut warnings)?; let binops = canonicalize_binops(bump, &env, module.binops); let exports = canonicalize_exports(bump, module)?; if matches!(module.kind, nash_ast::ModuleKind::Validator(_)) @@ -169,6 +205,7 @@ pub fn canonicalize<'a>( let can_module = CanModule { tests, + proofs, traits, impls, kind: module.kind, @@ -186,6 +223,7 @@ pub fn canonicalize<'a>( .imports .iter() .chain(module.tests.into_iter().flat_map(|t| t.imports.iter())) + .chain(module.proofs.into_iter().flat_map(|t| t.imports.iter())) { let module_name = import.import.value; if !used_modules.contains(module_name) { @@ -199,6 +237,7 @@ pub fn canonicalize<'a>( let mut tables = crate::impls::tables(bump, context.interfaces, &can_module, &kind_env)?; tables.fields = environment::visible_fields(bump, &env); tables.test_fields = environment::visible_fields(bump, &test_env); + tables.proof_fields = environment::visible_fields(bump, &proof_env); Ok(CanResult { tables, module: can_module, @@ -210,6 +249,7 @@ fn canonicalize_tests<'a>( bump: &'a Bump, env: &Env<'a>, tests: Option<&nash_source::Tests<'a>>, + proof: bool, warnings: &mut Vec>, ) -> Result<&'a [nash_ast::Test<'a>], Vec>> { let Some(tests) = tests else { @@ -230,6 +270,11 @@ fn canonicalize_tests<'a>( let base = environment::Scope::new(env, None, &empty)?; let mut result = Vec::new(); for test in tests.tests { + if proof && test.value.budget.is_some() { + return Err(vec![Error::ProofBudget { + region: test.region, + }]); + } let (binders, block) = match test.value.body { nash_source::TestBody::Unit(body) => (&[][..], body), nash_source::TestBody::Prop { binders, body } => (binders, body), @@ -246,6 +291,14 @@ fn canonicalize_tests<'a>( &mut expression::FreeLocals::new(), warnings, )?; + if proof + && !matches!(generator.value, nash_ast::Expr::VarForeign { reference, .. } + if reference.home.package == Some(nash_ast::primitives::BASE) && reference.home.name == "Proof") + { + return Err(vec![Error::ProofDomain { + region: binder.value.generator.region, + }]); + } can_binders.push(nash_ast::ViaBinder { pattern, generator }); } let scope = base.add_locals(&bindings)?; @@ -1168,7 +1221,7 @@ fn canonicalize_binops<'a>( fn collect_used_modules<'a>(module: &CanModule<'a>) -> BTreeSet<&'a str> { let mut used = BTreeSet::new(); let home = module.name; - for test in module.tests { + for test in module.tests.iter().chain(module.proofs) { collect_from_expr(&test.body.value, home, &mut used); for binder in test.binders { collect_from_expr(&binder.generator.value, home, &mut used); diff --git a/crates/nash-cli/Cargo.toml b/crates/nash-cli/Cargo.toml index 4c863556..a0a4da24 100644 --- a/crates/nash-cli/Cargo.toml +++ b/crates/nash-cli/Cargo.toml @@ -16,6 +16,7 @@ name = "nash" path = "src/main.rs" [dependencies] +nash-proof = { path = "../nash-proof", version = "0.1.0" } nash-region = { path = "../nash-region", version = "0.3.0" } nash-docs = { path = "../nash-docs", version = "0.2.0" } nash-fmt = { path = "../nash-fmt", version = "0.2.0" } diff --git a/crates/nash-cli/src/cmd/mod.rs b/crates/nash-cli/src/cmd/mod.rs index 8f44be5d..45379461 100644 --- a/crates/nash-cli/src/cmd/mod.rs +++ b/crates/nash-cli/src/cmd/mod.rs @@ -3,6 +3,7 @@ pub mod check; pub mod docs; pub mod format; pub mod lsp; +pub mod proof; pub mod test; #[derive(clap::Subcommand)] @@ -16,6 +17,9 @@ pub enum Cmd { /// Compile and execute module tests and properties #[clap(visible_alias = "t")] Test(test::Args), + /// Verify proof properties with Lean-blaster and compiled UPLC semantics + #[clap(visible_alias = "verify")] + Proof(proof::Args), /// Format Nash source files #[clap(visible_alias = "fmt")] Format(format::Args), @@ -32,6 +36,7 @@ impl Cmd { Cmd::Check(args) => args.exec(color).await, Cmd::Build(args) => args.exec(color).await, Cmd::Test(args) => args.exec(color).await, + Cmd::Proof(args) => args.exec(color).await, Cmd::Format(args) => args.exec(color).await, Cmd::Lsp(args) => lsp::exec(args).await, } diff --git a/crates/nash-cli/src/cmd/proof.rs b/crates/nash-cli/src/cmd/proof.rs new file mode 100644 index 00000000..3437dcc2 --- /dev/null +++ b/crates/nash-cli/src/cmd/proof.rs @@ -0,0 +1,211 @@ +use super::build::PlutusVersionArg; +use miette::{IntoDiagnostic, Result}; +use nash_driver::{Database, FileSystemSource, Project, build_graph_with_tests, test_with}; +use std::{path::PathBuf, sync::Arc, time::Duration}; +use tokio::sync::Mutex; + +#[derive(clap::Args)] +pub struct Args { + #[arg(default_value = ".")] + pub path: PathBuf, + /// Directory for a managed Lean project; each run preserves its own sources. + #[arg(long, default_value = "build/proofs")] + pub output: PathBuf, + /// Export without invoking Lean or a solver. + #[arg(long)] + pub emit_only: bool, + /// Existing Lake project with the pinned dependencies already built. + #[arg(long)] + pub lean_project: Option, + /// CEK execution step limit; exhaustion counts as rejection. + #[arg(long, default_value = "10000", value_parser = positive)] + pub fuel: u32, + #[arg(long, default_value = "30", value_parser = positive)] + pub timeout: u32, + #[arg(long, default_value = "120", value_parser = positive)] + pub wall_timeout: u32, + #[arg(short = 'm', long = "match")] + pub matches: Vec, + #[arg(long)] + pub exact: bool, + #[arg(long, value_enum)] + pub plutus_version: Option, + #[arg(long)] + pub json: bool, +} +fn positive(value: &str) -> std::result::Result { + value + .parse::() + .ok() + .filter(|n| *n > 0) + .ok_or_else(|| "expected a positive integer".into()) +} +impl Args { + pub async fn exec(self, color: bool) -> Result<()> { + let project = Project::load(&self.path).await.into_diagnostic()?; + let db = Arc::new(Mutex::new(Database::new(FileSystemSource::new()))); + let modules = project + .discover_modules(&*db.lock().await) + .await + .into_diagnostic()?; + let roots = project + .discover_own_modules(&*db.lock().await) + .await + .into_diagnostic()?; + let graph = build_graph_with_tests( + db.clone(), + &modules.keys().cloned().collect::>(), + &roots.keys().cloned().collect::>(), + ) + .await + .into_diagnostic()?; + let member_settings: Vec<_> = project + .members + .iter() + .flat_map(|member| { + member.source_dirs.iter().map(move |directory| { + ( + directory + .canonicalize() + .unwrap_or_else(|_| directory.clone()), + member.config.build(), + ) + }) + }) + .collect(); + let configs: std::collections::BTreeMap<_, _> = roots + .keys() + .map(|uri| { + let path = uri.to_file_path().expect("discovered file URL"); + let path = path.canonicalize().unwrap_or(path); + let mut config = member_settings + .iter() + .filter(|(directory, _)| path.starts_with(directory)) + .max_by_key(|(directory, _)| directory.components().count()) + .map_or_else(|| project.config.build(), |(_, config)| *config); + if let Some(version) = self.plutus_version { + config.plutus_version = match version { + PlutusVersionArg::V1 => nash_config::PlutusVersion::V1, + PlutusVersionArg::V2 => nash_config::PlutusVersion::V2, + PlutusVersionArg::V3 => nash_config::PlutusVersion::V3, + }; + } + (uri.clone(), config) + }) + .collect(); + let patterns = self.matches.clone(); + let exact = self.exact; + let (report, programs) = test_with(db, &graph, &modules, move |solved| { + nash_driver::build::compile_proofs_matching_with( + solved, + |uri| configs.get(uri).copied(), + |module, name| super::test::matches_filter(&patterns, exact, module, name), + ) + }) + .await; + if !report.is_success() || !self.json { + super::check::Args { + path: self.path.clone(), + report: if self.json { + super::check::ReportFormat::Json + } else { + super::check::ReportFormat::Human + }, + no_warnings: false, + } + .report_result(Ok((project.root.clone(), report)), color)?; + } + let programs = programs + .expect("successful frontend invokes finish") + .into_diagnostic()?; + if programs.is_empty() { + return Err(miette::miette!("No proof declarations matched.")); + } + let output = if self.output.is_absolute() { + self.output.clone() + } else { + project.root.join(&self.output) + }; + if let Some(parent) = output.parent() { + std::fs::create_dir_all(parent).into_diagnostic()?; + } + let files = + nash_proof::export(&programs, &output, self.fuel, self.timeout).into_diagnostic()?; + if self.emit_only { + if self.json { + println!( + "{}", + serde_json::json!({"status":"exported", "output":output, "files":files}) + ); + } else { + eprintln!("Exported {} proof(s) to {}", files.len(), output.display()); + } + return Ok(()); + } + let lean_project = self.lean_project.as_ref().map_or_else( + || output.clone(), + |p| { + if p.is_absolute() { + p.clone() + } else { + project.root.join(p) + } + }, + ); + if self.lean_project.is_none() { + let mut setup = Vec::new(); + if !lean_project.join("lake-manifest.json").exists() { + setup.push(vec!["update"]); + } + setup.push(vec!["build", "CardanoLedgerApi"]); + for args in setup { + let status = std::process::Command::new("lake") + .current_dir(&lean_project) + .args(args) + .stdout(std::process::Stdio::inherit()) + .stderr(std::process::Stdio::inherit()) + .status() + .into_diagnostic()?; + if !status.success() { + return Err(miette::miette!( + "Lean backend setup failed. Sources are preserved at {}", + output.display() + )); + } + } + } + let config = nash_proof::Config { + lean_project, + fuel: self.fuel, + solver_timeout: self.timeout, + wall_timeout: Duration::from_secs(u64::from(self.wall_timeout)), + }; + let mut outcomes = Vec::new(); + for (program, file) in programs.iter().zip(files) { + let outcome = nash_proof::run(program, &file, &config).into_diagnostic()?; + if !self.json { + eprintln!( + "{}: {:?} (SMT verification, fuel {})", + outcome.name, outcome.status, outcome.fuel + ); + for entry in &outcome.counterexample { + eprintln!(" {entry}"); + } + if matches!(outcome.status, nash_proof::Status::BackendError) { + eprintln!("{}", outcome.diagnostics); + } + } + outcomes.push(outcome); + } + if self.json { + println!("{}", serde_json::to_string(&outcomes).into_diagnostic()?); + } + if outcomes.iter().any(|outcome| !outcome.passed()) { + return Err(miette::miette!( + "One or more proof obligations remain unverified. Sources are preserved at {}", + output.display() + )); + } + Ok(()) + } +} diff --git a/crates/nash-cli/src/cmd/test.rs b/crates/nash-cli/src/cmd/test.rs index 11792961..44dd1eb8 100644 --- a/crates/nash-cli/src/cmd/test.rs +++ b/crates/nash-cli/src/cmd/test.rs @@ -193,7 +193,7 @@ impl Args { } } -fn matches_filter(patterns: &[String], exact: bool, module: &str, name: &str) -> bool { +pub(super) fn matches_filter(patterns: &[String], exact: bool, module: &str, name: &str) -> bool { patterns.is_empty() || patterns.iter().any(|pattern| { if let Some((wanted_module, wanted_name)) = pattern diff --git a/crates/nash-codegen/Cargo.toml b/crates/nash-codegen/Cargo.toml index 0b82d6e5..843292f7 100644 --- a/crates/nash-codegen/Cargo.toml +++ b/crates/nash-codegen/Cargo.toml @@ -8,6 +8,7 @@ repository.workspace = true license.workspace = true [dependencies] +nash-proof = { path = "../nash-proof", version = "0.1.0" } nash-ast = { path = "../nash-ast", version = "0.12.0" } nash-region = { path = "../nash-region", version = "0.3.0" } nash-can = { path = "../nash-can", version = "0.13.0" } diff --git a/crates/nash-codegen/src/build/tests/source/builtins.rs b/crates/nash-codegen/src/build/tests/source/builtins.rs index 00e3c381..0ff6622b 100644 --- a/crates/nash-codegen/src/build/tests/source/builtins.rs +++ b/crates/nash-codegen/src/build/tests/source/builtins.rs @@ -4,10 +4,10 @@ case!( module Main exposing (..) import Primitive exposing (..) import Builtin exposing (..) - type alias proof = { piA : bls_g1, piB : bls_g2 } - encode : proof -> Data + type alias blsProof = { piA : bls_g1, piB : bls_g2 } + encode : blsProof -> Data encode p = Builtin.constrData 0 [B (bls12_381_g1_compress p.piA), B (bls12_381_g2_compress p.piB)] - pk : proof + pk : blsProof pk = { piA = (bls12_381_g1_uncompress #"b28cb29bc282be68df977b35eb9d8e98b3a0a3fc7c372990bddc50419ca86693e491755338fed4fb42231a7c081252ce") , piB = (bls12_381_g2_uncompress #"b9215e5bc481ba6552384c89c23d45bd650b69462868248bfbb83aee7060579404dba41c781dec7c2bec5fccec06842e0e66ad6d86c7c76c468a32c9c0080eea0219d0953b44b1c4f5605afb1e5a3193264ff730222e94f55207628235f3b423") @@ -23,10 +23,10 @@ case!( module Main exposing (..) import Primitive exposing (..) import Builtin exposing (..) - type alias proof = { piA : bls_g1, piB : bls_g2 } - encode : proof -> Data + type alias blsProof = { piA : bls_g1, piB : bls_g2 } + encode : blsProof -> Data encode p = Builtin.constrData 0 [B (bls12_381_g1_compress p.piA), B (bls12_381_g2_compress p.piB)] - pk : proof + pk : blsProof pk = { piA = (bls12_381_g1_uncompress #"b28cb29bc282be68df977b35eb9d8e98b3a0a3fc7c372990bddc50419ca86693e491755338fed4fb42231a7c081252ce") , piB = (bls12_381_g2_uncompress #"b9215e5bc481ba6552384c89c23d45bd650b69462868248bfbb83aee7060579404dba41c781dec7c2bec5fccec06842e0e66ad6d86c7c76c468a32c9c0080eea0219d0953b44b1c4f5605afb1e5a3193264ff730222e94f55207628235f3b423") diff --git a/crates/nash-codegen/src/demand.rs b/crates/nash-codegen/src/demand.rs index 69f79e05..4bf1736b 100644 --- a/crates/nash-codegen/src/demand.rs +++ b/crates/nash-codegen/src/demand.rs @@ -606,6 +606,7 @@ mod graph_tests { } Module { tests: &[], + proofs: &[], traits: &[], impls: &[], kind: ModuleKind::Normal, diff --git a/crates/nash-codegen/src/lib.rs b/crates/nash-codegen/src/lib.rs index b9c3903a..e48cf6e2 100644 --- a/crates/nash-codegen/src/lib.rs +++ b/crates/nash-codegen/src/lib.rs @@ -26,6 +26,7 @@ pub mod tests; #[cfg(test)] mod optimizer_tests; +pub mod proofs; #[cfg(test)] #[path = "../tests/support/optimizer.rs"] mod snapshot_optimizer; diff --git a/crates/nash-codegen/src/proofs.rs b/crates/nash-codegen/src/proofs.rs new file mode 100644 index 00000000..11d13ea6 --- /dev/null +++ b/crates/nash-codegen/src/proofs.rs @@ -0,0 +1,166 @@ +//! Compile proof bodies as UPLC functions over symbolic input domains. +use crate::{ + build::{Binding, Build, Engine, TraceConfig}, + decision_tree::{self, MatchBranch, MatchInputs}, +}; +use nash_ast::{Expr, ModuleName, NodeId, primitives}; +use nash_ir::core::*; +use nash_plutus::{arena::Arena, flat}; +pub use nash_proof::{Domain, ProofProgram}; + +#[derive(Debug, thiserror::Error)] +pub enum Error<'a> { + #[error("unknown proof module {0:?}")] + UnknownModule(ModuleName<'a>), + #[error( + "proof input must use a domain from the bundled Proof module; random generators and arbitrary values are not proof domains" + )] + Domain, + #[error( + "execution-budget constraints are not supported in proof blocks; use tests for measured budgets" + )] + Budget, + #[error("ledger domain version must match the proof's Plutus version")] + LedgerVersion, + #[error("{0}")] + Build(crate::build::Error<'a>), + #[error("{0}")] + Program(crate::program::Error<'a>), + #[error("could not encode proof program: {0}")] + Encoding(String), +} + +impl<'a> From> for Error<'a> { + fn from(error: crate::build::Error<'a>) -> Self { + Self::Build(error) + } +} +impl<'a> From> for Error<'a> { + fn from(error: crate::program::Error<'a>) -> Self { + Self::Program(error) + } +} +impl<'a> From> for Error<'a> { + fn from(error: crate::decision_tree::Error<'a>) -> Self { + Self::Build(error.into()) + } +} + +pub fn compile_proofs_matching<'a>( + arena: &'a Arena, + build: &Build<'a, '_>, + module: ModuleName<'a>, + version: nash_config::PlutusVersion, + mut include: impl FnMut(&nash_ast::Test<'a>) -> bool, +) -> Result, Error<'a>> { + let input = build + .inputs + .iter() + .position(|i| i.module.name == module) + .ok_or(Error::UnknownModule(module))?; + let mut programs = Vec::new(); + for proof in build.inputs[input].module.proofs { + if !include(proof) { + continue; + } + if proof.budget.is_some() { + return Err(Error::Budget); + } + let domains = proof + .binders + .iter() + .map(|binder| { + let Expr::VarForeign { reference, .. } = binder.generator.value else { + return Err(Error::Domain); + }; + if reference.home.package != Some(primitives::BASE) + || reference.home.name != "Proof" + { + return Err(Error::Domain); + } + let domain = Domain::named(reference.name).ok_or(Error::Domain)?; + let target = match version { + nash_config::PlutusVersion::V1 => 1, + nash_config::PlutusVersion::V2 => 2, + nash_config::PlutusVersion::V3 => 3, + }; + if matches!(domain, Domain::Spending(v) | Domain::Minting(v) if v != target) { + return Err(Error::LedgerVersion); + } + Ok(domain) + }) + .collect::, _>>()?; + let mut engine = Engine::new( + build, + arena, + TraceConfig { + user: crate::build::TraceLevel::Silent, + compiler: false, + }, + ); + let mut ctx = engine.test_context(input); + ctx.test = false; // Ordinary assertions: no test power-assert instrumentation. + let mut params = Vec::new(); + let mut patterns = Vec::new(); + for binder in proof.binders { + let ty = engine.ty(NodeId::pattern(binder.pattern), &ctx)?; + let value = Binder { + name: engine.ir.fresh("symbolic"), + ty, + }; + let (records, literals) = engine.pattern_inputs(binder.pattern, &ctx)?; + let bindings = decision_tree::bindings( + &engine.ir, + &mut engine.types, + ty, + binder.pattern, + &records, + )?; + for (name, bound) in &bindings { + ctx.env.insert(name, Binding::Value(*bound)); + } + params.push(value); + patterns.push((binder, value, records, literals, bindings)); + } + let mut body = engine.expr(proof.body, &ctx)?; + for (binder, value, records, literals, bindings) in patterns.into_iter().rev() { + body = decision_tree::compile( + &engine.ir, + &mut engine.types, + value.ty, + engine.ir.var(value.name, value.ty), + &[MatchBranch { + pattern: binder.pattern, + bindings, + body, + }], + MatchInputs { + record_fields: &records, + literal_tests: &literals, + }, + engine.ir.error(body.ty), + )?; + } + let root = if params.is_empty() { + body + } else { + engine.ir.lam(¶ms, body) + }; + let core = engine.finish_root(root)?; + let target = match version { + nash_config::PlutusVersion::V1 => nash_plutus::machine::PlutusVersion::V1, + nash_config::PlutusVersion::V2 => nash_plutus::machine::PlutusVersion::V2, + nash_config::PlutusVersion::V3 => nash_plutus::machine::PlutusVersion::V3, + }; + let compiled = crate::program::assemble_core_for_version(arena, core, target)?; + programs.push(ProofProgram { + module: module.name.to_owned(), + name: proof.name.value.to_owned(), + expect: proof.expect, + domains, + flat: flat::encode(compiled.program).map_err(|e| Error::Encoding(e.to_string()))?, + plutus_version: version, + }); + } + Ok(programs) +} diff --git a/crates/nash-constrain/src/module.rs b/crates/nash-constrain/src/module.rs index 07de94aa..5ae9152b 100644 --- a/crates/nash-constrain/src/module.rs +++ b/crates/nash-constrain/src/module.rs @@ -201,6 +201,7 @@ mod tests { const DOCS: Docs<'static> = Docs::NoDocs(Region::zero()); Module { tests: &[], + proofs: &[], kind: ModuleKind::Validator(Region::zero()), name: ModuleName { package: None, diff --git a/crates/nash-driver/Cargo.toml b/crates/nash-driver/Cargo.toml index f5621f57..1d53e081 100644 --- a/crates/nash-driver/Cargo.toml +++ b/crates/nash-driver/Cargo.toml @@ -8,6 +8,7 @@ repository.workspace = true license.workspace = true [dependencies] +nash-proof = { path = "../nash-proof", version = "0.1.0" } nash-test = { path = "../nash-test", version = "0.4.2" } nash-codegen = { path = "../nash-codegen", version = "0.4.3" } nash-plutus = { path = "../nash-plutus", version = "0.3.3" } diff --git a/crates/nash-driver/base/src/Proof.nash b/crates/nash-driver/base/src/Proof.nash new file mode 100644 index 00000000..d339d427 --- /dev/null +++ b/crates/nash-driver/base/src/Proof.nash @@ -0,0 +1,58 @@ +module Proof exposing (int, integer, bool, bytes, byteString, string, data, spendingV1, spendingV2, spendingV3, mintingV1, mintingV2, mintingV3) + +{-| Symbolic input domains for Lean-blaster verification. -} + +import Builtin +import Primitive + +{-| All native integers. -} +int : int +int = 0 + +{-| All Data-represented integers. -} +integer : Int +integer = Builtin.iData 0 + +{-| All native Booleans. -} +bool : bool +bool = Primitive.False + +{-| All native bytestrings in the PlutusCore model. -} +bytes : bytes +bytes = #"" + +{-| All Data-represented bytestrings in the PlutusCore model. -} +byteString : Bytes +byteString = Builtin.bData #"" + +{-| All native strings. -} +string : string +string = "" + +{-| All Plutus Data values, including malformed application encodings. -} +data : Data +data = Primitive.coerce (Builtin.iData 0) + +{-| Contexts satisfying the CardanoLedgerApi Plutus V1 spending validity predicate. -} +spendingV1 : Data +spendingV1 = data + +{-| Contexts satisfying the CardanoLedgerApi Plutus V2 spending validity predicate. -} +spendingV2 : Data +spendingV2 = data + +{-| Contexts satisfying the CardanoLedgerApi Plutus V3 spending validity predicate. -} +spendingV3 : Data +spendingV3 = data + +{-| Contexts satisfying the CardanoLedgerApi Plutus V1 minting validity predicate. -} +mintingV1 : Data +mintingV1 = data + +{-| Contexts satisfying the CardanoLedgerApi Plutus V2 minting validity predicate. -} +mintingV2 : Data +mintingV2 = data + +{-| Contexts satisfying the CardanoLedgerApi Plutus V3 minting validity predicate. -} +mintingV3 : Data +mintingV3 = data diff --git a/crates/nash-driver/src/build.rs b/crates/nash-driver/src/build.rs index 3b71d6a1..6349f67d 100644 --- a/crates/nash-driver/src/build.rs +++ b/crates/nash-driver/src/build.rs @@ -331,3 +331,37 @@ pub fn compile_tests_matching_with( } Ok(outputs) } + +/// Compile only selected proof roots. Random generators are never evaluated. +pub fn compile_proofs_matching_with( + solved: Solved<'_>, + mut config_for: impl FnMut(&url::Url) -> Option, + mut include: impl FnMut(&str, &str) -> bool, +) -> Result, BuildError> { + let arena = Arena::new(); + let build = Build::new(solved.modules.iter().map(|module| Input { + module: module.module, + types: &module.types, + tables: &module.tables, + })); + let mut outputs = Vec::new(); + for module in &solved.modules { + let Some(config) = config_for(&module.uri) else { + continue; + }; + outputs.extend( + nash_codegen::proofs::compile_proofs_matching( + &arena, + &build, + module.module.name, + config.plutus_version, + |proof| include(module.module.name.name, proof.name.value), + ) + .map_err(|error| BuildError { + module: module.module.name.name.to_owned(), + message: error.to_string(), + })?, + ); + } + Ok(outputs) +} diff --git a/crates/nash-driver/src/compile.rs b/crates/nash-driver/src/compile.rs index cc12fdfd..f7c10bc4 100644 --- a/crates/nash-driver/src/compile.rs +++ b/crates/nash-driver/src/compile.rs @@ -420,6 +420,7 @@ fn compile_module<'s>( }; if exclude_tests { module.tests = None; + module.proofs = None; } let name = module.name.map_or(expected_name, |name| name.value); let defaults = nash_can::defaults::imports( @@ -614,6 +615,7 @@ fn scan_module( module .tests .into_iter() + .chain(module.proofs) .filter(|_| include_tests) .flat_map(|tests| tests.imports.iter()), ) { diff --git a/crates/nash-driver/tests/proofs.rs b/crates/nash-driver/tests/proofs.rs new file mode 100644 index 00000000..8667b26f --- /dev/null +++ b/crates/nash-driver/tests/proofs.rs @@ -0,0 +1,179 @@ +use nash_driver::{Database, InMemorySource, build_graph, bundled_base, test_with}; +use std::sync::Arc; +use tokio::sync::Mutex; +use url::Url; + +async fn compile(source: &str) -> Result, String> { + let memory = InMemorySource::new(); + let main = Url::parse("file:///proof-project/src/Main.nash").unwrap(); + memory.insert(main.clone(), source.into()); + let db = Arc::new(Mutex::new(Database::new(memory))); + let mut modules = bundled_base::modules(); + modules.insert(main.clone(), None); + let graph = build_graph(db.clone(), &modules.keys().cloned().collect::>()) + .await + .unwrap(); + let (report, programs) = test_with(db, &graph, &modules, move |solved| { + nash_driver::build::compile_proofs_matching_with( + solved, + |uri| (*uri == main).then(nash_config::Build::default), + |_, _| true, + ) + }) + .await; + if !report.is_success() { + return Err(format!("{:#?}", report.ordered_reports())); + } + programs.unwrap().map_err(|e| e.message) +} + +#[tokio::test] +async fn proof_domains_compile_to_closed_uplc_without_test_instrumentation() { + let programs = compile( + r#"module Main exposing (..) +proof + import Proof + prop "integer identity" = + let x via Proof.int in + do + assert (x + 0 == x) + prop "big integer identity" = + let x via Proof.integer in + do + assert (x == x) + prop "ledger identity" = + let ctx via Proof.spendingV3 in + do + assert (ctx == ctx) +"#, + ) + .await + .unwrap(); + assert_eq!(programs.len(), 3); + assert_eq!(programs[0].domains, vec![nash_proof::Domain::Int]); + assert_eq!(programs[2].domains, vec![nash_proof::Domain::Spending(3)]); + assert!(programs.iter().all(|p| !p.flat.is_empty())); +} + +#[tokio::test] +async fn random_generator_is_not_a_symbolic_proof_domain() { + let error = compile( + r#"module Main exposing (..) +proof + import Prop + prop "random is not universal" = + let x via Prop.int in + do + assert (x == x) +"#, + ) + .await + .unwrap_err(); + assert!(error.contains("invalid_domain"), "{error}"); +} + +#[tokio::test] +async fn proof_imports_do_not_leak_into_tests() { + let error = compile( + r#"module Main exposing (..) +tests + test "private import" = do + assert (Proof.int == 0) +proof + import Proof + test "closed" = do + assert True +"#, + ) + .await + .unwrap_err(); + assert!(error.contains("Proof"), "{error}"); +} + +#[tokio::test] +async fn proof_budgets_and_mismatched_ledger_versions_are_rejected() { + for (body, expected) in [ + ( + "test \"budget\" within (cpu 100) = do\n assert True", + "budget", + ), + ( + "prop \"wrong version\" =\n let ctx via Proof.spendingV2 in\n do\n assert (ctx == ctx)", + "version", + ), + ] { + let source = format!("module Main exposing (..)\nproof\n import Proof\n {body}\n"); + let error = compile(&source).await.unwrap_err(); + assert!(error.contains(expected), "{error}"); + } +} + +#[tokio::test] +#[ignore = "requires NASH_PROOF_LEAN_PROJECT, Lean 4.24, the pinned libraries and Z3"] +async fn live_backend() { + let project = std::env::var_os("NASH_PROOF_LEAN_PROJECT").expect("NASH_PROOF_LEAN_PROJECT"); + let mut programs = compile( + r#"module Main exposing (..) +proof + import Proof + prop "identity" = let x via Proof.int in do + assert (x + 0 == x) + prop "not all integers are nonnegative" = let x via Proof.int in do + assert (x >= 0) + prop "division always fails" fail = let x via Proof.int in do + assert (x / 0 == 0) + prop "negative witness" fail once = let x via Proof.int in do + assert (x >= 0) +"#, + ) + .await + .unwrap(); + let directory = std::env::temp_dir().join(format!( + "nash-live-proofs-{}-{}", + std::process::id(), + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_nanos() + )); + let files = nash_proof::export(&programs, &directory, 65, 10).unwrap(); + let config = nash_proof::Config { + lean_project: project.into(), + fuel: 65, + solver_timeout: 10, + wall_timeout: std::time::Duration::from_secs(60), + }; + for ((program, source), expected) in programs.iter().zip(files).zip([ + nash_proof::Status::Verified, + nash_proof::Status::Counterexample, + nash_proof::Status::Verified, + nash_proof::Status::Witness, + ]) { + let outcome = nash_proof::run(program, &source, &config).unwrap(); + assert_eq!(outcome.status, expected, "{}", outcome.diagnostics); + } + let limited = directory.join("limited"); + let files = nash_proof::export(&programs[..1], &limited, 1, 10).unwrap(); + let config = nash_proof::Config { fuel: 1, ..config }; + let outcome = nash_proof::run(&programs[0], &files[0], &config).unwrap(); + assert_eq!( + outcome.status, + nash_proof::Status::Counterexample, + "{}", + outcome.diagnostics + ); + for (index, expect, status) in [ + (0, nash_source::Expect::Fail, nash_proof::Status::Verified), + ( + 1, + nash_source::Expect::FailOnce, + nash_proof::Status::Witness, + ), + ] { + programs[0].expect = expect; + let directory = directory.join(format!("exhaustion-{index}")); + let files = nash_proof::export(&programs[..1], &directory, 1, 10).unwrap(); + let outcome = nash_proof::run(&programs[0], &files[0], &config).unwrap(); + assert_eq!(outcome.status, status, "{}", outcome.diagnostics); + } +} diff --git a/crates/nash-fmt/src/declarations.rs b/crates/nash-fmt/src/declarations.rs index 3a940f83..bd90bc24 100644 --- a/crates/nash-fmt/src/declarations.rs +++ b/crates/nash-fmt/src/declarations.rs @@ -189,7 +189,7 @@ impl Printer<'_> { cat([Doc::Hard, methods]).nest(), ]) } - fn tests(&mut self, tests: &Tests<'_>) -> Doc { + fn tests(&mut self, tests: &Tests<'_>, proof: bool) -> Doc { let mut docs = Vec::new(); for import in tests.imports { docs.push(self.import(import)); @@ -242,7 +242,7 @@ impl Printer<'_> { ])); } cat([ - text("tests"), + text(if proof { "proof" } else { "tests" }), cat([Doc::Hard, join(docs, cat([Doc::Hard, Doc::Hard]))]).nest(), ]) } @@ -353,7 +353,10 @@ impl Printer<'_> { chunks.push(cat([before, doc])); } if let Some(tests) = module.tests { - chunks.push(self.tests(tests)); + chunks.push(self.tests(tests, false)); + } + if let Some(proofs) = module.proofs { + chunks.push(self.tests(proofs, true)); } let trailing = self.before(Position::new(usize::MAX, usize::MAX)); if chunks.is_empty() { diff --git a/crates/nash-fmt/src/tests/semantic.rs b/crates/nash-fmt/src/tests/semantic.rs index 9b5a2e4f..4459e167 100644 --- a/crates/nash-fmt/src/tests/semantic.rs +++ b/crates/nash-fmt/src/tests/semantic.rs @@ -78,6 +78,7 @@ structure!(Module { traits, impls, tests, + proofs, binops }); impl Semantic for ModuleKind { diff --git a/crates/nash-nitpick/src/check.rs b/crates/nash-nitpick/src/check.rs index 38f264b2..703b94e6 100644 --- a/crates/nash-nitpick/src/check.rs +++ b/crates/nash-nitpick/src/check.rs @@ -29,7 +29,7 @@ pub fn check<'a>(bump: &'a Bump, module: &Module<'a>) -> Result<(), Vec Parser<'a> { self.keyword(b"validator", to_error) } + pub fn keyword_proof(&mut self, to_error: impl FnOnce(Row, Col) -> E) -> Result<(), E> { + self.keyword(b"proof", to_error) + } + pub fn keyword_tests(&mut self, to_error: impl FnOnce(Row, Col) -> E) -> Result<(), E> { self.keyword(b"tests", to_error) } diff --git a/crates/nash-parse/src/module.rs b/crates/nash-parse/src/module.rs index a804d624..ecf7961f 100644 --- a/crates/nash-parse/src/module.rs +++ b/crates/nash-parse/src/module.rs @@ -242,13 +242,18 @@ impl<'a> Parser<'a> { let decls = self.declarations()?; self.chomp(error::Module::Space)?; - let tests = self.one_of_with_fallback( - vec![Box::new(|parser: &mut Parser<'a>| { - parser.tests_block().map(Some) - })], - None, - )?; - self.chomp(error::Module::Space)?; + let mut tests = None; + let mut proofs = None; + loop { + if self.starts_keyword(b"tests") && tests.is_none() { + tests = Some(self.tests_block()?); + } else if self.starts_keyword(b"proof") && proofs.is_none() { + proofs = Some(self.proofs_block()?); + } else { + break; + } + self.chomp(error::Module::Space)?; + } if !self.is_eof() { return Err(error::Module::BadEnd(self.row, self.col)); } @@ -293,6 +298,7 @@ impl<'a> Parser<'a> { traits, impls, tests, + proofs, binops, }) } diff --git a/crates/nash-parse/src/tests_block.rs b/crates/nash-parse/src/tests_block.rs index dd39d219..d330ae9f 100644 --- a/crates/nash-parse/src/tests_block.rs +++ b/crates/nash-parse/src/tests_block.rs @@ -7,9 +7,23 @@ use crate::error::{self, Test as TestErr, Tests as TestsErr}; impl<'a> Parser<'a> { pub(crate) fn tests_block(&mut self) -> Result<&'a Tests<'a>, error::Module<'a>> { + self.specification_block(false) + } + + pub(crate) fn proofs_block(&mut self) -> Result<&'a Tests<'a>, error::Module<'a>> { + self.specification_block(true) + } + + fn specification_block(&mut self, proof: bool) -> Result<&'a Tests<'a>, error::Module<'a>> { self.in_context( |bump, error, row, col| error::Module::Tests(bump.alloc(error), row, col), - |parser| parser.keyword_tests(error::Module::BadEnd), + |parser| { + if proof { + parser.keyword_proof(error::Module::BadEnd) + } else { + parser.keyword_tests(error::Module::BadEnd) + } + }, |parser| { let tests_end = parser.get_position(); parser.chomp(TestsErr::Space)?; @@ -564,3 +578,28 @@ mod tests { ); } } + +#[cfg(test)] +mod proof_tests { + #[test] + fn tests_and_proofs_are_separate_and_either_order_parses() { + for source in [ + "module Main exposing (..)\ntests\n test \"t\" = do\n assert True\nproof\n prop \"p\" = let x via Proof.int in do\n assert (x == x)\n", + "module Main exposing (..)\nproof\n test \"p\" = do\n assert True\ntests\n test \"t\" = do\n assert True\n", + ] { + let bump = bumpalo::Bump::new(); + let module = crate::Parser::new(&bump, source).module().unwrap(); + assert_eq!(module.tests.unwrap().tests.len(), 1); + assert_eq!(module.proofs.unwrap().tests.len(), 1); + } + } + #[test] + fn duplicate_proof_blocks_are_rejected() { + let bump = bumpalo::Bump::new(); + assert!( + crate::Parser::new(&bump, "module Main exposing (..)\nproof\nproof\n") + .module() + .is_err() + ); + } +} diff --git a/crates/nash-proof/Cargo.toml b/crates/nash-proof/Cargo.toml new file mode 100644 index 00000000..13c130bf --- /dev/null +++ b/crates/nash-proof/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "nash-proof" +version = "0.1.0" +edition.workspace = true +description = "Lean-blaster verification of compiled Nash proof properties" +homepage.workspace = true +repository.workspace = true +license.workspace = true + +[dependencies] +nash-source = { path = "../nash-source", version = "0.10.0" } +nash-config = { path = "../nash-config", version = "0.5.0" } +hex.workspace = true +serde.workspace = true +serde_json.workspace = true +thiserror.workspace = true + +[target.'cfg(unix)'.dependencies] +libc = "0.2" diff --git a/crates/nash-proof/src/lean.rs b/crates/nash-proof/src/lean.rs new file mode 100644 index 00000000..67accd1c --- /dev/null +++ b/crates/nash-proof/src/lean.rs @@ -0,0 +1,208 @@ +use crate::{Domain, ProofProgram}; +use nash_source::Expect; + +pub fn lakefile() -> &'static str { + r#"import Lake +open Lake DSL +package NashProof +require Blaster from git "https://github.com/input-output-hk/Lean-blaster" @ "bafdd4f7976037cd7bd8e443df04af096dc5b96e" +require PlutusCore from git "https://github.com/input-output-hk/PlutusCoreBlaster" @ "41fe7eadf460dc66bef22b85656bc36408638bdc" +require CardanoLedgerApi from git "https://github.com/input-output-hk/CardanoLedgerApiBlaster" @ "3f9f7c6b6ecf0012e1397bab0a814daa36e70ba2" +"# +} + +/// A command-level result protocol avoids admissions and human-log parsing. +const SUPPORT: &str = r##"import Blaster +import PlutusCore.UPLC +import CardanoLedgerApi.V1 +import CardanoLedgerApi.V2 +import CardanoLedgerApi.V3 +open PlutusCore.UPLC.Term (Term Const) +open PlutusCore.UPLC.Utils +open CardanoLedgerApi.IsData.Class (toTerm) +set_option maxRecDepth 100000 +set_option maxHeartbeats 0 + +namespace NashProof + +open PlutusCore.UPLC.CekMachine +open PlutusCore.UPLC.PlutusScript +open PlutusCore.UPLC.Term (Program) + +-- Preserve a running state at fuel zero. Upstream runSteps turns it into Error. +def runSteps (semantics : PlutusCore.Default.BuiltinSemanticsVariant) (state : State) (fuel : Nat) : State := + match fuel, state with + | _, .Halt _ => state + | _, .Error => state + | 0, _ => state + | fuel + 1, _ => runSteps semantics (step semantics state) fuel + +def execute (script : PlutusScript) (params : List PlutusCore.UPLC.Term.Term) (fuel : Nat) : State := + let semantics : PlutusCore.Default.BuiltinSemanticsVariant := match script.lang with + | .PlutusV1 | .PlutusV2 => .defaultFunSemanticsVariantD + | .PlutusV3 => .defaultFunSemanticsVariantE + match script.script with + | .Program _ body => runSteps semantics (initialState (applyParams body params)) fuel + +-- Rejection includes an unfinished state at the configured execution limit. +def isRejected (state : State) : Prop := ¬ PlutusCore.UPLC.Utils.isSuccessful state + +open Lean Elab Command Term Meta + +syntax (name := prepare) "#nash_prep" ident ident ident num : command +@[command_elab prepare] +def prepareImpl : CommandElab := fun stx => do + let declaration ← withoutModifyingEnv <| runTermElabM fun _ => do + let some script ← resolveId? stx[2] | throwError "unknown proof script" + let some inputs ← resolveId? stx[3] | throwError "unknown proof input conversion" + let application ← Meta.lambdaTelescope (← Meta.etaExpand inputs) fun xs _ => + mkLambdaFVars xs (mkApp3 (mkConst ``execute) script + (mkAppN inputs xs) (mkNatLit (TSyntax.getNat ⟨stx[4]⟩))) + let (expression, _) ← Blaster.Optimize.Optimize.main application |>.run default + return Declaration.defnDecl { + name := stx[1].getId, levelParams := [], type := ← inferType expression, + value := expression, hints := .abbrev, safety := .safe } + modifyEnv (addNoncomputable · stx[1].getId) + liftCoreM <| addDecl declaration + +syntax (name := verify) "#nash_verify" str num "[" term "]" : command +@[command_elab verify] +def verifyImpl : CommandElab := fun stx => do + let saved ← get + let result ← runTermElabM fun _ => do + let expression ← instantiateMVars (← withSynthesize (postpone := .partial) <| elabTerm stx[4] none) + let options : Blaster.Options.BlasterOptions := { timeout := some (TSyntax.getNat ⟨stx[2]⟩) } + let env : Blaster.Optimize.TranslateEnv := { (default : Blaster.Optimize.TranslateEnv) with optEnv.options.solverOptions := options } + let ((result, _), _) ← Blaster.Smt.Translate.main expression |>.run env + return result + -- Blaster logs a mismatched expected result as an elaboration error. The structured + -- protocol handles all outcomes, so remove only this command's solver messages. + set saved + let (status, cex) := match result with + | .Valid => ("valid", ([] : List String)) + | .Falsified cex => ("falsified", cex) + | .Undetermined => ("unknown", []) + let json := Json.mkObj [("query", toJson (TSyntax.getString ⟨stx[1]⟩)), ("status", toJson status), ("counterexample", toJson cex)] + liftIO <| IO.println ("@@NASH_PROOF@@" ++ json.compress) +end NashProof +"##; + +pub fn render(program: &ProofProgram, fuel: u32, timeout: u32) -> String { + let mut text = SUPPORT.to_owned(); + let version = match program.plutus_version { + nash_config::PlutusVersion::V1 => 1, + nash_config::PlutusVersion::V2 => 2, + nash_config::PlutusVersion::V3 => 3, + }; + // Embed flat bytes, so moving an exported project cannot break script paths. + text.push_str(&format!("\ndef script : PlutusCore.UPLC.PlutusScript.PlutusScript :=\n {{ lang := .PlutusV{version}, script := flatEncodedScriptFromHexM \"{}\" }}\n", hex::encode(&program.flat))); + let mut binders = String::new(); + let mut args = Vec::new(); + let mut terms = Vec::new(); + let mut assumptions = Vec::new(); + for (i, domain) in program.domains.iter().enumerate() { + let name = format!("input{i}"); + let (ty, term, assumption) = domain.input(&name); + binders.push_str(&format!(" ({name} : {ty})")); + args.push(name); + terms.push(term); + if let Some(assumption) = assumption { + assumptions.push(assumption); + } + } + text.push_str(&format!( + "\ndef inputs{binders} : List Term := [{}]\n", + terms.join(", ") + )); + text.push_str(&format!("\n#nash_prep prepared script inputs {fuel}\n")); + let state = format!("(prepared {})", args.join(" ")); + let claim = match program.expect { + Expect::Pass => format!("isSuccessful {state}"), + Expect::Fail => format!("NashProof.isRejected {state}"), + // Refuting universal success finds a script error or fuel exhaustion. + Expect::FailOnce => format!("isSuccessful {state}"), + }; + let quantify = |body: &str| { + let pre = if assumptions.is_empty() { + String::new() + } else { + format!("{} → ", assumptions.join(" → ")) + }; + if binders.is_empty() { + format!("{pre}{body}") + } else { + format!("∀{binders}, {pre}{body}") + } + }; + text.push_str(&format!( + "\n#nash_verify \"property\" {timeout} [{}]\n", + quantify(&claim) + )); + text +} + +impl Domain { + fn input(self, name: &str) -> (String, String, Option) { + let (ty, term) = match self { + Self::Int => ( + "PlutusCore.Integer.Integer", + format!("Term.Const (Const.Integer {name})"), + ), + Self::Integer => ("PlutusCore.Integer.Integer", format!("toTerm {name}")), + Self::Bool => ("Bool", format!("Term.Const (Const.Bool {name})")), + Self::Bytes => ( + "PlutusCore.ByteString.ByteString", + format!("Term.Const (Const.ByteString {name})"), + ), + Self::ByteString => ("PlutusCore.ByteString.ByteString", format!("toTerm {name}")), + Self::String => ("String", format!("Term.Const (Const.String {name})")), + Self::Data => ("PlutusCore.Data.Data", format!("toTerm {name}")), + Self::Spending(v) | Self::Minting(v) => { + let purpose = if matches!(self, Self::Spending(_)) { + "Spending" + } else { + "Minting" + }; + let ty = if v == 3 { + format!("CardanoLedgerApi.V{v}.ScriptContext") + } else { + format!("CardanoLedgerApi.V{v}.{purpose}Input") + }; + let term = if v == 3 { + format!("toTerm {name}") + } else { + format!("toTerm {name}.ctx") + }; + return ( + ty, + term, + Some(format!( + "CardanoLedgerApi.V{v}.valid{purpose}Context {name}" + )), + ); + } + }; + (ty.to_owned(), term, None) + } +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn ledger_input_retains_validity_and_bytecode() { + let program = ProofProgram { + module: "M".into(), + name: "p".into(), + expect: Expect::Pass, + domains: vec![Domain::Spending(3)], + flat: vec![1, 2, 3], + plutus_version: nash_config::PlutusVersion::V3, + }; + let source = render(&program, 1000, 5); + assert!(source.contains("flatEncodedScriptFromHexM \"010203\"")); + assert!(source.contains("∀ (input0 : CardanoLedgerApi.V3.ScriptContext), CardanoLedgerApi.V3.validSpendingContext input0 →")); + assert!(source.contains("isSuccessful (prepared input0)")); + assert!(!source.contains("by blaster")); + } +} diff --git a/crates/nash-proof/src/lib.rs b/crates/nash-proof/src/lib.rs new file mode 100644 index 00000000..f1910746 --- /dev/null +++ b/crates/nash-proof/src/lib.rs @@ -0,0 +1,52 @@ +//! Verify actual compiled UPLC using PlutusCoreBlaster and CardanoLedgerApiBlaster. +mod lean; +mod runner; +pub use lean::{lakefile, render}; +pub use runner::{Config, Outcome, Status, export, run}; + +use nash_source::Expect; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Domain { + Int, + Integer, + Bool, + Bytes, + ByteString, + String, + Data, + Spending(u8), + Minting(u8), +} + +impl Domain { + /// Names are recognized only after resolving to the compiler-bundled Proof module. + pub fn named(name: &str) -> Option { + Some(match name { + "int" => Self::Int, + "integer" => Self::Integer, + "bool" => Self::Bool, + "bytes" => Self::Bytes, + "byteString" => Self::ByteString, + "string" => Self::String, + "data" => Self::Data, + "spendingV1" => Self::Spending(1), + "spendingV2" => Self::Spending(2), + "spendingV3" => Self::Spending(3), + "mintingV1" => Self::Minting(1), + "mintingV2" => Self::Minting(2), + "mintingV3" => Self::Minting(3), + _ => return None, + }) + } +} + +#[derive(Debug)] +pub struct ProofProgram { + pub module: String, + pub name: String, + pub expect: Expect, + pub domains: Vec, + pub flat: Vec, + pub plutus_version: nash_config::PlutusVersion, +} diff --git a/crates/nash-proof/src/runner.rs b/crates/nash-proof/src/runner.rs new file mode 100644 index 00000000..f3026f3a --- /dev/null +++ b/crates/nash-proof/src/runner.rs @@ -0,0 +1,249 @@ +use crate::{ProofProgram, lakefile, render}; +use nash_source::Expect; +use serde::{Deserialize, Serialize}; +use std::{ + fs, + path::{Path, PathBuf}, + process::{Command, Stdio}, + time::{Duration, Instant}, +}; + +#[derive(Debug)] +pub struct Config { + /// An existing Lake project with the three pinned dependencies built. + pub lean_project: PathBuf, + pub fuel: u32, + pub solver_timeout: u32, + pub wall_timeout: Duration, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize)] +#[serde(rename_all = "snake_case")] +pub enum Status { + Verified, + Counterexample, + Witness, + NoWitness, + Unknown, + BackendError, + Timeout, +} + +#[derive(Debug, Serialize)] +pub struct Outcome { + pub module: String, + pub name: String, + pub status: Status, + pub fuel: u32, + pub trust: &'static str, + pub counterexample: Vec, + pub diagnostics: String, +} +impl Outcome { + pub fn passed(&self) -> bool { + matches!(self.status, Status::Verified | Status::Witness) + } +} + +#[derive(Deserialize)] +struct Reply { + query: String, + status: String, + counterexample: Vec, +} + +/// Write a portable, inspectable Lake project. No solver or network is invoked. +pub fn export( + programs: &[ProofProgram], + destination: &Path, + fuel: u32, + timeout: u32, +) -> std::io::Result> { + // Only reuse a project we created, with the unchanged pinned configuration. + match fs::create_dir(destination) { + Ok(()) => { + fs::write(destination.join("lakefile.lean"), lakefile())?; + fs::write( + destination.join("lean-toolchain"), + "leanprover/lean4:v4.24.0\n", + )?; + fs::write(destination.join(".nash-proof-project"), "1\n")?; + } + Err(error) if error.kind() == std::io::ErrorKind::AlreadyExists => { + if fs::symlink_metadata(destination)?.file_type().is_symlink() + || fs::read_to_string(destination.join(".nash-proof-project"))? != "1\n" + || fs::read_to_string(destination.join("lakefile.lean"))? != lakefile() + || fs::read_to_string(destination.join("lean-toolchain"))? + != "leanprover/lean4:v4.24.0\n" + { + return Err(std::io::Error::other( + "output is not an unchanged Nash proof project; choose another --output", + )); + } + } + Err(error) => return Err(error), + } + // Each run is immutable; the Lake dependency cache stays in the parent project. + let run_id = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .map_err(std::io::Error::other)? + .as_nanos(); + let run = destination.join(format!("run-{}-{run_id}", std::process::id())); + fs::create_dir(&run)?; + let mut files = Vec::new(); + let mut manifest = Vec::new(); + for (i, program) in programs.iter().enumerate() { + let filename = format!("Proof{i}.lean"); + let file = run.join(&filename); + fs::write(&file, render(program, fuel, timeout))?; + manifest.push(serde_json::json!({"file": filename, "module": program.module, "name": program.name, + "fuel": fuel, "trust": "smt_verified", "plutus_version": format!("{:?}", program.plutus_version)})); + files.push(file); + } + fs::write( + run.join("proofs.json"), + serde_json::to_vec_pretty(&manifest)?, + )?; + Ok(files) +} + +pub fn run(program: &ProofProgram, source: &Path, config: &Config) -> std::io::Result { + let stdout = source.with_extension("stdout"); + let stderr = source.with_extension("stderr"); + let source = source.canonicalize()?; + let mut command = Command::new("lake"); + command + .current_dir(&config.lean_project) + .args(["env", "lean"]) + .arg(&source) + .stdout(Stdio::from(fs::File::create(&stdout)?)) + .stderr(Stdio::from(fs::File::create(&stderr)?)); + #[cfg(unix)] + { + use std::os::unix::process::CommandExt; + command.process_group(0); + } + let mut child = command.spawn()?; + let start = Instant::now(); + let (success, timed_out) = loop { + if let Some(status) = child.try_wait()? { + break (status.success(), false); + } + if start.elapsed() >= config.wall_timeout { + #[cfg(unix)] + unsafe { + libc::kill(-(child.id() as i32), libc::SIGKILL); + } + #[cfg(not(unix))] + child.kill()?; + child.wait()?; + break (false, true); + } + std::thread::sleep(Duration::from_millis(25)); + }; + let out = fs::read_to_string(stdout)?; + let err = fs::read_to_string(stderr)?; + let (status, counterexample) = classify(program.expect, success, timed_out, &out); + Ok(Outcome { + module: program.module.clone(), + name: program.name.clone(), + status, + fuel: config.fuel, + trust: "smt_verified", + counterexample, + diagnostics: format!("{out}{err}"), + }) +} + +fn classify(expect: Expect, success: bool, timed_out: bool, output: &str) -> (Status, Vec) { + if timed_out { + return (Status::Timeout, vec![]); + } + if !success { + return (Status::BackendError, vec![]); + } + let mut replies = Vec::new(); + for line in output.lines() { + if let Some(json) = line.strip_prefix("@@NASH_PROOF@@") { + let Ok(reply) = serde_json::from_str::(json) else { + return (Status::BackendError, vec![]); + }; + if !matches!(reply.status.as_str(), "valid" | "falsified" | "unknown") { + return (Status::BackendError, vec![]); + } + replies.push(reply); + } + } + let existential = expect == Expect::FailOnce; + if replies.len() != 1 || replies[0].query != "property" { + return (Status::BackendError, vec![]); + } + let property = replies.pop().unwrap(); + let status = match (existential, property.status.as_str()) { + (false, "valid") => Status::Verified, + (false, "falsified") => Status::Counterexample, + (true, "falsified") => Status::Witness, + // All inputs succeed within the configured execution limit. + (true, "valid") => Status::NoWitness, + _ => Status::Unknown, + }; + (status, property.counterexample) +} + +#[cfg(test)] +mod tests { + use super::*; + fn reply(query: &str, status: &str) -> String { + format!( + "@@NASH_PROOF@@{{\"query\":\"{query}\",\"status\":\"{status}\",\"counterexample\":[]}}\n" + ) + } + #[test] + fn unknown_and_crashed_runs_never_pass() { + let output = reply("property", "unknown"); + assert_eq!( + classify(Expect::Pass, true, false, &output).0, + Status::Unknown + ); + assert_eq!( + classify(Expect::Pass, false, false, &output).0, + Status::BackendError + ); + assert_eq!( + classify(Expect::Pass, true, false, "✅ Valid").0, + Status::BackendError + ); + } + #[test] + fn existential_failure_requires_an_actual_witness() { + assert_eq!( + classify( + Expect::FailOnce, + true, + false, + &reply("property", "falsified") + ) + .0, + Status::Witness + ); + assert_eq!( + classify(Expect::FailOnce, true, false, &reply("property", "unknown")).0, + Status::Unknown + ); + } + #[test] + fn malformed_duplicate_and_missing_replies_fail_closed() { + for output in [ + String::new(), + reply("completion", "valid") + + &reply("property", "valid") + + &reply("property", "valid"), + reply("completion", "valid") + "@@NASH_PROOF@@bad\n", + ] { + assert_eq!( + classify(Expect::Pass, true, false, &output).0, + Status::BackendError + ); + } + } +} diff --git a/crates/nash-report/src/canonicalize.rs b/crates/nash-report/src/canonicalize.rs index fddddce2..b1e43900 100644 --- a/crates/nash-report/src/canonicalize.rs +++ b/crates/nash-report/src/canonicalize.rs @@ -286,6 +286,24 @@ pub fn to_report_with_name(source: &Source<'_>, error: &Error<'_>, expected_name expected, actual, } => arity(*region, name, "trait", *expected, *actual), + Error::ProofDomain { region } => Report::snippet( + "INVALID PROOF DOMAIN", + *region, + None, + Doc::text("Proof inputs require a symbolic domain from the bundled Proof module."), + Doc::text( + "Use `x via Proof.int` or a ledger domain such as `ctx via Proof.spendingV3`. Random generators are only available in tests.", + ), + ), + Error::ProofBudget { region } => Report::snippet( + "UNSUPPORTED PROOF BUDGET", + *region, + None, + Doc::text("Execution-budget constraints are not supported in proof blocks."), + Doc::text( + "Use tests to measure execution budgets. Proofs use a separately checked CEK step bound.", + ), + ), Error::DuplicateTest { name, first, @@ -979,6 +997,8 @@ pub fn to_report_with_name(source: &Source<'_>, error: &Error<'_>, expected_name Error::BadArity { .. } => "nash::names::bad_arity", Error::ExportNotFound { .. } => "nash::names::export_not_found", Error::ExportOpenAlias { .. } => "nash::names::export_open_alias", + Error::ProofDomain { .. } => "nash::proof::invalid_domain", + Error::ProofBudget { .. } => "nash::proof::unsupported_budget", Error::DuplicateTest { .. } => "nash::names::duplicate_test", Error::DuplicateDecl { .. } => "nash::names::duplicate_decl", Error::DuplicateType { .. } => "nash::names::duplicate_type", diff --git a/crates/nash-solve/src/solve.rs b/crates/nash-solve/src/solve.rs index f1c24005..44ee9486 100644 --- a/crates/nash-solve/src/solve.rs +++ b/crates/nash-solve/src/solve.rs @@ -106,9 +106,17 @@ fn add_error<'a>(mut state: State<'a>, error: Error<'a>) -> State<'a> { state } +#[derive(Clone, Copy, Default)] +enum SpecificationScope { + #[default] + Production, + Test, + Proof, +} + #[derive(Clone, Copy)] struct DeferredField<'a> { - test_scope: bool, + test_scope: SpecificationScope, region: nash_region::Region, context: type_::FieldContext<'a>, record: Variable, @@ -118,7 +126,7 @@ struct DeferredField<'a> { struct Solver<'a, 'tables> { bump: &'a Bump, tables: &'tables nash_can::environment::Tables<'a>, - test_scope: bool, + test_scope: SpecificationScope, pools: Vec>, copied: Vec<(Variable, Variable)>, predicates: Store<'a>, @@ -180,7 +188,7 @@ impl<'a, 'tables> Solver<'a, 'tables> { kind_contracts: Vec::new(), kind_errors: Vec::new(), fields: Vec::new(), - test_scope: false, + test_scope: SpecificationScope::Production, } } @@ -412,10 +420,10 @@ impl<'a> Solver<'a, '_> { }; } Content::Structure(FlatType::App1(home, name, args)) => { - let visible = if field.test_scope { - &self.tables.test_fields - } else { - &self.tables.fields + let visible = match field.test_scope { + SpecificationScope::Production => &self.tables.fields, + SpecificationScope::Test => &self.tables.test_fields, + SpecificationScope::Proof => &self.tables.proof_fields, }; let Some(union) = visible .get(&nash_ast::QualifiedName { home, name }) @@ -3087,7 +3095,7 @@ mod copy_tests { kind_contracts: Vec::new(), kind_errors: Vec::new(), fields: Vec::new(), - test_scope: false, + test_scope: SpecificationScope::Production, }; let mut uf = UnionFind::new(); let name = bump.alloc(Located::at_zero("value")); @@ -3180,7 +3188,7 @@ mod copy_tests { kind_contracts: Vec::new(), kind_errors: Vec::new(), fields: Vec::new(), - test_scope: false, + test_scope: SpecificationScope::Production, }; let result = solver.infer_module( &mut uf, @@ -3252,7 +3260,7 @@ mod copy_tests { kind_contracts: Vec::new(), kind_errors: Vec::new(), fields: Vec::new(), - test_scope: false, + test_scope: SpecificationScope::Production, }; let result = solver.infer_module( &mut uf, @@ -3326,7 +3334,7 @@ mod copy_tests { kind_contracts: Vec::new(), kind_errors: Vec::new(), fields: Vec::new(), - test_scope: false, + test_scope: SpecificationScope::Production, }; let result = solver.infer_module( &mut uf, @@ -3413,7 +3421,7 @@ mod copy_tests { kind_contracts: Vec::new(), kind_errors: Vec::new(), fields: Vec::new(), - test_scope: false, + test_scope: SpecificationScope::Production, }; let result = solver.infer_module( &mut uf, @@ -3550,7 +3558,7 @@ mod copy_tests { kind_contracts: Vec::new(), kind_errors: Vec::new(), fields: Vec::new(), - test_scope: false, + test_scope: SpecificationScope::Production, }; let result = solver.infer_module( &mut uf, @@ -3721,7 +3729,7 @@ mod copy_tests { kind_contracts: Vec::new(), kind_errors: Vec::new(), fields: Vec::new(), - test_scope: false, + test_scope: SpecificationScope::Production, }; let mut uf = UnionFind::new(); let a = bump.alloc(Located::at_zero(CanType::Var("a"))); @@ -3808,7 +3816,7 @@ mod copy_tests { kind_contracts: Vec::new(), kind_errors: Vec::new(), fields: Vec::new(), - test_scope: false, + test_scope: SpecificationScope::Production, }; let mut uf = UnionFind::new(); let result = uf.fresh(make_descriptor(Content::RigidVar("a"))); diff --git a/crates/nash-solve/src/solve/infer.rs b/crates/nash-solve/src/solve/infer.rs index 8499b15c..83506be9 100644 --- a/crates/nash-solve/src/solve/infer.rs +++ b/crates/nash-solve/src/solve/infer.rs @@ -688,8 +688,17 @@ impl<'a> Solver<'a, '_> { self.infer_definition(uf, env, rank, state, &Rtv::new(), definition, false); state = self.close_locals(uf, scope.state, scope.locals); } - for test in module.tests { - self.test_scope = true; + for (test, proof) in module + .tests + .iter() + .map(|t| (t, false)) + .chain(module.proofs.iter().map(|t| (t, true))) + { + self.test_scope = if proof { + SpecificationScope::Proof + } else { + SpecificationScope::Test + }; let young = self.young_pool(rank); let start = self.wanted.len(); let errors_before = state.errors.len(); @@ -736,7 +745,11 @@ impl<'a> Solver<'a, '_> { vec![pair], ), ); - let generator = self.structure(uf, young, FlatType::Fun1(prng, result)); + let generator = if proof { + element + } else { + self.structure(uf, young, FlatType::Fun1(prng, result)) + }; state = self.infer_expr( uf, env, @@ -785,7 +798,7 @@ impl<'a> Solver<'a, '_> { ) .state; } - self.test_scope = false; + self.test_scope = SpecificationScope::Production; state.env = env.clone(); return state; } diff --git a/crates/nash-source/src/lib.rs b/crates/nash-source/src/lib.rs index 40625416..3ba7d624 100644 --- a/crates/nash-source/src/lib.rs +++ b/crates/nash-source/src/lib.rs @@ -1,6 +1,5 @@ use nash_region::{Located, Region}; -#[derive(Debug)] pub struct Module<'a> { pub kind: ModuleKind, pub name: Option<&'a Located<&'a str>>, @@ -15,6 +14,7 @@ pub struct Module<'a> { pub traits: &'a [&'a Located>], pub impls: &'a [&'a Located>], pub tests: Option<&'a Tests<'a>>, + pub proofs: Option<&'a Tests<'a>>, pub binops: &'a [&'a Located>], } @@ -509,3 +509,26 @@ pub enum Privacy { Public(Region), Private, } + +impl std::fmt::Debug for Module<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut debug = f.debug_struct("Module"); + debug.field("kind", &self.kind); + debug.field("name", &self.name); + debug.field("exports", &self.exports); + debug.field("docs", &self.docs); + debug.field("comments", &self.comments); + debug.field("imports", &self.imports); + debug.field("values", &self.values); + debug.field("unions", &self.unions); + debug.field("aliases", &self.aliases); + debug.field("traits", &self.traits); + debug.field("impls", &self.impls); + debug.field("tests", &self.tests); + if self.proofs.is_some() { + debug.field("proofs", &self.proofs); + } + debug.field("binops", &self.binops); + debug.finish() + } +} diff --git a/docs/overview.md b/docs/overview.md index 25983ae4..d0c0498a 100644 --- a/docs/overview.md +++ b/docs/overview.md @@ -254,3 +254,10 @@ crates/nash-driver/base/ compiler-bundled Base (Nash source) - [diagnostics.md](diagnostics.md) — error reporting architecture - [stdlib.md](stdlib.md) — `nash/base` layout, prelude, `Builtin` - [cli.md](cli.md) — commands and outputs + +## Optional proof backend + +Module-local `proof` blocks mirror tests but use universally quantified symbolic +`Proof` domains. `nash proof` verifies the compiled UPLC through PlutusCoreBlaster, +CardanoLedgerApiBlaster and Lean-blaster. Results retain model assumptions, +SMT trust and explicit execution limits, where exhaustion counts as rejection. See [proofs.md](proofs.md). diff --git a/docs/proofs.md b/docs/proofs.md new file mode 100644 index 00000000..20a45000 --- /dev/null +++ b/docs/proofs.md @@ -0,0 +1,171 @@ +# Proofs with Lean-blaster + +A module can end with a `proof` block alongside its `tests` block. Both have +private imports and access to the module's private functions. Proofs compile +through Nash's ordinary specialization, pattern compilation, recursion rewrite +and UPLC lowering. The generated UPLC is imported by PlutusCoreBlaster and +symbolically evaluated with its CEK step semantics. A local wrapper preserves +unfinished states at zero fuel, rather than using the upstream convention that +turns fuel exhaustion into an error. Lean-blaster checks the resulting +obligation. No independently translated Nash semantics are introduced. + +```elm +module Identity exposing (identity) + +identity : int -> int +identity x = x + 0 + +proof + import Proof + + test "closed fact" = do + assert (identity 42 == 42) + + prop "every integer" = + let x via Proof.int in + do + assert (identity x == x) + + prop "division by zero" fail = + let x via Proof.int in + do + assert (x / 0 == 0) + + prop "find a negative integer" fail once = + let x via Proof.int in + do + assert (x >= 0) +``` + +## Input domains + +`via` in a proof declares a symbolic domain; it does not draw from a generator. +Only references to the compiler-bundled `Proof` module are accepted (including +import aliases and unqualified exposed names). User functions, generator +expressions, `Prop.int`, and ordinary values are rejected before type checking. +The domain values in Base supply Nash type information and are never executed +by proof compilation. + +| Domain | Nash type | Lean input and UPLC encoding | +|---|---|---| +| `Proof.int` | `int` | arbitrary mathematical integer; native integer constant | +| `Proof.integer` | `Int` | arbitrary mathematical integer; `Data.I` | +| `Proof.bool` | `bool` | arbitrary Boolean; native Boolean constant | +| `Proof.bytes` | `bytes` | PlutusCore `ByteString`; native bytes constant | +| `Proof.byteString` | `Bytes` | PlutusCore `ByteString`; `Data.B` | +| `Proof.string` | `string` | arbitrary string; native string constant | +| `Proof.data` | `Data` | arbitrary Plutus Data, including malformed application encodings | +| `Proof.spendingV1/V2/V3` | `Data` | script context subject to the matching ledger spending predicate | +| `Proof.mintingV1/V2/V3` | `Data` | script context subject to the matching ledger minting predicate | + +Ledger domain versions must match the configured Plutus version. V3 domains +quantify over `ScriptContext`. V1/V2 quantify over the library's `SpendingInput` +or `MintingInput`, apply its validity predicate, and pass **only its encoded +context** to the Nash binder. They do not automatically supply datum/redeemer +arguments to a validator. For correlated datum/redeemer/context properties or +custom inductive domains, edit an exported Lean project using the library's +full input builders. Generic symbolic list/product/custom ADT domains are not +implemented in this first version. + +The validity predicates are assumptions from CardanoLedgerApiBlaster, not a +claim that Nash enforces those rules. Primitive Data domains add no ledger +assumptions. Results inherit the pinned libraries' models and limitations, +including their ByteString representation and admitted supporting declarations. + +Multiple `via` binders are quantified independently. Conditions can be written +as ordinary Nash control flow, for example: + +```elm +prop "positive input" = + let x via Proof.int in + do + if x > 0 then + assert (x + 1 > x) + else + () +``` + +There is no `assume` keyword or implicit precondition inference. Conditions +that return unit in the excluded branch mean implication for ordinary passing +properties. For `fail` properties that branch succeeds and therefore refutes +the universal failure claim. A successfully halting excluded branch cannot supply +a `fail once` rejection witness; an excluded branch that exhausts the configured +execution limit still rejects. + +## Execution and expected outcomes + +| Declaration | Obligation | +|---|---| +| `test` | closed body successfully halts | +| `test ... fail` | closed body rejects through a script error or fuel exhaustion | +| `prop` | every input in the domain successfully halts | +| `prop ... fail` | every input in the domain rejects through a script error or fuel exhaustion | +| `prop ... fail once` | find an input that rejects through a script error or fuel exhaustion | + +As with Nash validators, successful execution means halting without an error. +A returned false Boolean is not rejection. Bodies are checked as `unit` and use +ordinary strict assertion/sequence semantics without power-assert test tracing. +`within (cpu ..., mem ...)` is rejected: proof fuel is an evaluation-step limit, +not the ledger's execution cost model. Continue using `tests` for measured +budgets, labels, randomized generators and shrinking. + +Fuel is part of the property's execution limit. Exhaustion is rejection, so both +`fail` and `fail once` accept it. A passing property must halt successfully within +that limit. This is bounded execution semantics, not an unbounded termination +claim. Solver timeouts and unknown results are inconclusive and never count as +rejection witnesses. `no_witness` means every modeled input succeeds within the +configured limit. + +The command uses Lean-blaster's structured result API and emits no admitted +Nash theorem. Results are explicitly **SMT verification under the pinned +models**, not independently kernel-certified proofs. Counterexamples are the +library's textual SMT models; automatic decoding, replay and shrinking are not +yet implemented. + +## Command and generated artifacts + +```sh +nash proof . +nash verify . --match "integer identity" --fuel 10000 --timeout 30 +nash proof . --emit-only --output build/exported-proofs +nash proof . --lean-project /path/to/prebuilt/lean-project --json +``` + +`verify` is an alias for `proof`. Project/member Plutus settings are honored, +with an optional `--plutus-version v1|v2|v3` override. Match filters use the same +module/property matching rules as `nash test`. + +The command exports a portable Lake project: `lakefile.lean`, `lean-toolchain`, +`proofs.json`, and one `ProofN.lean` per selected declaration. Script flat bytes +are embedded in the Lean sources. The source is preserved on solver failures, +with stdout/stderr beside it when executed. Dependency revisions are pinned: + +- Lean 4.24.0; +- Lean-blaster `bafdd4f7976037cd7bd8e443df04af096dc5b96e`; +- PlutusCoreBlaster `41fe7eadf460dc66bef22b85656bc36408638bdc`; +- CardanoLedgerApiBlaster `3f9f7c6b6ecf0012e1397bab0a814daa36e70ba2`. + +The default command initializes and builds that project's Lake dependencies on +first use; this needs network access and an installed Lean/Lake toolchain. +`--emit-only` needs neither Lean nor an SMT solver. `--lean-project` uses an +already built project without updating it; it must provide the pinned API. +An appropriate Z3 must be on `PATH` for obligations requiring SMT reasoning. + +Defaults: 10,000 CEK steps, 30 seconds per SMT query, 120 seconds total per +property (including preprocessing). Backend setup is outside that per-property +limit. On Unix, a timed-out run terminates the Lake/Lean/solver process group. +The command exits unsuccessfully on refutation, unknown, timeout, +backend failure, no witness, or an empty selection. + +Exports refuse unrelated output directories. Repeated runs reuse the pinned Lake +project and its dependency cache, while preserving each run in its own +`run-...` directory with its Lean sources and `proofs.json`. Existing generated +sources remain available for review or direct `lake env lean run-.../ProofN.lean` use. + +`examples/proofs` demonstrates the complete flow. An optional integration test +runs the real backend: + +```sh +NASH_PROOF_LEAN_PROJECT=/path/to/prebuilt/lean-project \ + cargo test -p nash-driver --test proofs live_backend -- --ignored +``` diff --git a/docs/syntax.md b/docs/syntax.md index db6bc80b..d6ac0c5c 100644 --- a/docs/syntax.md +++ b/docs/syntax.md @@ -715,3 +715,12 @@ in Pair destructuring lowers to native UPLC `case`, including wildcard fields. It does not implicitly construct a pair or convert a tuple to a pair. + +## Proof blocks + +After ordinary declarations, a module may contain at most one `tests` block and +one `proof` block, in either order. A proof block uses the tests-block declaration +and sequencing grammar (`test`, `prop`, `fail`, `fail once`, and `let ... via ... +in do ...`). Its `via` expressions must resolve to bundled `Proof` domains; +execution budgets are rejected. Imports are private to the corresponding block. +See [proofs.md](proofs.md) for the domain and result semantics. diff --git a/examples/proofs/nash.jsonc b/examples/proofs/nash.jsonc new file mode 100644 index 00000000..37f4f508 --- /dev/null +++ b/examples/proofs/nash.jsonc @@ -0,0 +1 @@ +{"type":"application","source-directories":["src"]} diff --git a/examples/proofs/src/Main.nash b/examples/proofs/src/Main.nash new file mode 100644 index 00000000..1d9782f2 --- /dev/null +++ b/examples/proofs/src/Main.nash @@ -0,0 +1,26 @@ +module Main exposing (addZero) + +addZero : int -> int +addZero x = x + 0 + +tests + test "concrete identity" = do + assert (addZero 42 == 42) + +proof + import Proof + + prop "integer identity" = + let x via Proof.int in + do + assert (addZero x == x) + + prop "division by zero always fails" fail = + let x via Proof.int in + do + assert (x / 0 == 0) + + prop "find a negative integer" fail once = + let x via Proof.int in + do + assert (x >= 0) From ee37f51037a27e2ce73b4bcd4972fada732397d9 Mon Sep 17 00:00:00 2001 From: RSoulatIOHK Date: Thu, 1 Oct 2026 11:37:53 +0200 Subject: [PATCH 2/7] style: format existing proof examples and Base test fixtures --- crates/nash-driver/base/src/Proof.nash | 31 +- .../fixtures/base-traits/DataConversions.nash | 468 +++++-- .../tests/fixtures/base-traits/Equality.nash | 213 ++-- .../fixtures/base-traits/IntegerMath.nash | 1105 +++++++++++++---- examples/proofs/src/Main.nash | 34 +- examples/vesting/app/src/Vesting.nash | 38 +- examples/vesting/app/src/VestingParam.nash | 48 +- 7 files changed, 1476 insertions(+), 461 deletions(-) diff --git a/crates/nash-driver/base/src/Proof.nash b/crates/nash-driver/base/src/Proof.nash index d339d427..77bd1745 100644 --- a/crates/nash-driver/base/src/Proof.nash +++ b/crates/nash-driver/base/src/Proof.nash @@ -1,58 +1,87 @@ -module Proof exposing (int, integer, bool, bytes, byteString, string, data, spendingV1, spendingV2, spendingV3, mintingV1, mintingV2, mintingV3) +module Proof exposing + ( int + , integer + , bool + , bytes + , byteString + , string + , data + , spendingV1 + , spendingV2 + , spendingV3 + , mintingV1 + , mintingV2 + , mintingV3 + ) + {-| Symbolic input domains for Lean-blaster verification. -} + import Builtin import Primitive + {-| All native integers. -} int : int int = 0 + {-| All Data-represented integers. -} integer : Int integer = Builtin.iData 0 + {-| All native Booleans. -} bool : bool bool = Primitive.False + {-| All native bytestrings in the PlutusCore model. -} bytes : bytes bytes = #"" + {-| All Data-represented bytestrings in the PlutusCore model. -} byteString : Bytes byteString = Builtin.bData #"" + {-| All native strings. -} string : string string = "" + {-| All Plutus Data values, including malformed application encodings. -} data : Data data = Primitive.coerce (Builtin.iData 0) + {-| Contexts satisfying the CardanoLedgerApi Plutus V1 spending validity predicate. -} spendingV1 : Data spendingV1 = data + {-| Contexts satisfying the CardanoLedgerApi Plutus V2 spending validity predicate. -} spendingV2 : Data spendingV2 = data + {-| Contexts satisfying the CardanoLedgerApi Plutus V3 spending validity predicate. -} spendingV3 : Data spendingV3 = data + {-| Contexts satisfying the CardanoLedgerApi Plutus V1 minting validity predicate. -} mintingV1 : Data mintingV1 = data + {-| Contexts satisfying the CardanoLedgerApi Plutus V2 minting validity predicate. -} mintingV2 : Data mintingV2 = data + {-| Contexts satisfying the CardanoLedgerApi Plutus V3 minting validity predicate. -} mintingV3 : Data mintingV3 = data diff --git a/crates/nash-driver/tests/fixtures/base-traits/DataConversions.nash b/crates/nash-driver/tests/fixtures/base-traits/DataConversions.nash index 828171a6..a1a65360 100644 --- a/crates/nash-driver/tests/fixtures/base-traits/DataConversions.nash +++ b/crates/nash-driver/tests/fixtures/base-traits/DataConversions.nash @@ -1,207 +1,425 @@ module Main exposing (..) -type count = Count int + +type count + = Count int + impl ToData count where toData (Count n) = toData n + impl FromData count where fromData raw = Count (fromData raw) + impl Decode count where decode raw = do n <- decode raw Some (Count n) + asInt : Data -> option int asInt = decode + + asBytes : Data -> option bytes asBytes = decode + + asString : Data -> option string asString = decode + + asBool : Data -> option bool asBool = decode + + asUnit : Data -> option unit asUnit = decode + + asList : Data -> option (list int) asList = decode + + asBigList : Data -> option (List Int) asBigList = decode + + asMap : Data -> option (Map Int Bytes) asMap = decode + + asOption : Data -> option (option int) asOption = decode + + asBigOption : Data -> option (Option Int) asBigOption = decode + + asData : Data -> option Data asData = decode + + unchecked : Data -> Int unchecked = fromData + + checked : Data -> Int checked = validate + + little : Data -> int little = fromData + + text : Data -> string text = fromData + + countValue : count countValue = fromData (I 42) + + countDecoded : option count countDecoded = decode (I 42) -entries = [Builtin.mkPairData (I 1) (B #"aa"), Builtin.mkPairData (I 1) (B #"bb")] + +entries = + [ Builtin.mkPairData (I 1) (B #"aa"), Builtin.mkPairData (I 1) (B #"bb") ] + number : int number = 42 + + rawBytes : bytes rawBytes = #"aaff" + + yes : bool yes = True + + no : bool no = False + + numbers : list int -numbers = [1, 2] +numbers = [ 1, 2 ] + + bigOne : Int bigOne = 1 + + firstBytes : Bytes firstBytes = #"aa" + + secondBytes : Bytes secondBytes = #"bb" + + three : int three = 3 + fromList : Data -> list int fromList = fromData + + fromOption : Data -> option int fromOption = fromData + + fromBool : Data -> bool fromBool = fromData + + fromUnit : Data -> unit fromUnit = fromData + + fromBytes : Data -> bytes fromBytes = fromData + + bigBool : Data -> option Bool bigBool = decode + tests - test "little list unpacking" = do - assert (fromList (toData numbers) == numbers) - test "little option unpacking" = do - assert (fromOption (toData (Some number)) == Some number) - test "little bool unpacking" = do - assert (fromBool (toData yes) == yes) - test "little unit unpacking" = do - assert (fromUnit (toData ()) == ()) - test "little bytes unpacking" = do - assert (fromBytes (toData rawBytes) == rawBytes) - test "Big Bool optional decoding" = do - assert (bigBool (toData yes) == Some Bool.True) - test "Big Bool optional rejection" = do - assert (bigBool (Builtin.constrData 0 [I 1]) == None) - test "little string unpacking rejects bad UTF8" fail = do - let _ = text (B #"80") in () - test "little scalar conversions 1" = do - assert (toData number == I 42) - test "little scalar conversions 2" = do - assert (little (I 42) == 42) - test "little scalar conversions 3" = do - assert (asBytes (toData rawBytes) == Some #"aaff") - test "little scalar conversions 4" = do - assert (asString (toData "é") == Some "é") - test "little scalar conversions 5" = do - assert (text (toData "é") == "é") - test "little scalar conversions 6" = do - assert (asBool (toData yes) == Some True) - test "little scalar conversions 7" = do - assert (asBool (toData no) == Some False) - test "little scalar conversions 8" = do - assert (asUnit (toData ()) == Some ()) - test "user little conversions and ordinary option composition 1" = do - assert (toData countValue == I 42) - test "user little conversions and ordinary option composition 2" = do - assert (case countDecoded of - Some (Count n) -> n == 42 - None -> False) - test "Big blanket conversion remains unchecked 1" = do - assert (toData (unchecked (B #"aa")) == B #"aa") - test "Big blanket conversion remains unchecked 2" = do - assert (toData (checked (I 42)) == I 42) - test "independent validation fails" fail = do - let _ = checked (B #"aa") in () - test "little unpacking fails" fail = do - let _ = little (B #"aa") in () - test "scalar decode wrong shapes 1" = do - assert (asInt (B #"aa") == None) - test "scalar decode wrong shapes 2" = do - assert (asBytes (I 1) == None) - test "scalar decode wrong shapes 3" = do - assert (asString (I 1) == None) - test "scalar decode wrong shapes 4" = do - assert (asInt (List []) == None) - test "scalar decode wrong shapes 5" = do - assert (asInt (Map []) == None) - test "scalar decode wrong shapes 6" = do - assert (asInt (Builtin.constrData 0 []) == None) - test "boolean and unit exact shape 1" = do - assert (asBool (Builtin.constrData 2 []) == None) - test "boolean and unit exact shape 2" = do - assert (asBool (Builtin.constrData 0 [I 1]) == None) - test "boolean and unit exact shape 3" = do - assert (asBool (I 0) == None) - test "boolean and unit exact shape 4" = do - assert (asUnit (Builtin.constrData 1 []) == None) - test "boolean and unit exact shape 5" = do - assert (asUnit (Builtin.constrData 0 [I 1]) == None) - test "UTF8 accepts boundaries 1" = do - assert (asString (B #"00c280e0a080eda080") == None) - test "UTF8 accepts boundaries 2" = do - assert (asString (B #"00c280e0a080ed9fbff0908080f48fbfbf") /= None) - test "UTF8 rejects malformed sequences 1" = do - assert (asString (B #"80") == None) - test "UTF8 rejects malformed sequences 2" = do - assert (asString (B #"c080") == None) - test "UTF8 rejects malformed sequences 3" = do - assert (asString (B #"c2") == None) - test "UTF8 rejects malformed sequences 4" = do - assert (asString (B #"e08080") == None) - test "UTF8 rejects malformed sequences 5" = do - assert (asString (B #"eda080") == None) - test "UTF8 rejects malformed sequences 6" = do - assert (asString (B #"f0808080") == None) - test "UTF8 rejects malformed sequences 7" = do - assert (asString (B #"f4908080") == None) - test "UTF8 rejects malformed sequences 8" = do - assert (asString (B #"f5808080") == None) - test "little collection conversion 1" = do - assert (asList (toData numbers) == Some [1, 2]) - test "little collection conversion 2" = do - assert (asList (List []) == Some []) - test "little collection conversion 3" = do - assert (asList (List [I 1, B #"aa"]) == None) - test "little collection conversion 4" = do - assert (asList (I 1) == None) - test "Big collection decode validates children 1" = do - assert (asBigList (List [I 1, I 2]) == Some (lift [1, 2])) - test "Big collection decode validates children 2" = do - assert (asBigList (List [B #"aa"]) == None) - test "Big collection decode validates children 3" = do - assert (asMap (Map entries) == Some (Builtin.mapData [Builtin.mkPairData bigOne firstBytes, Builtin.mkPairData bigOne secondBytes])) - test "Big collection decode validates children 4" = do - assert (asMap (Map [Builtin.mkPairData (B #"aa") (B #"bb")]) == None) - test "Big collection decode validates children 5" = do - assert (asMap (Map [Builtin.mkPairData (I 1) (I 2)]) == None) - test "optional values exact shape 1" = do - assert (asOption (toData (Some three)) == Some (Some 3)) - test "optional values exact shape 2" = do - assert (asOption (Builtin.constrData 1 []) == Some None) - test "optional values exact shape 3" = do - assert (asOption (Builtin.constrData 0 []) == None) - test "optional values exact shape 4" = do - assert (asOption (Builtin.constrData 0 [B #"aa"]) == None) - test "optional values exact shape 5" = do - assert (asOption (Builtin.constrData 1 [I 1]) == None) - test "optional values exact shape 6" = do - assert (asBigOption (Builtin.constrData 0 [I 3]) == Some (Option.Some 3)) - test "optional values exact shape 7" = do - assert (asBigOption (Builtin.constrData 0 [B #"aa"]) == None) - test "Data decode preserves any shape 1" = do - assert (asData (Map entries) == Some (Map entries)) + test "little list unpacking" = + do + assert (fromList (toData numbers) == numbers) + + test "little option unpacking" = + do + assert (fromOption (toData (Some number)) == Some number) + + test "little bool unpacking" = + do + assert (fromBool (toData yes) == yes) + + test "little unit unpacking" = + do + assert (fromUnit (toData ()) == ()) + + test "little bytes unpacking" = + do + assert (fromBytes (toData rawBytes) == rawBytes) + + test "Big Bool optional decoding" = + do + assert (bigBool (toData yes) == Some Bool.True) + + test "Big Bool optional rejection" = + do + assert (bigBool (Builtin.constrData 0 [ I 1 ]) == None) + + test "little string unpacking rejects bad UTF8" fail = + do + let + _ = text (B #"80") + in + () + + test "little scalar conversions 1" = + do + assert (toData number == I 42) + + test "little scalar conversions 2" = + do + assert (little (I 42) == 42) + + test "little scalar conversions 3" = + do + assert (asBytes (toData rawBytes) == Some #"aaff") + + test "little scalar conversions 4" = + do + assert (asString (toData "é") == Some "é") + + test "little scalar conversions 5" = + do + assert (text (toData "é") == "é") + + test "little scalar conversions 6" = + do + assert (asBool (toData yes) == Some True) + + test "little scalar conversions 7" = + do + assert (asBool (toData no) == Some False) + + test "little scalar conversions 8" = + do + assert (asUnit (toData ()) == Some ()) + + test "user little conversions and ordinary option composition 1" = + do + assert (toData countValue == I 42) + + test "user little conversions and ordinary option composition 2" = + do + assert + ( + case countDecoded of + Some (Count n) -> + n == 42 + + None -> + False + ) + + test "Big blanket conversion remains unchecked 1" = + do + assert (toData (unchecked (B #"aa")) == B #"aa") + + test "Big blanket conversion remains unchecked 2" = + do + assert (toData (checked (I 42)) == I 42) + + test "independent validation fails" fail = + do + let + _ = checked (B #"aa") + in + () + + test "little unpacking fails" fail = + do + let + _ = little (B #"aa") + in + () + + test "scalar decode wrong shapes 1" = + do + assert (asInt (B #"aa") == None) + + test "scalar decode wrong shapes 2" = + do + assert (asBytes (I 1) == None) + + test "scalar decode wrong shapes 3" = + do + assert (asString (I 1) == None) + + test "scalar decode wrong shapes 4" = + do + assert (asInt (List []) == None) + + test "scalar decode wrong shapes 5" = + do + assert (asInt (Map []) == None) + + test "scalar decode wrong shapes 6" = + do + assert (asInt (Builtin.constrData 0 []) == None) + + test "boolean and unit exact shape 1" = + do + assert (asBool (Builtin.constrData 2 []) == None) + + test "boolean and unit exact shape 2" = + do + assert (asBool (Builtin.constrData 0 [ I 1 ]) == None) + + test "boolean and unit exact shape 3" = + do + assert (asBool (I 0) == None) + + test "boolean and unit exact shape 4" = + do + assert (asUnit (Builtin.constrData 1 []) == None) + + test "boolean and unit exact shape 5" = + do + assert (asUnit (Builtin.constrData 0 [ I 1 ]) == None) + + test "UTF8 accepts boundaries 1" = + do + assert (asString (B #"00c280e0a080eda080") == None) + + test "UTF8 accepts boundaries 2" = + do + assert (asString (B #"00c280e0a080ed9fbff0908080f48fbfbf") /= None) + + test "UTF8 rejects malformed sequences 1" = + do + assert (asString (B #"80") == None) + + test "UTF8 rejects malformed sequences 2" = + do + assert (asString (B #"c080") == None) + + test "UTF8 rejects malformed sequences 3" = + do + assert (asString (B #"c2") == None) + + test "UTF8 rejects malformed sequences 4" = + do + assert (asString (B #"e08080") == None) + + test "UTF8 rejects malformed sequences 5" = + do + assert (asString (B #"eda080") == None) + + test "UTF8 rejects malformed sequences 6" = + do + assert (asString (B #"f0808080") == None) + + test "UTF8 rejects malformed sequences 7" = + do + assert (asString (B #"f4908080") == None) + + test "UTF8 rejects malformed sequences 8" = + do + assert (asString (B #"f5808080") == None) + + test "little collection conversion 1" = + do + assert (asList (toData numbers) == Some [ 1, 2 ]) + + test "little collection conversion 2" = + do + assert (asList (List []) == Some []) + + test "little collection conversion 3" = + do + assert (asList (List [ I 1, B #"aa" ]) == None) + + test "little collection conversion 4" = + do + assert (asList (I 1) == None) + + test "Big collection decode validates children 1" = + do + assert (asBigList (List [ I 1, I 2 ]) == Some (lift [ 1, 2 ])) + + test "Big collection decode validates children 2" = + do + assert (asBigList (List [ B #"aa" ]) == None) + + test "Big collection decode validates children 3" = + do + assert + ( + asMap (Map entries) == + Some + ( + Builtin.mapData + [ Builtin.mkPairData bigOne firstBytes + , Builtin.mkPairData bigOne secondBytes + ] + ) + ) + + test "Big collection decode validates children 4" = + do + assert + (asMap (Map [ Builtin.mkPairData (B #"aa") (B #"bb") ]) == None) + + test "Big collection decode validates children 5" = + do + assert (asMap (Map [ Builtin.mkPairData (I 1) (I 2) ]) == None) + + test "optional values exact shape 1" = + do + assert (asOption (toData (Some three)) == Some (Some 3)) + + test "optional values exact shape 2" = + do + assert (asOption (Builtin.constrData 1 []) == Some None) + + test "optional values exact shape 3" = + do + assert (asOption (Builtin.constrData 0 []) == None) + + test "optional values exact shape 4" = + do + assert (asOption (Builtin.constrData 0 [ B #"aa" ]) == None) + + test "optional values exact shape 5" = + do + assert (asOption (Builtin.constrData 1 [ I 1 ]) == None) + + test "optional values exact shape 6" = + do + assert + ( + asBigOption (Builtin.constrData 0 [ I 3 ]) == + Some (Option.Some 3) + ) + + test "optional values exact shape 7" = + do + assert (asBigOption (Builtin.constrData 0 [ B #"aa" ]) == None) + + test "Data decode preserves any shape 1" = + do + assert (asData (Map entries) == Some (Map entries)) diff --git a/crates/nash-driver/tests/fixtures/base-traits/Equality.nash b/crates/nash-driver/tests/fixtures/base-traits/Equality.nash index 3b359afa..301cc8fd 100644 --- a/crates/nash-driver/tests/fixtures/base-traits/Equality.nash +++ b/crates/nash-driver/tests/fixtures/base-traits/Equality.nash @@ -1,92 +1,149 @@ module Main exposing (..) -type Token = Token Int + +type Token + = Token Int + + bigList : List Int -bigList = lift [Builtin.iData 1, Builtin.iData 2] +bigList = lift [ Builtin.iData 1, Builtin.iData 2 ] + + bigMap : Map Int Bytes -bigMap = Builtin.mapData [Builtin.mkPairData (Builtin.iData 1) (Builtin.bData #"aa")] +bigMap = + Builtin.mapData + [ Builtin.mkPairData (Builtin.iData 1) (Builtin.bData #"aa") ] + nativeMap : map Int Int -nativeMap = [Builtin.mkPairData 1 2, Builtin.mkPairData 1 3] +nativeMap = [ Builtin.mkPairData 1 2, Builtin.mkPairData 1 3 ] + + reversedMap : map Int Int -reversedMap = [Builtin.mkPairData 1 3, Builtin.mkPairData 1 2] +reversedMap = [ Builtin.mkPairData 1 3, Builtin.mkPairData 1 2 ] + + emptyMap : map Int Int emptyMap = [] + + sameGeneric : Eq 'a => 'a -> 'a -> bool sameGeneric = eq + + mixedMap : map int (list Data) -mixedMap = [Builtin.unConstrData (Builtin.constrData 7 [])] +mixedMap = [ Builtin.unConstrData (Builtin.constrData 7 []) ] + tests - test "boolean equality and ordering truth tables" = do - assert (eq False False && eq True True) - assert (neq False True && neq True False) - assert (compare False False == EQ) - assert (compare False True == LT) - assert (compare True False == GT) - assert (compare True True == EQ) - test "native map alias equality" = do - assert (sameGeneric nativeMap nativeMap) - assert (nativeMap /= reversedMap) - assert (emptyMap == emptyMap) - assert (emptyMap /= nativeMap) - test "native map operation results" = do - assert (Map.union nativeMap emptyMap == nativeMap) - assert (Map.toList nativeMap == [Builtin.mkPairData 1 2, Builtin.mkPairData 1 3]) - test "mixed native map operations" = do - assert (Map.get 7 mixedMap == Some []) - assert (Map.remove 7 mixedMap == []) - test "primitive equality" = do - assert (1 == 1) - assert (1 /= 2) - assert (#"aa" == #"aa") - assert (#"aa" /= #"ab") - assert ("a" == "a") - assert ("a" /= "b") - assert (True /= False) - assert (() == ()) - test "list and pair equality" = do - assert ([1, 2] == [1, 2]) - assert ([1] /= [1, 2]) - assert ([1, 2] /= [1]) - assert ([1, 2] /= [1, 3]) - assert (Builtin.unConstrData (Builtin.constrData 1 []) == Builtin.unConstrData (Builtin.constrData 1 [])) - assert (Builtin.unConstrData (Builtin.constrData 1 []) /= Builtin.unConstrData (Builtin.constrData 2 [])) - test "Big equality" = do - assert (Token 1 == Token 1) - assert (Token 1 /= Token 2) - assert (bigList == lift [Builtin.iData 1, Builtin.iData 2]) - assert (bigMap == bigMap) - assert (I 1 /= B #"01") - assert (Bool.True /= Bool.False) - assert (Unit.Unit == Unit.Unit) - assert (Option.Some (Token 1) /= Option.None) - assert (Ordering.LT /= Ordering.GT) - test "little twin equality" = do - assert (Some 1 == Some 1) - assert (Some 1 /= None) - assert (LT /= GT) - test "ordering and defaults" = do - assert (compare 1 2 == LT) - assert (compare 2 2 == EQ) - assert (compare 3 2 == GT) - assert (compare #"00ff" #"01" == LT) - assert (compare "a" "b" == LT) - test "boolean and list ordering" = do - assert (compare False True == LT) - assert (compare () () == EQ) - assert (compare [1, 2] [1, 3] == LT) - assert (compare [1] [1, 0] == LT) - assert (compare [1, 0] [1] == GT) - assert (Int.compare (Builtin.iData 2) (Builtin.iData 1) == GT) - assert (Bytes.compare (Builtin.bData #"aa") (Builtin.bData #"bb") == LT) - test "ordering operators and bounds" = do - assert (1 < 2 && 2 <= 2 && 3 > 2 && 3 >= 3) - assert (max 1 2 == 2 && min 1 2 == 1) - test "tuple equality and ordering" = do - assert ((1, True) == (1, True)) - assert ((1, True, #"aa") /= (1, False, #"aa")) - assert ((1, 2, 3, 4) /= (1, 2, 3, 5)) - assert (compare (1, 2) (1, 3) == LT) - assert (compare (1, 2, 3) (1, 2, 2) == GT) - assert (compare (1, 2, 3, 4) (1, 2, 3, 4) == EQ) + test "boolean equality and ordering truth tables" = + do + assert (eq False False && eq True True) + assert (neq False True && neq True False) + assert (compare False False == EQ) + assert (compare False True == LT) + assert (compare True False == GT) + assert (compare True True == EQ) + + test "native map alias equality" = + do + assert (sameGeneric nativeMap nativeMap) + assert (nativeMap /= reversedMap) + assert (emptyMap == emptyMap) + assert (emptyMap /= nativeMap) + + test "native map operation results" = + do + assert (Map.union nativeMap emptyMap == nativeMap) + assert + ( + Map.toList nativeMap == + [ Builtin.mkPairData 1 2, Builtin.mkPairData 1 3 ] + ) + + test "mixed native map operations" = + do + assert (Map.get 7 mixedMap == Some []) + assert (Map.remove 7 mixedMap == []) + + test "primitive equality" = + do + assert (1 == 1) + assert (1 /= 2) + assert (#"aa" == #"aa") + assert (#"aa" /= #"ab") + assert ("a" == "a") + assert ("a" /= "b") + assert (True /= False) + assert (() == ()) + + test "list and pair equality" = + do + assert ([ 1, 2 ] == [ 1, 2 ]) + assert ([ 1 ] /= [ 1, 2 ]) + assert ([ 1, 2 ] /= [ 1 ]) + assert ([ 1, 2 ] /= [ 1, 3 ]) + assert + ( + Builtin.unConstrData (Builtin.constrData 1 []) == + Builtin.unConstrData (Builtin.constrData 1 []) + ) + assert + ( + Builtin.unConstrData (Builtin.constrData 1 []) /= + Builtin.unConstrData (Builtin.constrData 2 []) + ) + + test "Big equality" = + do + assert (Token 1 == Token 1) + assert (Token 1 /= Token 2) + assert (bigList == lift [ Builtin.iData 1, Builtin.iData 2 ]) + assert (bigMap == bigMap) + assert (I 1 /= B #"01") + assert (Bool.True /= Bool.False) + assert (Unit.Unit == Unit.Unit) + assert (Option.Some (Token 1) /= Option.None) + assert (Ordering.LT /= Ordering.GT) + + test "little twin equality" = + do + assert (Some 1 == Some 1) + assert (Some 1 /= None) + assert (LT /= GT) + + test "ordering and defaults" = + do + assert (compare 1 2 == LT) + assert (compare 2 2 == EQ) + assert (compare 3 2 == GT) + assert (compare #"00ff" #"01" == LT) + assert (compare "a" "b" == LT) + + test "boolean and list ordering" = + do + assert (compare False True == LT) + assert (compare () () == EQ) + assert (compare [ 1, 2 ] [ 1, 3 ] == LT) + assert (compare [ 1 ] [ 1, 0 ] == LT) + assert (compare [ 1, 0 ] [ 1 ] == GT) + assert (Int.compare (Builtin.iData 2) (Builtin.iData 1) == GT) + assert + ( + Bytes.compare (Builtin.bData #"aa") (Builtin.bData #"bb") == + LT + ) + + test "ordering operators and bounds" = + do + assert (1 < 2 && 2 <= 2 && 3 > 2 && 3 >= 3) + assert (max 1 2 == 2 && min 1 2 == 1) + + test "tuple equality and ordering" = + do + assert (( 1, True ) == ( 1, True )) + assert (( 1, True, #"aa" ) /= ( 1, False, #"aa" )) + assert (( 1, 2, 3, 4 ) /= ( 1, 2, 3, 5 )) + assert (compare ( 1, 2 ) ( 1, 3 ) == LT) + assert (compare ( 1, 2, 3 ) ( 1, 2, 2 ) == GT) + assert (compare ( 1, 2, 3, 4 ) ( 1, 2, 3, 4 ) == EQ) diff --git a/crates/nash-driver/tests/fixtures/base-traits/IntegerMath.nash b/crates/nash-driver/tests/fixtures/base-traits/IntegerMath.nash index 5d641cc8..c9ebc0c6 100644 --- a/crates/nash-driver/tests/fixtures/base-traits/IntegerMath.nash +++ b/crates/nash-driver/tests/fixtures/base-traits/IntegerMath.nash @@ -1,232 +1,893 @@ module Main exposing (..) + big : Int big = 48 + + bigGcd : Int bigGcd = 6 + + bigLcm : Int bigLcm = 144 + tests - test "gcd mixed inputs" = do - assert (Int.gcd big 18 == 6) - test "gcd zero" = do - assert (Int.gcd 0 0 == 0) - test "gcd signs" = do - assert (Int.gcd -48 -18 == 6) - test "lcm mixed inputs" = do - assert (Int.lcm 18 big == 144) - test "lcm zero" = do - assert (Int.lcm 0 0 == 0) - test "lcm signs" = do - assert (Int.lcm -48 18 == 144) - test "sqrt zero" = do - assert (Int.isqrt 0 == 0) - test "sqrt one" = do - assert (Int.isqrt 1 == 1) - test "sqrt rounds down" = do - assert (Int.isqrt big == 6) - test "sqrt exact" = do - assert (Int.isqrt 49 == 7) - test "sqrt rejects negative" fail = do - let _ = Int.isqrt -1 in () - test "oracle gcd 0" = do - assert (Int.gcd (0) (0) == 0) - test "oracle lcm 0" = do - assert (Int.lcm (0) (0) == 0) - test "oracle isGcd 0" = do - assert (Int.isGcd (0) (0) 0) - test "oracle isGcd rejects 0" = do - assert (not (Int.isGcd (0) (0) 1)) - test "oracle isLcm 0" = do - assert (Int.isLcm (0) (0) 0) - test "oracle isLcm rejects 0" = do - assert (not (Int.isLcm (0) (0) 1)) - test "oracle gcd 1" = do - assert (Int.gcd (0) (-18) == 18) - test "oracle lcm 1" = do - assert (Int.lcm (0) (-18) == 0) - test "oracle isGcd 1" = do - assert (Int.isGcd (0) (-18) 18) - test "oracle isGcd rejects 1" = do - assert (not (Int.isGcd (0) (-18) 19)) - test "oracle isLcm 1" = do - assert (Int.isLcm (0) (-18) 0) - test "oracle isLcm rejects 1" = do - assert (not (Int.isLcm (0) (-18) 1)) - test "oracle gcd 2" = do - assert (Int.gcd (-48) (18) == 6) - test "oracle lcm 2" = do - assert (Int.lcm (-48) (18) == 144) - test "oracle isGcd 2" = do - assert (Int.isGcd (-48) (18) 6) - test "oracle isGcd rejects 2" = do - assert (not (Int.isGcd (-48) (18) 7)) - test "oracle isLcm 2" = do - assert (Int.isLcm (-48) (18) 144) - test "oracle isLcm rejects 2" = do - assert (not (Int.isLcm (-48) (18) 145)) - test "oracle gcd 3" = do - assert (Int.gcd (48) (-18) == 6) - test "oracle lcm 3" = do - assert (Int.lcm (48) (-18) == 144) - test "oracle isGcd 3" = do - assert (Int.isGcd (48) (-18) 6) - test "oracle isGcd rejects 3" = do - assert (not (Int.isGcd (48) (-18) 7)) - test "oracle isLcm 3" = do - assert (Int.isLcm (48) (-18) 144) - test "oracle isLcm rejects 3" = do - assert (not (Int.isLcm (48) (-18) 145)) - test "oracle gcd 4" = do - assert (Int.gcd (-48) (-18) == 6) - test "oracle lcm 4" = do - assert (Int.lcm (-48) (-18) == 144) - test "oracle isGcd 4" = do - assert (Int.isGcd (-48) (-18) 6) - test "oracle isGcd rejects 4" = do - assert (not (Int.isGcd (-48) (-18) 7)) - test "oracle isLcm 4" = do - assert (Int.isLcm (-48) (-18) 144) - test "oracle isLcm rejects 4" = do - assert (not (Int.isLcm (-48) (-18) 145)) - test "oracle gcd 5" = do - assert (Int.gcd (35) (64) == 1) - test "oracle lcm 5" = do - assert (Int.lcm (35) (64) == 2240) - test "oracle isGcd 5" = do - assert (Int.isGcd (35) (64) 1) - test "oracle isGcd rejects 5" = do - assert (not (Int.isGcd (35) (64) 2)) - test "oracle isLcm 5" = do - assert (Int.isLcm (35) (64) 2240) - test "oracle isLcm rejects 5" = do - assert (not (Int.isLcm (35) (64) 2241)) - test "oracle gcd 6" = do - assert (Int.gcd ((Int.fromBytes True #"1500000000000000000000000000000015")) ((Int.fromBytes True #"2300000000000000000000000000000023")) == (Int.fromBytes True #"0700000000000000000000000000000007")) - test "oracle lcm 6" = do - assert (Int.lcm ((Int.fromBytes True #"1500000000000000000000000000000015")) ((Int.fromBytes True #"2300000000000000000000000000000023")) == (Int.fromBytes True #"6900000000000000000000000000000069")) - test "oracle isGcd 6" = do - assert (Int.isGcd ((Int.fromBytes True #"1500000000000000000000000000000015")) ((Int.fromBytes True #"2300000000000000000000000000000023")) (Int.fromBytes True #"0700000000000000000000000000000007")) - test "oracle isGcd rejects 6" = do - assert (not (Int.isGcd ((Int.fromBytes True #"1500000000000000000000000000000015")) ((Int.fromBytes True #"2300000000000000000000000000000023")) (Int.fromBytes True #"0700000000000000000000000000000008"))) - test "oracle isLcm 6" = do - assert (Int.isLcm ((Int.fromBytes True #"1500000000000000000000000000000015")) ((Int.fromBytes True #"2300000000000000000000000000000023")) (Int.fromBytes True #"6900000000000000000000000000000069")) - test "oracle isLcm rejects 6" = do - assert (not (Int.isLcm ((Int.fromBytes True #"1500000000000000000000000000000015")) ((Int.fromBytes True #"2300000000000000000000000000000023")) (Int.fromBytes True #"690000000000000000000000000000006a"))) - test "oracle sqrt 0" = do - assert (Int.isqrt 0 == 0) - test "oracle isSqrt 0" = do - assert (Int.isSqrt 0 0) - test "oracle isSqrt rejects 0" = do - assert (not (Int.isSqrt 0 1)) - test "oracle sqrt 1" = do - assert (Int.isqrt 1 == 1) - test "oracle isSqrt 1" = do - assert (Int.isSqrt 1 1) - test "oracle isSqrt rejects 1" = do - assert (not (Int.isSqrt 1 2)) - test "oracle sqrt 2" = do - assert (Int.isqrt 2 == 1) - test "oracle isSqrt 2" = do - assert (Int.isSqrt 2 1) - test "oracle isSqrt rejects 2" = do - assert (not (Int.isSqrt 2 2)) - test "oracle sqrt 3" = do - assert (Int.isqrt 3 == 1) - test "oracle isSqrt 3" = do - assert (Int.isSqrt 3 1) - test "oracle isSqrt rejects 3" = do - assert (not (Int.isSqrt 3 2)) - test "oracle sqrt 4" = do - assert (Int.isqrt 4 == 2) - test "oracle isSqrt 4" = do - assert (Int.isSqrt 4 2) - test "oracle isSqrt rejects 4" = do - assert (not (Int.isSqrt 4 3)) - test "oracle sqrt 5" = do - assert (Int.isqrt 15 == 3) - test "oracle isSqrt 5" = do - assert (Int.isSqrt 15 3) - test "oracle isSqrt rejects 5" = do - assert (not (Int.isSqrt 15 4)) - test "oracle sqrt 6" = do - assert (Int.isqrt 16 == 4) - test "oracle isSqrt 6" = do - assert (Int.isSqrt 16 4) - test "oracle isSqrt rejects 6" = do - assert (not (Int.isSqrt 16 5)) - test "oracle sqrt 7" = do - assert (Int.isqrt 17 == 4) - test "oracle isSqrt 7" = do - assert (Int.isSqrt 17 4) - test "oracle isSqrt rejects 7" = do - assert (not (Int.isSqrt 17 5)) - test "oracle sqrt 8" = do - assert (Int.isqrt (Int.fromBytes True #"0100000000000000020000000000000000") == 18446744073709551616) - test "oracle isSqrt 8" = do - assert (Int.isSqrt (Int.fromBytes True #"0100000000000000020000000000000000") 18446744073709551616) - test "oracle isSqrt rejects 8" = do - assert (not (Int.isSqrt (Int.fromBytes True #"0100000000000000020000000000000000") 18446744073709551617)) - test "oracle sqrt 9" = do - assert (Int.isqrt (Int.fromBytes True #"0100000000000000020000000000000001") == 18446744073709551617) - test "oracle isSqrt 9" = do - assert (Int.isSqrt (Int.fromBytes True #"0100000000000000020000000000000001") 18446744073709551617) - test "oracle isSqrt rejects 9" = do - assert (not (Int.isSqrt (Int.fromBytes True #"0100000000000000020000000000000001") 18446744073709551618)) - test "oracle sqrt 10" = do - assert (Int.isqrt (Int.fromBytes True #"0100000000000000020000000000000002") == 18446744073709551617) - test "oracle isSqrt 10" = do - assert (Int.isSqrt (Int.fromBytes True #"0100000000000000020000000000000002") 18446744073709551617) - test "oracle isSqrt rejects 10" = do - assert (not (Int.isSqrt (Int.fromBytes True #"0100000000000000020000000000000002") 18446744073709551618)) - test "oracle sqrt 11" = do - assert (Int.isqrt (Int.fromBytes True #"0100000000000000040000000000000003") == 18446744073709551617) - test "oracle isSqrt 11" = do - assert (Int.isSqrt (Int.fromBytes True #"0100000000000000040000000000000003") 18446744073709551617) - test "oracle isSqrt rejects 11" = do - assert (not (Int.isSqrt (Int.fromBytes True #"0100000000000000040000000000000003") 18446744073709551618)) - test "oracle sqrt 12" = do - assert (Int.isqrt (Int.fromBytes True #"01000000000000000000000000000000f600000000000000000000000000003b18") == (Int.fromBytes True #"010000000000000000000000000000007a")) - test "oracle isSqrt 12" = do - assert (Int.isSqrt (Int.fromBytes True #"01000000000000000000000000000000f600000000000000000000000000003b18") (Int.fromBytes True #"010000000000000000000000000000007a")) - test "oracle isSqrt rejects 12" = do - assert (not (Int.isSqrt (Int.fromBytes True #"01000000000000000000000000000000f600000000000000000000000000003b18") (Int.fromBytes True #"010000000000000000000000000000007b"))) - test "oracle sqrt 13" = do - assert (Int.isqrt (Int.fromBytes True #"01000000000000000000000000000000f600000000000000000000000000003b19") == (Int.fromBytes True #"010000000000000000000000000000007b")) - test "oracle isSqrt 13" = do - assert (Int.isSqrt (Int.fromBytes True #"01000000000000000000000000000000f600000000000000000000000000003b19") (Int.fromBytes True #"010000000000000000000000000000007b")) - test "oracle isSqrt rejects 13" = do - assert (not (Int.isSqrt (Int.fromBytes True #"01000000000000000000000000000000f600000000000000000000000000003b19") (Int.fromBytes True #"010000000000000000000000000000007c"))) - test "oracle sqrt 14" = do - assert (Int.isqrt (Int.fromBytes True #"01000000000000000000000000000000f600000000000000000000000000003b1a") == (Int.fromBytes True #"010000000000000000000000000000007b")) - test "oracle isSqrt 14" = do - assert (Int.isSqrt (Int.fromBytes True #"01000000000000000000000000000000f600000000000000000000000000003b1a") (Int.fromBytes True #"010000000000000000000000000000007b")) - test "oracle isSqrt rejects 14" = do - assert (not (Int.isSqrt (Int.fromBytes True #"01000000000000000000000000000000f600000000000000000000000000003b1a") (Int.fromBytes True #"010000000000000000000000000000007c"))) - test "oracle sqrt 15" = do - assert (Int.isqrt (Int.fromBytes True #"01000000000000000000000000000000f800000000000000000000000000003c0f") == (Int.fromBytes True #"010000000000000000000000000000007b")) - test "oracle isSqrt 15" = do - assert (Int.isSqrt (Int.fromBytes True #"01000000000000000000000000000000f800000000000000000000000000003c0f") (Int.fromBytes True #"010000000000000000000000000000007b")) - test "oracle isSqrt rejects 15" = do - assert (not (Int.isSqrt (Int.fromBytes True #"01000000000000000000000000000000f800000000000000000000000000003c0f") (Int.fromBytes True #"010000000000000000000000000000007c"))) - test "gcd negative expected" = do - assert (not (Int.isGcd 12 18 -6)) - test "lcm negative expected" = do - assert (not (Int.isLcm 12 18 -36)) - test "sqrt negative expected" = do - assert (not (Int.isSqrt 4 -2)) - test "sqrt negative input" = do - assert (not (Int.isSqrt -4 2)) - test "reject non-greatest common divisor" = do - assert (not (Int.isGcd 12 18 3)) - test "reject non-least common multiple" = do - assert (not (Int.isLcm 12 18 72)) - test "predicate mixed Big inputs" = do - assert (Int.isGcd big 18 bigGcd && Int.isLcm big 18 bigLcm && Int.isSqrt big bigGcd) - test "gcd predicate exhaustive small inputs" = do - assert (List.all (\a -> List.all (\b -> List.all (\g -> Int.isGcd a b g == (Int.gcd a b == g)) (List.range -1 6)) (List.range -3 4)) (List.range -3 4)) - test "lcm predicate exhaustive small inputs" = do - assert (List.all (\a -> List.all (\b -> List.all (\m -> Int.isLcm a b m == (Int.lcm a b == m)) (List.range -1 14)) (List.range -3 4)) (List.range -3 4)) - test "sqrt predicate exhaustive small inputs" = do - assert (List.all (\n -> List.all (\r -> Int.isSqrt n r == (Int.isqrt n == r)) (List.range -1 12)) (List.range 0 101)) + test "gcd mixed inputs" = + do + assert (Int.gcd big 18 == 6) + + test "gcd zero" = + do + assert (Int.gcd 0 0 == 0) + + test "gcd signs" = + do + assert (Int.gcd (-48) (-18) == 6) + + test "lcm mixed inputs" = + do + assert (Int.lcm 18 big == 144) + + test "lcm zero" = + do + assert (Int.lcm 0 0 == 0) + + test "lcm signs" = + do + assert (Int.lcm (-48) 18 == 144) + + test "sqrt zero" = + do + assert (Int.isqrt 0 == 0) + + test "sqrt one" = + do + assert (Int.isqrt 1 == 1) + + test "sqrt rounds down" = + do + assert (Int.isqrt big == 6) + + test "sqrt exact" = + do + assert (Int.isqrt 49 == 7) + + test "sqrt rejects negative" fail = + do + let + _ = Int.isqrt (-1) + in + () + + test "oracle gcd 0" = + do + assert (Int.gcd 0 0 == 0) + + test "oracle lcm 0" = + do + assert (Int.lcm 0 0 == 0) + + test "oracle isGcd 0" = + do + assert (Int.isGcd 0 0 0) + + test "oracle isGcd rejects 0" = + do + assert (not (Int.isGcd 0 0 1)) + + test "oracle isLcm 0" = + do + assert (Int.isLcm 0 0 0) + + test "oracle isLcm rejects 0" = + do + assert (not (Int.isLcm 0 0 1)) + + test "oracle gcd 1" = + do + assert (Int.gcd 0 (-18) == 18) + + test "oracle lcm 1" = + do + assert (Int.lcm 0 (-18) == 0) + + test "oracle isGcd 1" = + do + assert (Int.isGcd 0 (-18) 18) + + test "oracle isGcd rejects 1" = + do + assert (not (Int.isGcd 0 (-18) 19)) + + test "oracle isLcm 1" = + do + assert (Int.isLcm 0 (-18) 0) + + test "oracle isLcm rejects 1" = + do + assert (not (Int.isLcm 0 (-18) 1)) + + test "oracle gcd 2" = + do + assert (Int.gcd (-48) 18 == 6) + + test "oracle lcm 2" = + do + assert (Int.lcm (-48) 18 == 144) + + test "oracle isGcd 2" = + do + assert (Int.isGcd (-48) 18 6) + + test "oracle isGcd rejects 2" = + do + assert (not (Int.isGcd (-48) 18 7)) + + test "oracle isLcm 2" = + do + assert (Int.isLcm (-48) 18 144) + + test "oracle isLcm rejects 2" = + do + assert (not (Int.isLcm (-48) 18 145)) + + test "oracle gcd 3" = + do + assert (Int.gcd 48 (-18) == 6) + + test "oracle lcm 3" = + do + assert (Int.lcm 48 (-18) == 144) + + test "oracle isGcd 3" = + do + assert (Int.isGcd 48 (-18) 6) + + test "oracle isGcd rejects 3" = + do + assert (not (Int.isGcd 48 (-18) 7)) + + test "oracle isLcm 3" = + do + assert (Int.isLcm 48 (-18) 144) + + test "oracle isLcm rejects 3" = + do + assert (not (Int.isLcm 48 (-18) 145)) + + test "oracle gcd 4" = + do + assert (Int.gcd (-48) (-18) == 6) + + test "oracle lcm 4" = + do + assert (Int.lcm (-48) (-18) == 144) + + test "oracle isGcd 4" = + do + assert (Int.isGcd (-48) (-18) 6) + + test "oracle isGcd rejects 4" = + do + assert (not (Int.isGcd (-48) (-18) 7)) + + test "oracle isLcm 4" = + do + assert (Int.isLcm (-48) (-18) 144) + + test "oracle isLcm rejects 4" = + do + assert (not (Int.isLcm (-48) (-18) 145)) + + test "oracle gcd 5" = + do + assert (Int.gcd 35 64 == 1) + + test "oracle lcm 5" = + do + assert (Int.lcm 35 64 == 2240) + + test "oracle isGcd 5" = + do + assert (Int.isGcd 35 64 1) + + test "oracle isGcd rejects 5" = + do + assert (not (Int.isGcd 35 64 2)) + + test "oracle isLcm 5" = + do + assert (Int.isLcm 35 64 2240) + + test "oracle isLcm rejects 5" = + do + assert (not (Int.isLcm 35 64 2241)) + + test "oracle gcd 6" = + do + assert + ( + Int.gcd + ( + Int.fromBytes + True + #"1500000000000000000000000000000015" + ) + ( + Int.fromBytes + True + #"2300000000000000000000000000000023" + ) == + Int.fromBytes True #"0700000000000000000000000000000007" + ) + + test "oracle lcm 6" = + do + assert + ( + Int.lcm + ( + Int.fromBytes + True + #"1500000000000000000000000000000015" + ) + ( + Int.fromBytes + True + #"2300000000000000000000000000000023" + ) == + Int.fromBytes True #"6900000000000000000000000000000069" + ) + + test "oracle isGcd 6" = + do + assert + ( + Int.isGcd + ( + Int.fromBytes + True + #"1500000000000000000000000000000015" + ) + ( + Int.fromBytes + True + #"2300000000000000000000000000000023" + ) + ( + Int.fromBytes + True + #"0700000000000000000000000000000007" + ) + ) + + test "oracle isGcd rejects 6" = + do + assert + ( + not + ( + Int.isGcd + ( + Int.fromBytes + True + #"1500000000000000000000000000000015" + ) + ( + Int.fromBytes + True + #"2300000000000000000000000000000023" + ) + ( + Int.fromBytes + True + #"0700000000000000000000000000000008" + ) + ) + ) + + test "oracle isLcm 6" = + do + assert + ( + Int.isLcm + ( + Int.fromBytes + True + #"1500000000000000000000000000000015" + ) + ( + Int.fromBytes + True + #"2300000000000000000000000000000023" + ) + ( + Int.fromBytes + True + #"6900000000000000000000000000000069" + ) + ) + + test "oracle isLcm rejects 6" = + do + assert + ( + not + ( + Int.isLcm + ( + Int.fromBytes + True + #"1500000000000000000000000000000015" + ) + ( + Int.fromBytes + True + #"2300000000000000000000000000000023" + ) + ( + Int.fromBytes + True + #"690000000000000000000000000000006a" + ) + ) + ) + + test "oracle sqrt 0" = + do + assert (Int.isqrt 0 == 0) + + test "oracle isSqrt 0" = + do + assert (Int.isSqrt 0 0) + + test "oracle isSqrt rejects 0" = + do + assert (not (Int.isSqrt 0 1)) + + test "oracle sqrt 1" = + do + assert (Int.isqrt 1 == 1) + + test "oracle isSqrt 1" = + do + assert (Int.isSqrt 1 1) + + test "oracle isSqrt rejects 1" = + do + assert (not (Int.isSqrt 1 2)) + + test "oracle sqrt 2" = + do + assert (Int.isqrt 2 == 1) + + test "oracle isSqrt 2" = + do + assert (Int.isSqrt 2 1) + + test "oracle isSqrt rejects 2" = + do + assert (not (Int.isSqrt 2 2)) + + test "oracle sqrt 3" = + do + assert (Int.isqrt 3 == 1) + + test "oracle isSqrt 3" = + do + assert (Int.isSqrt 3 1) + + test "oracle isSqrt rejects 3" = + do + assert (not (Int.isSqrt 3 2)) + + test "oracle sqrt 4" = + do + assert (Int.isqrt 4 == 2) + + test "oracle isSqrt 4" = + do + assert (Int.isSqrt 4 2) + + test "oracle isSqrt rejects 4" = + do + assert (not (Int.isSqrt 4 3)) + + test "oracle sqrt 5" = + do + assert (Int.isqrt 15 == 3) + + test "oracle isSqrt 5" = + do + assert (Int.isSqrt 15 3) + + test "oracle isSqrt rejects 5" = + do + assert (not (Int.isSqrt 15 4)) + + test "oracle sqrt 6" = + do + assert (Int.isqrt 16 == 4) + + test "oracle isSqrt 6" = + do + assert (Int.isSqrt 16 4) + + test "oracle isSqrt rejects 6" = + do + assert (not (Int.isSqrt 16 5)) + + test "oracle sqrt 7" = + do + assert (Int.isqrt 17 == 4) + + test "oracle isSqrt 7" = + do + assert (Int.isSqrt 17 4) + + test "oracle isSqrt rejects 7" = + do + assert (not (Int.isSqrt 17 5)) + + test "oracle sqrt 8" = + do + assert + ( + Int.isqrt + ( + Int.fromBytes + True + #"0100000000000000020000000000000000" + ) == + 18446744073709551616 + ) + + test "oracle isSqrt 8" = + do + assert + ( + Int.isSqrt + ( + Int.fromBytes + True + #"0100000000000000020000000000000000" + ) + 18446744073709551616 + ) + + test "oracle isSqrt rejects 8" = + do + assert + ( + not + ( + Int.isSqrt + ( + Int.fromBytes + True + #"0100000000000000020000000000000000" + ) + 18446744073709551617 + ) + ) + + test "oracle sqrt 9" = + do + assert + ( + Int.isqrt + ( + Int.fromBytes + True + #"0100000000000000020000000000000001" + ) == + 18446744073709551617 + ) + + test "oracle isSqrt 9" = + do + assert + ( + Int.isSqrt + ( + Int.fromBytes + True + #"0100000000000000020000000000000001" + ) + 18446744073709551617 + ) + + test "oracle isSqrt rejects 9" = + do + assert + ( + not + ( + Int.isSqrt + ( + Int.fromBytes + True + #"0100000000000000020000000000000001" + ) + 18446744073709551618 + ) + ) + + test "oracle sqrt 10" = + do + assert + ( + Int.isqrt + ( + Int.fromBytes + True + #"0100000000000000020000000000000002" + ) == + 18446744073709551617 + ) + + test "oracle isSqrt 10" = + do + assert + ( + Int.isSqrt + ( + Int.fromBytes + True + #"0100000000000000020000000000000002" + ) + 18446744073709551617 + ) + + test "oracle isSqrt rejects 10" = + do + assert + ( + not + ( + Int.isSqrt + ( + Int.fromBytes + True + #"0100000000000000020000000000000002" + ) + 18446744073709551618 + ) + ) + + test "oracle sqrt 11" = + do + assert + ( + Int.isqrt + ( + Int.fromBytes + True + #"0100000000000000040000000000000003" + ) == + 18446744073709551617 + ) + + test "oracle isSqrt 11" = + do + assert + ( + Int.isSqrt + ( + Int.fromBytes + True + #"0100000000000000040000000000000003" + ) + 18446744073709551617 + ) + + test "oracle isSqrt rejects 11" = + do + assert + ( + not + ( + Int.isSqrt + ( + Int.fromBytes + True + #"0100000000000000040000000000000003" + ) + 18446744073709551618 + ) + ) + + test "oracle sqrt 12" = + do + assert + ( + Int.isqrt + ( + Int.fromBytes + True + #"01000000000000000000000000000000f600000000000000000000000000003b18" + ) == + Int.fromBytes True #"010000000000000000000000000000007a" + ) + + test "oracle isSqrt 12" = + do + assert + ( + Int.isSqrt + ( + Int.fromBytes + True + #"01000000000000000000000000000000f600000000000000000000000000003b18" + ) + ( + Int.fromBytes + True + #"010000000000000000000000000000007a" + ) + ) + + test "oracle isSqrt rejects 12" = + do + assert + ( + not + ( + Int.isSqrt + ( + Int.fromBytes + True + #"01000000000000000000000000000000f600000000000000000000000000003b18" + ) + ( + Int.fromBytes + True + #"010000000000000000000000000000007b" + ) + ) + ) + + test "oracle sqrt 13" = + do + assert + ( + Int.isqrt + ( + Int.fromBytes + True + #"01000000000000000000000000000000f600000000000000000000000000003b19" + ) == + Int.fromBytes True #"010000000000000000000000000000007b" + ) + + test "oracle isSqrt 13" = + do + assert + ( + Int.isSqrt + ( + Int.fromBytes + True + #"01000000000000000000000000000000f600000000000000000000000000003b19" + ) + ( + Int.fromBytes + True + #"010000000000000000000000000000007b" + ) + ) + + test "oracle isSqrt rejects 13" = + do + assert + ( + not + ( + Int.isSqrt + ( + Int.fromBytes + True + #"01000000000000000000000000000000f600000000000000000000000000003b19" + ) + ( + Int.fromBytes + True + #"010000000000000000000000000000007c" + ) + ) + ) + + test "oracle sqrt 14" = + do + assert + ( + Int.isqrt + ( + Int.fromBytes + True + #"01000000000000000000000000000000f600000000000000000000000000003b1a" + ) == + Int.fromBytes True #"010000000000000000000000000000007b" + ) + + test "oracle isSqrt 14" = + do + assert + ( + Int.isSqrt + ( + Int.fromBytes + True + #"01000000000000000000000000000000f600000000000000000000000000003b1a" + ) + ( + Int.fromBytes + True + #"010000000000000000000000000000007b" + ) + ) + + test "oracle isSqrt rejects 14" = + do + assert + ( + not + ( + Int.isSqrt + ( + Int.fromBytes + True + #"01000000000000000000000000000000f600000000000000000000000000003b1a" + ) + ( + Int.fromBytes + True + #"010000000000000000000000000000007c" + ) + ) + ) + + test "oracle sqrt 15" = + do + assert + ( + Int.isqrt + ( + Int.fromBytes + True + #"01000000000000000000000000000000f800000000000000000000000000003c0f" + ) == + Int.fromBytes True #"010000000000000000000000000000007b" + ) + + test "oracle isSqrt 15" = + do + assert + ( + Int.isSqrt + ( + Int.fromBytes + True + #"01000000000000000000000000000000f800000000000000000000000000003c0f" + ) + ( + Int.fromBytes + True + #"010000000000000000000000000000007b" + ) + ) + + test "oracle isSqrt rejects 15" = + do + assert + ( + not + ( + Int.isSqrt + ( + Int.fromBytes + True + #"01000000000000000000000000000000f800000000000000000000000000003c0f" + ) + ( + Int.fromBytes + True + #"010000000000000000000000000000007c" + ) + ) + ) + + test "gcd negative expected" = + do + assert (not (Int.isGcd 12 18 (-6))) + + test "lcm negative expected" = + do + assert (not (Int.isLcm 12 18 (-36))) + + test "sqrt negative expected" = + do + assert (not (Int.isSqrt 4 (-2))) + + test "sqrt negative input" = + do + assert (not (Int.isSqrt (-4) 2)) + + test "reject non-greatest common divisor" = + do + assert (not (Int.isGcd 12 18 3)) + + test "reject non-least common multiple" = + do + assert (not (Int.isLcm 12 18 72)) + + test "predicate mixed Big inputs" = + do + assert + ( + Int.isGcd big 18 bigGcd && + Int.isLcm big 18 bigLcm && + Int.isSqrt big bigGcd + ) + + test "gcd predicate exhaustive small inputs" = + do + assert + ( + List.all + ( + \a -> + List.all + ( + \b -> + List.all + ( + \g -> + Int.isGcd a b g == + (Int.gcd a b == g) + ) + (List.range (-1) 6) + ) + (List.range (-3) 4) + ) + (List.range (-3) 4) + ) + + test "lcm predicate exhaustive small inputs" = + do + assert + ( + List.all + ( + \a -> + List.all + ( + \b -> + List.all + ( + \m -> + Int.isLcm a b m == + (Int.lcm a b == m) + ) + (List.range (-1) 14) + ) + (List.range (-3) 4) + ) + (List.range (-3) 4) + ) + + test "sqrt predicate exhaustive small inputs" = + do + assert + ( + List.all + ( + \n -> + List.all + (\r -> Int.isSqrt n r == (Int.isqrt n == r)) + (List.range (-1) 12) + ) + (List.range 0 101) + ) diff --git a/examples/proofs/src/Main.nash b/examples/proofs/src/Main.nash index 1d9782f2..4f151f24 100644 --- a/examples/proofs/src/Main.nash +++ b/examples/proofs/src/Main.nash @@ -1,26 +1,36 @@ -module Main exposing (addZero) +module Main exposing ( addZero ) + addZero : int -> int addZero x = x + 0 + tests - test "concrete identity" = do - assert (addZero 42 == 42) + test "concrete identity" = + do + assert (addZero 42 == 42) + proof import Proof prop "integer identity" = - let x via Proof.int in - do - assert (addZero x == x) + let + x via Proof.int + in + do + assert (addZero x == x) prop "division by zero always fails" fail = - let x via Proof.int in - do - assert (x / 0 == 0) + let + x via Proof.int + in + do + assert (x / 0 == 0) prop "find a negative integer" fail once = - let x via Proof.int in - do - assert (x >= 0) + let + x via Proof.int + in + do + assert (x >= 0) diff --git a/examples/vesting/app/src/Vesting.nash b/examples/vesting/app/src/Vesting.nash index 306797b7..f6caf2d0 100644 --- a/examples/vesting/app/src/Vesting.nash +++ b/examples/vesting/app/src/Vesting.nash @@ -1,24 +1,42 @@ -validator module Vesting exposing (main) +validator module Vesting exposing ( main ) + import Builtin exposing (..) -import Eq exposing (Eq) -import Lift exposing (Lift) -import VestingTx exposing (validAfter, signedBy) +import Eq exposing ( Eq ) +import Lift exposing ( Lift ) +import VestingTx exposing ( validAfter, signedBy ) + + +type Datum + = Datum { owner : Bytes, deadline : Int } + -type Datum = Datum { owner : Bytes, deadline : Int } +type Redeemer + = Claim + | Cancel -type Redeemer = Claim | Cancel -- The ledger supplies one V3 ScriptContext; spending datum is in ScriptInfo. main : Data -> unit main ctx = case ctx of - Constr pair(0, [ tx, action, Constr pair(1, [ _, Constr pair(0, [ datum ]) ]) ]) -> + Constr + pair(0, [ tx + , action + , Constr pair(1, [ _, Constr pair(0, [ datum ]) ]) + ]) -> check (Primitive.coerce datum) (Primitive.coerce action) tx - _ -> fail + + _ -> + fail + check : Datum -> Redeemer -> Data -> unit check datum redeemer tx = case redeemer of - Claim -> assert (Builtin.lessThanInteger (lower datum.deadline) (validAfter tx)) - Cancel -> assert (signedBy tx datum.owner) + Claim -> + assert + (Builtin.lessThanInteger (lower datum.deadline) (validAfter tx)) + + Cancel -> + assert (signedBy tx datum.owner) diff --git a/examples/vesting/app/src/VestingParam.nash b/examples/vesting/app/src/VestingParam.nash index bfc82b55..5ad62dad 100644 --- a/examples/vesting/app/src/VestingParam.nash +++ b/examples/vesting/app/src/VestingParam.nash @@ -1,28 +1,50 @@ -validator module VestingParam exposing (main) +validator module VestingParam exposing ( main ) + import Builtin exposing (..) -import Eq exposing (Eq) -import Lift exposing (Lift) -import VestingTx exposing (validAfter, signedBy) +import Eq exposing ( Eq ) +import Lift exposing ( Lift ) +import VestingTx exposing ( validAfter, signedBy ) + + +type Datum + = Datum { owner : Bytes, deadline : Int } + -type Datum = Datum { owner : Bytes, deadline : Int } +type Redeemer + = Claim + | Cancel -type Redeemer = Claim | Cancel -- The ledger supplies one V3 ScriptContext; spending datum is in ScriptInfo. main : int -> Data -> unit main minimumLock ctx = case ctx of - Constr pair(0, [ tx, action, Constr pair(1, [ _, Constr pair(0, [ datum ]) ]) ]) -> - check minimumLock (Primitive.coerce datum) (Primitive.coerce action) tx - _ -> fail + Constr + pair(0, [ tx + , action + , Constr pair(1, [ _, Constr pair(0, [ datum ]) ]) + ]) -> + check + minimumLock + (Primitive.coerce datum) + (Primitive.coerce action) + tx + + _ -> + fail + check : int -> Datum -> Redeemer -> Data -> unit check minimumLock datum redeemer tx = case redeemer of Claim -> assert - (Builtin.lessThanInteger - (Builtin.addInteger (lower datum.deadline) minimumLock) - (validAfter tx)) - Cancel -> assert (signedBy tx datum.owner) + ( + Builtin.lessThanInteger + (Builtin.addInteger (lower datum.deadline) minimumLock) + (validAfter tx) + ) + + Cancel -> + assert (signedBy tx datum.owner) From 910005966b7e5e545c898ddb1a4033056a1d33a5 Mon Sep 17 00:00:00 2001 From: RSoulatIOHK Date: Thu, 1 Oct 2026 11:37:53 +0200 Subject: [PATCH 3/7] feat: prove partial correctness and extend existing test coverage --- .sampo/changesets/lean-blaster-proofs.md | 6 + crates/nash-cli/src/cmd/proof.rs | 26 +- crates/nash-codegen/src/proofs.rs | 116 +++++-- crates/nash-driver/base/src/Proof.nash | 12 +- .../fixtures/base-traits/DataConversions.nash | 53 ++++ .../tests/fixtures/base-traits/Equality.nash | 65 ++++ .../fixtures/base-traits/IntegerMath.nash | 113 +++++++ crates/nash-driver/tests/production.rs | 25 ++ crates/nash-driver/tests/proofs.rs | 284 +++++++++++++++++- crates/nash-driver/tests/vesting.rs | 37 +++ crates/nash-proof/src/lean.rs | 191 +++++++++++- crates/nash-proof/src/lib.rs | 18 ++ crates/nash-proof/src/runner.rs | 82 ++++- docs/proofs.md | 117 +++++++- examples/proofs/src/Main.nash | 7 + examples/vesting/README.md | 15 + examples/vesting/app/src/Vesting.nash | 43 +++ examples/vesting/app/src/VestingParam.nash | 45 +++ 18 files changed, 1199 insertions(+), 56 deletions(-) diff --git a/.sampo/changesets/lean-blaster-proofs.md b/.sampo/changesets/lean-blaster-proofs.md index 2fe06a84..b8a6bcda 100644 --- a/.sampo/changesets/lean-blaster-proofs.md +++ b/.sampo/changesets/lean-blaster-proofs.md @@ -17,3 +17,9 @@ cargo/nash-proof: minor Add module-local proof blocks and a Lean-blaster proof command over compiled UPLC, with symbolic primitive and Cardano ledger domains, portable pinned Lean projects, structured results, explicit SMT trust, and CEK execution limits with exhaustion treated as rejection. + +Support partial correctness with `Proof.returns`, separate postcondition evaluation +limits, and explicit inconclusive checker exhaustion. Add universal properties +to existing conversion, ordering and arithmetic fixtures and authorization +properties to the vesting examples. Extend deadline, signer and malformed-context +regressions while preserving runtime and oracle coverage. diff --git a/crates/nash-cli/src/cmd/proof.rs b/crates/nash-cli/src/cmd/proof.rs index 3437dcc2..42b04eab 100644 --- a/crates/nash-cli/src/cmd/proof.rs +++ b/crates/nash-cli/src/cmd/proof.rs @@ -20,6 +20,9 @@ pub struct Args { /// CEK execution step limit; exhaustion counts as rejection. #[arg(long, default_value = "10000", value_parser = positive)] pub fuel: u32, + /// Separate CEK limit for evaluating a partial-correctness postcondition. + #[arg(long, default_value = "10000", value_parser = positive)] + pub postcondition_fuel: u32, #[arg(long, default_value = "30", value_parser = positive)] pub timeout: u32, #[arg(long, default_value = "120", value_parser = positive)] @@ -129,8 +132,14 @@ impl Args { if let Some(parent) = output.parent() { std::fs::create_dir_all(parent).into_diagnostic()?; } - let files = - nash_proof::export(&programs, &output, self.fuel, self.timeout).into_diagnostic()?; + let files = nash_proof::export( + &programs, + &output, + self.fuel, + self.postcondition_fuel, + self.timeout, + ) + .into_diagnostic()?; if self.emit_only { if self.json { println!( @@ -177,6 +186,7 @@ impl Args { let config = nash_proof::Config { lean_project, fuel: self.fuel, + postcondition_fuel: self.postcondition_fuel, solver_timeout: self.timeout, wall_timeout: Duration::from_secs(u64::from(self.wall_timeout)), }; @@ -184,9 +194,17 @@ impl Args { for (program, file) in programs.iter().zip(files) { let outcome = nash_proof::run(program, &file, &config).into_diagnostic()?; if !self.json { + let limits = if program.postcondition.is_some() { + format!( + "execution fuel {}, postcondition fuel {}", + outcome.fuel, outcome.postcondition_fuel + ) + } else { + format!("execution fuel {}", outcome.fuel) + }; eprintln!( - "{}: {:?} (SMT verification, fuel {})", - outcome.name, outcome.status, outcome.fuel + "{}: {:?} (SMT verification, {limits})", + outcome.name, outcome.status ); for entry in &outcome.counterexample { eprintln!(" {entry}"); diff --git a/crates/nash-codegen/src/proofs.rs b/crates/nash-codegen/src/proofs.rs index 11d13ea6..a8c04ac8 100644 --- a/crates/nash-codegen/src/proofs.rs +++ b/crates/nash-codegen/src/proofs.rs @@ -4,9 +4,13 @@ use crate::{ decision_tree::{self, MatchBranch, MatchInputs}, }; use nash_ast::{Expr, ModuleName, NodeId, primitives}; -use nash_ir::core::*; +use nash_ir::{ + core::*, + ty::{ConstTy, Ty}, +}; use nash_plutus::{arena::Arena, flat}; pub use nash_proof::{Domain, ProofProgram}; +use nash_proof::{Postcondition, ReturnDomain}; #[derive(Debug, thiserror::Error)] pub enum Error<'a> { @@ -22,6 +26,12 @@ pub enum Error<'a> { Budget, #[error("ledger domain version must match the proof's Plutus version")] LedgerVersion, + #[error("Proof.returns is a partial-correctness claim and cannot use fail or fail once")] + PartialExpectation, + #[error( + "Proof.returns requires an integer, Boolean, bytes, string, unit or Data-represented result" + )] + PartialResult, #[error("{0}")] Build(crate::build::Error<'a>), #[error("{0}")] @@ -122,43 +132,103 @@ pub fn compile_proofs_matching<'a>( params.push(value); patterns.push((binder, value, records, literals, bindings)); } - let mut body = engine.expr(proof.body, &ctx)?; - for (binder, value, records, literals, bindings) in patterns.into_iter().rev() { - body = decision_tree::compile( - &engine.ir, - &mut engine.types, - value.ty, - engine.ir.var(value.name, value.ty), - &[MatchBranch { - pattern: binder.pattern, - bindings, - body, - }], - MatchInputs { - record_fields: &records, - literal_tests: &literals, - }, - engine.ir.error(body.ty), - )?; + let partial = match proof.body.value { + Expr::Call { + function, + arguments: [computation, condition], + } if matches!(function.value, Expr::VarForeign { reference, .. } + if reference.home.package == Some(primitives::BASE) + && reference.home.name == "Proof" && reference.name == "returns") => + { + Some((*computation, *condition)) + } + _ => None, + }; + if partial.is_some() && proof.expect != nash_ast::Expect::Pass { + return Err(Error::PartialExpectation); } - let root = if params.is_empty() { - body + let body = engine.expr(partial.map_or(proof.body, |(value, _)| value), &ctx)?; + let mut postcondition = None; + let mut checker = None; + if let Some((_, condition)) = partial { + let result = match body.ty { + Ty::Const(ConstTy::Int) => ReturnDomain::Int, + Ty::Const(ConstTy::Bool) => ReturnDomain::Bool, + Ty::Const(ConstTy::Bytes) => ReturnDomain::Bytes, + Ty::Const(ConstTy::String) => ReturnDomain::String, + Ty::Const(ConstTy::Unit) => ReturnDomain::Unit, + Ty::Big(_) => ReturnDomain::Data, + _ => return Err(Error::PartialResult), + }; + let value = Binder { + name: engine.ir.fresh("returned"), + ty: body.ty, + }; + let condition = engine.expr(condition, &ctx)?; + let holds = engine.ir.app( + condition, + &[engine.ir.var(value.name, value.ty)], + Ty::Const(&ConstTy::Bool), + ); + checker = Some((holds, value)); + postcondition = Some(Postcondition { + flat: Vec::new(), + result, + }); + } + // Both programs bind the same symbolic inputs, but only the condition + // receives a successfully returned value. Never guard the assertion body. + let wrap = |engine: &mut Engine<'a, '_, '_>, mut body| -> Result<_, Error<'a>> { + for (binder, value, records, literals, bindings) in patterns.iter().rev() { + body = decision_tree::compile( + &engine.ir, + &mut engine.types, + value.ty, + engine.ir.var(value.name, value.ty), + &[MatchBranch { + pattern: binder.pattern, + bindings: bindings.clone(), + body, + }], + MatchInputs { + record_fields: records, + literal_tests: literals, + }, + engine.ir.error(body.ty), + )?; + } + Ok(body) + }; + let body = wrap(&mut engine, body)?; + let root = engine.ir.lam(¶ms, body); + let checker_root = if let Some((body, value)) = checker { + let body = wrap(&mut engine, body)?; + let mut arguments = params.clone(); + arguments.push(value); + Some(engine.ir.lam(&arguments, body)) } else { - engine.ir.lam(¶ms, body) + None }; - let core = engine.finish_root(root)?; let target = match version { nash_config::PlutusVersion::V1 => nash_plutus::machine::PlutusVersion::V1, nash_config::PlutusVersion::V2 => nash_plutus::machine::PlutusVersion::V2, nash_config::PlutusVersion::V3 => nash_plutus::machine::PlutusVersion::V3, }; + let core = engine.finish_root(root)?; let compiled = crate::program::assemble_core_for_version(arena, core, target)?; + if let Some(root) = checker_root { + let core = engine.finish_root(root)?; + let compiled = crate::program::assemble_core_for_version(arena, core, target)?; + postcondition.as_mut().unwrap().flat = + flat::encode(compiled.program).map_err(|e| Error::Encoding(e.to_string()))?; + } programs.push(ProofProgram { module: module.name.to_owned(), name: proof.name.value.to_owned(), expect: proof.expect, domains, flat: flat::encode(compiled.program).map_err(|e| Error::Encoding(e.to_string()))?, + postcondition, plutus_version: version, }); } diff --git a/crates/nash-driver/base/src/Proof.nash b/crates/nash-driver/base/src/Proof.nash index 77bd1745..fb695224 100644 --- a/crates/nash-driver/base/src/Proof.nash +++ b/crates/nash-driver/base/src/Proof.nash @@ -1,5 +1,6 @@ module Proof exposing - ( int + ( returns + , int , integer , bool , bytes @@ -85,3 +86,12 @@ mintingV2 = data {-| Contexts satisfying the CardanoLedgerApi Plutus V3 minting validity predicate. -} mintingV3 : Data mintingV3 = data + + +{-| Partial correctness: if the computation successfully returns, its result must +satisfy the condition. Use as the sole expression of a passing proof body. +The proof backend compiles the computation and condition separately. +In ordinary Nash execution this checks the condition with an assertion. +-} +returns : 'a -> ('a -> bool) -> unit +returns value condition = assert (condition value) diff --git a/crates/nash-driver/tests/fixtures/base-traits/DataConversions.nash b/crates/nash-driver/tests/fixtures/base-traits/DataConversions.nash index a1a65360..b9ab5d8b 100644 --- a/crates/nash-driver/tests/fixtures/base-traits/DataConversions.nash +++ b/crates/nash-driver/tests/fixtures/base-traits/DataConversions.nash @@ -423,3 +423,56 @@ tests test "Data decode preserves any shape 1" = do assert (asData (Map entries) == Some (Map entries)) + + +proof + import Proof + + prop "integer Data roundtrip" = + let + value via Proof.int + in + do + assert (little (toData value) == value) + + prop "boolean Data roundtrip" = + let + value via Proof.bool + in + do + assert (fromBool (toData value) == value) + + prop "bytes Data roundtrip" = + let + value via Proof.bytes + in + do + assert (fromBytes (toData value) == value) + + prop "integer optional decoding" = + let + value via Proof.int + in + do + assert (asInt (toData value) == Some value) + + prop "integer decoder rejects bytes of any content" = + let + value via Proof.bytes + in + do + assert (asInt (B value) == None) + + prop "bytes decoder rejects every integer" = + let + value via Proof.int + in + do + assert (asBytes (I value) == None) + + prop "boolean decoder rejects a constructor with a payload" = + let + value via Proof.int + in + do + assert (asBool (Builtin.constrData 0 [ I value ]) == None) diff --git a/crates/nash-driver/tests/fixtures/base-traits/Equality.nash b/crates/nash-driver/tests/fixtures/base-traits/Equality.nash index 301cc8fd..4fe6f8ca 100644 --- a/crates/nash-driver/tests/fixtures/base-traits/Equality.nash +++ b/crates/nash-driver/tests/fixtures/base-traits/Equality.nash @@ -147,3 +147,68 @@ tests assert (compare ( 1, 2 ) ( 1, 3 ) == LT) assert (compare ( 1, 2, 3 ) ( 1, 2, 2 ) == GT) assert (compare ( 1, 2, 3, 4 ) ( 1, 2, 3, 4 ) == EQ) + + +proof + import Proof + + prop "integer ordering agrees with comparison" = + let + left via Proof.int + right via Proof.int + in + do + assert + ( + case compare left right of + LT -> + left < right + + EQ -> + left == right + + GT -> + left > right + ) + + prop "integer ordering is antisymmetric" = + let + left via Proof.int + right via Proof.int + in + do + assert + ( + case ( compare left right, compare right left ) of + ( LT, GT ) -> + True + + ( EQ, EQ ) -> + True + + ( GT, LT ) -> + True + + _ -> + False + ) + + prop "minimum and maximum partition integer inputs" = + let + left via Proof.int + right via Proof.int + in + do + assert (min left right + max left right == left + right) + + prop "boolean equality agrees with logic" = + let + left via Proof.bool + right via Proof.bool + in + do + assert + ( + (left == right) == + ((left && right) || (not left && not right)) + ) diff --git a/crates/nash-driver/tests/fixtures/base-traits/IntegerMath.nash b/crates/nash-driver/tests/fixtures/base-traits/IntegerMath.nash index c9ebc0c6..390f7b89 100644 --- a/crates/nash-driver/tests/fixtures/base-traits/IntegerMath.nash +++ b/crates/nash-driver/tests/fixtures/base-traits/IntegerMath.nash @@ -891,3 +891,116 @@ tests ) (List.range 0 101) ) + + +proof + import Proof + + -- Partial correctness checks successful results, without requiring termination. + -- The adjacent-squares predicate does not call the square-root algorithm. + prop "square root satisfies adjacent squares when it returns" = + let + value via Proof.int + in + do + Proof.returns + (Int.isqrt value) + ( + \root -> + root >= + 0 && + root * + root <= + value && + value < + (root + 1) * + (root + 1) + ) + + -- Every positive common divisor must divide the returned gcd. + prop "gcd satisfies the greatest common divisor specification" = + let + left via Proof.int + right via Proof.int + candidate via Proof.int + in + do + Proof.returns + (Int.gcd left right) + ( + \divisor -> + ( + if divisor == 0 then + left == 0 && right == 0 + else + divisor > + 0 && + left % + divisor == + 0 && + right % + divisor == + 0 + ) && + ( + if candidate > + 0 && + left % + candidate == + 0 && + right % + candidate == + 0 then + divisor % candidate == 0 + else + True + ) + ) + + -- Every positive common multiple must be divisible by the returned lcm. + prop "lcm satisfies the least common multiple specification" = + let + left via Proof.int + right via Proof.int + candidate via Proof.int + in + do + Proof.returns + (Int.lcm left right) + ( + \multiple -> + if left == 0 || right == 0 then + multiple == 0 + else + multiple > + 0 && + multiple % + left == + 0 && + multiple % + right == + 0 && + ( + if candidate > + 0 && + candidate % + left == + 0 && + candidate % + right == + 0 then + candidate % multiple == 0 + else + True + ) + ) + + prop "square root rejects every negative input" fail = + let + value via Proof.int + in + do + let + _ = Int.isqrt (-1 - Int.abs value) + in + () diff --git a/crates/nash-driver/tests/production.rs b/crates/nash-driver/tests/production.rs index 7157115c..c6ad07e4 100644 --- a/crates/nash-driver/tests/production.rs +++ b/crates/nash-driver/tests/production.rs @@ -249,3 +249,28 @@ async fn dependency_validators_are_not_emitted_and_selected_roots_keep_their_tar nash_plutus::script::script_hash(nash_plutus::machine::PlutusVersion::V1, &outputs[0].cbor) ); } + +#[tokio::test] +async fn production_excludes_proof_imports_and_bodies() { + let uri = Url::parse("file:///project/src/Main.nash").unwrap(); + let source = "validator module Main exposing (main)\nmain : Data -> unit\nmain _ = ()\nproof\n import MissingProofBackend\n test \"unreachable proof\" = do\n assert undefinedProofName\n"; + let memory = InMemorySource::new(); + memory.insert(uri.clone(), source.into()); + let mut origins = BTreeMap::from([(uri.clone(), None)]); + origins.extend(nash_driver::bundled_base::modules()); + let db = Arc::new(Mutex::new(Database::new(memory))); + let graph = nash_driver::build_graph_production( + db.clone(), + &origins.keys().cloned().collect::>(), + ) + .await + .unwrap(); + let (report, outputs) = build_with(db, &graph, &origins, move |solved| { + let main = solved.modules.iter().find(|m| m.uri == uri).unwrap(); + assert!(main.module.proofs.is_empty()); + build::build_validators(solved, nash_config::Build::default()) + }) + .await; + assert!(report.is_success(), "{report:#?}"); + assert_eq!(outputs.unwrap().unwrap().len(), 1); +} diff --git a/crates/nash-driver/tests/proofs.rs b/crates/nash-driver/tests/proofs.rs index 8667b26f..0f2936f6 100644 --- a/crates/nash-driver/tests/proofs.rs +++ b/crates/nash-driver/tests/proofs.rs @@ -136,10 +136,11 @@ proof .unwrap() .as_nanos() )); - let files = nash_proof::export(&programs, &directory, 65, 10).unwrap(); + let files = nash_proof::export(&programs, &directory, 65, 1000, 10).unwrap(); let config = nash_proof::Config { lean_project: project.into(), fuel: 65, + postcondition_fuel: 1000, solver_timeout: 10, wall_timeout: std::time::Duration::from_secs(60), }; @@ -153,7 +154,7 @@ proof assert_eq!(outcome.status, expected, "{}", outcome.diagnostics); } let limited = directory.join("limited"); - let files = nash_proof::export(&programs[..1], &limited, 1, 10).unwrap(); + let files = nash_proof::export(&programs[..1], &limited, 1, 1000, 10).unwrap(); let config = nash_proof::Config { fuel: 1, ..config }; let outcome = nash_proof::run(&programs[0], &files[0], &config).unwrap(); assert_eq!( @@ -172,8 +173,285 @@ proof ] { programs[0].expect = expect; let directory = directory.join(format!("exhaustion-{index}")); - let files = nash_proof::export(&programs[..1], &directory, 1, 10).unwrap(); + let files = nash_proof::export(&programs[..1], &directory, 1, 1000, 10).unwrap(); let outcome = nash_proof::run(&programs[0], &files[0], &config).unwrap(); assert_eq!(outcome.status, status, "{}", outcome.diagnostics); } } + +#[tokio::test] +async fn partial_correctness_compiles_computation_and_condition_separately() { + let programs = compile( + r#"module Main exposing (..) +proof + import Proof + prop "identity result" = let x via Proof.int in do + Proof.returns (x + 0) (\result -> result == x) + test "unit result" = do + Proof.returns () (\_ -> True) + prop "Data result" = let x via Proof.integer in do + Proof.returns x (\result -> result == x) +"#, + ) + .await + .unwrap(); + assert_eq!( + programs[0].postcondition.as_ref().unwrap().result, + nash_proof::ReturnDomain::Int + ); + assert_eq!( + programs[1].postcondition.as_ref().unwrap().result, + nash_proof::ReturnDomain::Unit + ); + assert_eq!( + programs[2].postcondition.as_ref().unwrap().result, + nash_proof::ReturnDomain::Data + ); + assert!( + programs + .iter() + .all(|p| !p.postcondition.as_ref().unwrap().flat.is_empty()) + ); + assert_ne!( + programs[0].flat, + programs[0].postcondition.as_ref().unwrap().flat + ); +} + +#[tokio::test] +async fn partial_correctness_rejects_failure_modifiers_and_function_results() { + for (body, expected) in [ + ( + "test \"invalid fail\" fail = do\n Proof.returns 1 (\\_ -> True)", + "cannot use fail", + ), + ( + "test \"function result\" = do\n Proof.returns (\\x -> x + 1) (\\_ -> True)", + "result", + ), + ] { + let source = format!("module Main exposing (..)\nproof\n import Proof\n {body}\n"); + let error = compile(&source).await.unwrap_err(); + assert!(error.contains(expected), "{error}"); + } +} + +#[tokio::test] +#[ignore = "requires the pinned Lean project and Z3"] +async fn live_partial_correctness() { + let programs = compile( + r#"module Main exposing (..) +wrong x = x + 1 +reject : int -> int +reject _ = fail +loop : int -> int +loop x = loop x +proof + import Proof + prop "correct identity" = let x via Proof.int in do + Proof.returns x (\result -> result == x) + prop "wrong returned result must refute" = let x via Proof.int in do + Proof.returns (wrong x) (\result -> result == x) + prop "rejection has no successful return" = let x via Proof.int in do + Proof.returns (reject x) (\_ -> False) + prop "exhaustion has no successful return" = let x via Proof.int in do + Proof.returns (loop x) (\_ -> False) + prop "undefined condition must refute" = let x via Proof.int in do + Proof.returns x (\_ -> 1 / 0 == 0) + prop "condition exhaustion must be inconclusive" = let x via Proof.int in do + Proof.returns x (\result -> loop result == 0) + test "unit return" = do + Proof.returns () (\_ -> True) + prop "Data return" = let x via Proof.integer in do + Proof.returns x (\result -> result == x) + prop "Boolean return" = let x via Proof.bool in do + Proof.returns x (\result -> result == x) + prop "Boolean false case must refute" = let x via Proof.bool in do + Proof.returns x (\result -> result) + prop "bytes return" = let x via Proof.bytes in do + Proof.returns x (\result -> result == x) + prop "string return" = let x via Proof.string in do + Proof.returns x (\result -> result == x) +"#, + ) + .await + .unwrap(); + let directory = std::env::temp_dir().join(format!("nash-live-partial-{}", std::process::id())); + let files = nash_proof::export(&programs, &directory, 120, 150, 10).unwrap(); + let config = nash_proof::Config { + lean_project: std::env::var_os("NASH_PROOF_LEAN_PROJECT") + .expect("Lean project") + .into(), + fuel: 120, + postcondition_fuel: 150, + solver_timeout: 10, + wall_timeout: std::time::Duration::from_secs(60), + }; + for ((program, source), expected) in programs.iter().zip(files).zip([ + nash_proof::Status::VerifiedPartial, + nash_proof::Status::Counterexample, + nash_proof::Status::VerifiedPartial, + nash_proof::Status::VerifiedPartial, + nash_proof::Status::Counterexample, + nash_proof::Status::PostconditionExhausted, + nash_proof::Status::VerifiedPartial, + nash_proof::Status::VerifiedPartial, + nash_proof::Status::VerifiedPartial, + nash_proof::Status::Counterexample, + nash_proof::Status::VerifiedPartial, + nash_proof::Status::VerifiedPartial, + ]) { + let outcome = nash_proof::run(program, &source, &config).unwrap(); + assert_eq!( + outcome.status, expected, + "{}: {}", + program.name, outcome.diagnostics + ); + } +} + +const EXISTING_FIXTURES: &[(&str, &str, usize)] = &[ + ( + "DataConversions", + include_str!("fixtures/base-traits/DataConversions.nash"), + 7, + ), + ( + "Equality", + include_str!("fixtures/base-traits/Equality.nash"), + 4, + ), + ( + "IntegerMath", + include_str!("fixtures/base-traits/IntegerMath.nash"), + 4, + ), +]; + +#[tokio::test] +async fn existing_test_fixtures_compile_and_export_proofs() { + let directory = std::env::temp_dir().join(format!( + "nash-fixture-export-{}-{}", + std::process::id(), + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_nanos() + )); + for (name, source, count) in EXISTING_FIXTURES { + let programs = compile(source) + .await + .unwrap_or_else(|error| panic!("{name}: {error}")); + assert_eq!(programs.len(), *count, "{name}"); + let files = nash_proof::export(&programs, &directory, 500, 1000, 30).unwrap(); + assert_eq!(files.len(), *count, "{name}"); + assert!(files.iter().all(|path| path.is_file())); + assert!(files[0].parent().unwrap().join("proofs.json").is_file()); + if *name == "IntegerMath" { + assert_eq!( + programs + .iter() + .filter(|p| p.postcondition.is_some()) + .count(), + 3 + ); + } + } +} + +/// Runs the actual proof blocks in the existing fixtures, rather than copies of them. +/// Defaults to the verified conversion/Boolean suite. NASH_PROOF_MATCH can select +/// the expensive arithmetic/ordering candidates; these must still fail closed. +#[tokio::test] +#[ignore = "requires the pinned Lean project and Z3"] +async fn live_existing_test_proofs() { + let matching = std::env::var("NASH_PROOF_MATCH").unwrap_or_default(); + let fuel = std::env::var("NASH_PROOF_FUEL").map_or(500, |s| s.parse().unwrap()); + let directory = + std::env::temp_dir().join(format!("nash-existing-proofs-{}", std::process::id())); + let config = nash_proof::Config { + lean_project: std::env::var_os("NASH_PROOF_LEAN_PROJECT") + .expect("Lean project") + .into(), + fuel, + postcondition_fuel: 1000, + solver_timeout: 30, + wall_timeout: std::time::Duration::from_secs(60), + }; + let mut checked = 0; + let mut failures = Vec::new(); + for (fixture, source, _) in EXISTING_FIXTURES { + let programs = compile(source).await.unwrap(); + let programs: Vec<_> = programs + .into_iter() + .filter(|p| { + if matching.is_empty() { + *fixture == "DataConversions" || p.name == "boolean equality agrees with logic" + } else { + format!("{fixture}/{}", p.name).contains(&matching) + } + }) + .collect(); + if programs.is_empty() { + continue; + } + let files = nash_proof::export(&programs, &directory, fuel, 1000, 30).unwrap(); + for (program, source) in programs.iter().zip(files) { + let outcome = nash_proof::run(program, &source, &config).unwrap(); + eprintln!("{fixture}/{}: {:?}", program.name, outcome.status); + if !outcome.passed() { + failures.push(format!( + "{fixture}/{}: {:?}: {}", + program.name, outcome.status, outcome.diagnostics + )); + } + checked += 1; + } + } + assert!(checked > 0, "no existing proofs matched"); + assert!(failures.is_empty(), "{}", failures.join("\n")); +} + +#[tokio::test] +async fn vesting_authorization_proofs_compile_and_export() { + use nash_driver::{FileSystemSource, Project, build_graph_with_tests}; + let root = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../../examples/vesting"); + let project = Project::load(root).await.unwrap(); + let db = Arc::new(Mutex::new(Database::new(FileSystemSource::new()))); + let modules = project.discover_modules(&*db.lock().await).await.unwrap(); + let roots = project + .discover_own_modules(&*db.lock().await) + .await + .unwrap(); + let graph = build_graph_with_tests( + db.clone(), + &modules.keys().cloned().collect::>(), + &roots.keys().cloned().collect::>(), + ) + .await + .unwrap(); + let (report, programs) = test_with(db, &graph, &modules, move |solved| { + nash_driver::build::compile_proofs_matching_with( + solved, + |uri| roots.contains_key(uri).then(nash_config::Build::default), + |_, _| true, + ) + }) + .await; + assert!(report.is_success(), "{report:#?}"); + let programs = programs.unwrap().unwrap(); + assert_eq!(programs.len(), 2); + assert!( + programs + .iter() + .all(|p| p.postcondition.as_ref().unwrap().result == nash_proof::ReturnDomain::Unit) + ); + assert_eq!(programs[0].domains, vec![nash_proof::Domain::Spending(3)]); + assert_eq!( + programs[1].domains, + vec![nash_proof::Domain::Int, nash_proof::Domain::Spending(3)] + ); + let output = std::env::temp_dir().join(format!("nash-vesting-proofs-{}", std::process::id())); + let files = nash_proof::export(&programs, &output, 1000, 1500, 30).unwrap(); + assert_eq!(files.len(), 2); +} diff --git a/crates/nash-driver/tests/vesting.rs b/crates/nash-driver/tests/vesting.rs index 60755278..c0c01952 100644 --- a/crates/nash-driver/tests/vesting.rs +++ b/crates/nash-driver/tests/vesting.rs @@ -76,11 +76,48 @@ async fn bundled_base_vesting_artifacts_execute_all_ledger_cases() { output.module ); let parameterized = output.module == "VestingParam"; + let applied = if parameterized { + program.apply(&arena, Term::integer_from(&arena, 5)) + } else { + program + }; + for malformed in [ + PlutusData::integer_from(&arena, 0), + PlutusData::constr(&arena, 0, &[]), + PlutusData::constr(&arena, 1, &[]), + ] { + assert!( + applied + .apply(&arena, Term::data(&arena, malformed)) + .eval(&arena) + .term + .is_err(), + "{}: malformed context accepted", + output.module + ); + } for (name, deadline, redeemer, signer, expected) in [ ("claim after deadline", 10, 0, &b""[..], true), ("claim before deadline", 30, 0, &b""[..], false), + ( + "claim exactly at deadline", + if parameterized { 15 } else { 20 }, + 0, + &b""[..], + false, + ), + ( + "claim just after deadline", + if parameterized { 14 } else { 19 }, + 0, + &b""[..], + true, + ), + ("claim with negative deadline", -100, 0, &b""[..], true), ("cancel signed by owner", 10, 1, &[0xaa][..], true), ("cancel unsigned", 10, 1, &b""[..], false), + ("cancel signed by another key", 10, 1, &[0xcc][..], false), + ("cancel ignores deadline", 100, 1, &[0xaa][..], true), ] { let datum = PlutusData::constr( &arena, diff --git a/crates/nash-proof/src/lean.rs b/crates/nash-proof/src/lean.rs index 67accd1c..9fcb34d0 100644 --- a/crates/nash-proof/src/lean.rs +++ b/crates/nash-proof/src/lean.rs @@ -1,4 +1,4 @@ -use crate::{Domain, ProofProgram}; +use crate::{Domain, ProofProgram, ReturnDomain}; use nash_source::Expect; pub fn lakefile() -> &'static str { @@ -47,6 +47,12 @@ def execute (script : PlutusScript) (params : List PlutusCore.UPLC.Term.Term) (f -- Rejection includes an unfinished state at the configured execution limit. def isRejected (state : State) : Prop := ¬ PlutusCore.UPLC.Utils.isSuccessful state +-- A postcondition returns a Boolean; False is a refutation, not an execution error. +def conditionHolds (state : State) : Prop := + match state with + | .Halt (.VCon (.Bool value)) => value = true + | _ => False + open Lean Elab Command Term Meta syntax (name := prepare) "#nash_prep" ident ident ident num : command @@ -87,7 +93,7 @@ def verifyImpl : CommandElab := fun stx => do end NashProof "##; -pub fn render(program: &ProofProgram, fuel: u32, timeout: u32) -> String { +pub fn render(program: &ProofProgram, fuel: u32, postcondition_fuel: u32, timeout: u32) -> String { let mut text = SUPPORT.to_owned(); let version = match program.plutus_version { nash_config::PlutusVersion::V1 => 1, @@ -98,30 +104,25 @@ pub fn render(program: &ProofProgram, fuel: u32, timeout: u32) -> String { text.push_str(&format!("\ndef script : PlutusCore.UPLC.PlutusScript.PlutusScript :=\n {{ lang := .PlutusV{version}, script := flatEncodedScriptFromHexM \"{}\" }}\n", hex::encode(&program.flat))); let mut binders = String::new(); let mut args = Vec::new(); - let mut terms = Vec::new(); let mut assumptions = Vec::new(); + let mut parameters = Vec::new(); for (i, domain) in program.domains.iter().enumerate() { let name = format!("input{i}"); let (ty, term, assumption) = domain.input(&name); binders.push_str(&format!(" ({name} : {ty})")); + parameters.push(( + name.clone(), + ty.clone(), + term.clone(), + *domain == Domain::Bool, + )); args.push(name); - terms.push(term); if let Some(assumption) = assumption { assumptions.push(assumption); } } - text.push_str(&format!( - "\ndef inputs{binders} : List Term := [{}]\n", - terms.join(", ") - )); - text.push_str(&format!("\n#nash_prep prepared script inputs {fuel}\n")); + prepare(&mut text, "prepared", "script", ¶meters, fuel); let state = format!("(prepared {})", args.join(" ")); - let claim = match program.expect { - Expect::Pass => format!("isSuccessful {state}"), - Expect::Fail => format!("NashProof.isRejected {state}"), - // Refuting universal success finds a script error or fuel exhaustion. - Expect::FailOnce => format!("isSuccessful {state}"), - }; let quantify = |body: &str| { let pre = if assumptions.is_empty() { String::new() @@ -134,6 +135,47 @@ pub fn render(program: &ProofProgram, fuel: u32, timeout: u32) -> String { format!("∀{binders}, {pre}{body}") } }; + let claim = if let Some(postcondition) = &program.postcondition { + let (ty, term, pattern) = postcondition.result.input("returned"); + text.push_str(&format!("\ndef postconditionScript : PlutusCore.UPLC.PlutusScript.PlutusScript :=\n {{ lang := .PlutusV{version}, script := flatEncodedScriptFromHexM \"{}\" }}\n", hex::encode(&postcondition.flat))); + let mut post_parameters = parameters.clone(); + post_parameters.push(( + "returned".into(), + ty, + term, + postcondition.result == ReturnDomain::Bool, + )); + prepare( + &mut text, + "preparedPostcondition", + "postconditionScript", + &post_parameters, + postcondition_fuel, + ); + let result = if postcondition.result == ReturnDomain::Unit { + "()" + } else { + "returned" + }; + let check = format!("(preparedPostcondition {} {result})", args.join(" ")); + let complete = format!( + "match {state} with | .Halt (.VCon ({pattern})) => isSuccessful {check} ∨ isUnsuccessful {check} | _ => True" + ); + text.push_str(&format!( + "\n#nash_verify \"postcondition_completion\" {timeout} [{}]\n", + quantify(&complete) + )); + format!( + "match {state} with | .Halt (.VCon ({pattern})) => NashProof.conditionHolds {check} | .Halt _ => False | _ => True" + ) + } else { + match program.expect { + Expect::Pass => format!("isSuccessful {state}"), + Expect::Fail => format!("NashProof.isRejected {state}"), + // Refuting universal success finds a script error or fuel exhaustion. + Expect::FailOnce => format!("isSuccessful {state}"), + } + }; text.push_str(&format!( "\n#nash_verify \"property\" {timeout} [{}]\n", quantify(&claim) @@ -141,6 +183,93 @@ pub fn render(program: &ProofProgram, fuel: u32, timeout: u32) -> String { text } +/// Specializing up to two Boolean parameters avoids carrying their Data-encoding +/// branches through every CEK step. Both values remain covered by the obligation. +/// Other parameters stay symbolic; the cap keeps export size linear in their count. +fn prepare( + text: &mut String, + name: &str, + script: &str, + parameters: &[(String, String, String, bool)], + fuel: u32, +) { + let split: Vec<_> = parameters + .iter() + .enumerate() + .filter(|(_, p)| p.3) + .take(2) + .map(|(i, _)| i) + .collect(); + let binders = |indices: &[usize]| { + indices + .iter() + .map(|&i| format!(" ({} : {})", parameters[i].0, parameters[i].1)) + .collect::() + }; + let all: Vec<_> = (0..parameters.len()).collect(); + let remaining: Vec<_> = all.iter().copied().filter(|i| !split.contains(i)).collect(); + let arguments = remaining + .iter() + .map(|&i| parameters[i].0.as_str()) + .collect::>() + .join(" "); + for case in 0..(1 << split.len()) { + let suffix = if split.is_empty() { + String::new() + } else { + format!("Case{case}") + }; + let terms = parameters + .iter() + .enumerate() + .map(|(i, p)| { + if let Some(bit) = split.iter().position(|&j| j == i) { + format!("Term.Const (Const.Bool {})", case & (1 << bit) != 0) + } else { + p.2.clone() + } + }) + .collect::>(); + text.push_str(&format!( + "\ndef {name}Inputs{suffix}{} : List Term := [{}]\n", + binders(&remaining), + terms.join(", ") + )); + text.push_str(&format!( + "\n#nash_prep {name}{suffix} {script} {name}Inputs{suffix} {fuel}\n" + )); + } + if !split.is_empty() { + fn branches( + parameters: &[(String, String, String, bool)], + split: &[usize], + depth: usize, + case: usize, + name: &str, + arguments: &str, + ) -> String { + if depth == split.len() { + return format!("({name}Case{case} {arguments})"); + } + let yes = branches( + parameters, + split, + depth + 1, + case | (1 << depth), + name, + arguments, + ); + let no = branches(parameters, split, depth + 1, case, name, arguments); + format!("(if {} then {yes} else {no})", parameters[split[depth]].0) + } + text.push_str(&format!( + "\nnoncomputable def {name}{} : PlutusCore.UPLC.CekMachine.State :=\n {}\n", + binders(&all), + branches(parameters, &split, 0, 0, name, &arguments) + )); + } +} + impl Domain { fn input(self, name: &str) -> (String, String, Option) { let (ty, term) = match self { @@ -186,6 +315,35 @@ impl Domain { } } +impl ReturnDomain { + fn input(self, name: &str) -> (String, String, String) { + let domain = match self { + Self::Int => Domain::Int, + Self::Bool => Domain::Bool, + Self::Bytes => Domain::Bytes, + Self::String => Domain::String, + Self::Data => Domain::Data, + Self::Unit => { + return ( + "Unit".into(), + "Term.Const Const.Unit".into(), + "Const.Unit".into(), + ); + } + }; + let (ty, term, _) = domain.input(name); + let constructor = match self { + Self::Int => "Integer", + Self::Bool => "Bool", + Self::Bytes => "ByteString", + Self::String => "String", + Self::Data => "Data", + Self::Unit => unreachable!(), + }; + (ty, term, format!("Const.{constructor} {name}")) + } +} + #[cfg(test)] mod tests { use super::*; @@ -197,9 +355,10 @@ mod tests { expect: Expect::Pass, domains: vec![Domain::Spending(3)], flat: vec![1, 2, 3], + postcondition: None, plutus_version: nash_config::PlutusVersion::V3, }; - let source = render(&program, 1000, 5); + let source = render(&program, 1000, 1000, 5); assert!(source.contains("flatEncodedScriptFromHexM \"010203\"")); assert!(source.contains("∀ (input0 : CardanoLedgerApi.V3.ScriptContext), CardanoLedgerApi.V3.validSpendingContext input0 →")); assert!(source.contains("isSuccessful (prepared input0)")); diff --git a/crates/nash-proof/src/lib.rs b/crates/nash-proof/src/lib.rs index f1910746..d9fbc9ac 100644 --- a/crates/nash-proof/src/lib.rs +++ b/crates/nash-proof/src/lib.rs @@ -48,5 +48,23 @@ pub struct ProofProgram { pub expect: Expect, pub domains: Vec, pub flat: Vec, + pub postcondition: Option, pub plutus_version: nash_config::PlutusVersion, } + +/// Constant representations that can cross the computation/postcondition boundary. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum ReturnDomain { + Int, + Bool, + Bytes, + String, + Unit, + Data, +} + +#[derive(Debug)] +pub struct Postcondition { + pub flat: Vec, + pub result: ReturnDomain, +} diff --git a/crates/nash-proof/src/runner.rs b/crates/nash-proof/src/runner.rs index f3026f3a..822fa77f 100644 --- a/crates/nash-proof/src/runner.rs +++ b/crates/nash-proof/src/runner.rs @@ -13,6 +13,7 @@ pub struct Config { /// An existing Lake project with the three pinned dependencies built. pub lean_project: PathBuf, pub fuel: u32, + pub postcondition_fuel: u32, pub solver_timeout: u32, pub wall_timeout: Duration, } @@ -21,6 +22,8 @@ pub struct Config { #[serde(rename_all = "snake_case")] pub enum Status { Verified, + VerifiedPartial, + PostconditionExhausted, Counterexample, Witness, NoWitness, @@ -35,13 +38,17 @@ pub struct Outcome { pub name: String, pub status: Status, pub fuel: u32, + pub postcondition_fuel: u32, pub trust: &'static str, pub counterexample: Vec, pub diagnostics: String, } impl Outcome { pub fn passed(&self) -> bool { - matches!(self.status, Status::Verified | Status::Witness) + matches!( + self.status, + Status::Verified | Status::VerifiedPartial | Status::Witness + ) } } @@ -57,6 +64,7 @@ pub fn export( programs: &[ProofProgram], destination: &Path, fuel: u32, + postcondition_fuel: u32, timeout: u32, ) -> std::io::Result> { // Only reuse a project we created, with the unchanged pinned configuration. @@ -95,9 +103,9 @@ pub fn export( for (i, program) in programs.iter().enumerate() { let filename = format!("Proof{i}.lean"); let file = run.join(&filename); - fs::write(&file, render(program, fuel, timeout))?; + fs::write(&file, render(program, fuel, postcondition_fuel, timeout))?; manifest.push(serde_json::json!({"file": filename, "module": program.module, "name": program.name, - "fuel": fuel, "trust": "smt_verified", "plutus_version": format!("{:?}", program.plutus_version)})); + "fuel": fuel, "postcondition_fuel": postcondition_fuel, "kind": if program.postcondition.is_some() { "partial_correctness" } else { "execution" }, "trust": "smt_verified", "plutus_version": format!("{:?}", program.plutus_version)})); files.push(file); } fs::write( @@ -143,19 +151,37 @@ pub fn run(program: &ProofProgram, source: &Path, config: &Config) -> std::io::R }; let out = fs::read_to_string(stdout)?; let err = fs::read_to_string(stderr)?; - let (status, counterexample) = classify(program.expect, success, timed_out, &out); + let (status, counterexample) = classify_mode( + program.expect, + program.postcondition.is_some(), + success, + timed_out, + &out, + ); Ok(Outcome { module: program.module.clone(), name: program.name.clone(), status, fuel: config.fuel, + postcondition_fuel: config.postcondition_fuel, trust: "smt_verified", counterexample, diagnostics: format!("{out}{err}"), }) } +#[cfg(test)] fn classify(expect: Expect, success: bool, timed_out: bool, output: &str) -> (Status, Vec) { + classify_mode(expect, false, success, timed_out, output) +} + +fn classify_mode( + expect: Expect, + partial: bool, + success: bool, + timed_out: bool, + output: &str, +) -> (Status, Vec) { if timed_out { return (Status::Timeout, vec![]); } @@ -175,11 +201,36 @@ fn classify(expect: Expect, success: bool, timed_out: bool, output: &str) -> (St } } let existential = expect == Expect::FailOnce; - if replies.len() != 1 || replies[0].query != "property" { + let queries: &[&str] = if partial { + &["postcondition_completion", "property"] + } else { + &["property"] + }; + if !replies + .iter() + .map(|r| r.query.as_str()) + .eq(queries.iter().copied()) + { return (Status::BackendError, vec![]); } + if partial { + if expect != Expect::Pass { + return (Status::BackendError, vec![]); + } + match replies[0].status.as_str() { + "valid" => {} + "falsified" => { + return ( + Status::PostconditionExhausted, + replies.remove(0).counterexample, + ); + } + _ => return (Status::Unknown, vec![]), + } + } let property = replies.pop().unwrap(); let status = match (existential, property.status.as_str()) { + (false, "valid") if partial => Status::VerifiedPartial, (false, "valid") => Status::Verified, (false, "falsified") => Status::Counterexample, (true, "falsified") => Status::Witness, @@ -246,4 +297,25 @@ mod tests { ); } } + #[test] + fn partial_correctness_requires_a_completed_postcondition() { + for (completion, property, expected) in [ + ("valid", "valid", Status::VerifiedPartial), + ("valid", "falsified", Status::Counterexample), + ("falsified", "valid", Status::PostconditionExhausted), + ("unknown", "valid", Status::Unknown), + ("valid", "unknown", Status::Unknown), + ] { + let output = + reply("postcondition_completion", completion) + &reply("property", property); + assert_eq!( + classify_mode(Expect::Pass, true, true, false, &output).0, + expected + ); + } + assert_eq!( + classify_mode(Expect::Pass, true, true, false, &reply("property", "valid")).0, + Status::BackendError + ); + } } diff --git a/docs/proofs.md b/docs/proofs.md index 20a45000..522b41f3 100644 --- a/docs/proofs.md +++ b/docs/proofs.md @@ -122,11 +122,102 @@ models**, not independently kernel-certified proofs. Counterexamples are the library's textual SMT models; automatic decoding, replay and shrinking are not yet implemented. +## Partial correctness for recursive functions + +Use `Proof.returns computation (\result -> condition)` as the sole expression +in a passing proof body. The compiler emits two separate UPLC programs: the +computation and its Boolean postcondition. For example: + +```elm +proof + import Proof + + prop "square root is correct when it returns" = + let value via Proof.int in + do + Proof.returns (Int.isqrt value) (\root -> + root >= 0 && root * root <= value && value < (root + 1) * (root + 1)) +``` + +This checks the returned root against adjacent squares, without calling `isqrt` +again in the specification. The condition can refer to every original symbolic +input as well as the returned value. Multiple inputs use a single `let`: + +```elm +let left via Proof.int + right via Proof.int +in +``` + +A `verified_partial` result means every successful return **within `--fuel`** +satisfies the condition. An error or exhaustion supplies no successful return; +it remains rejection under Nash's execution-limit semantics. Partial correctness +does not establish termination, acceptance, or successful return for any input. +In particular, an insufficient execution limit can make the implication vacuous. +It also does not establish correctness of returns beyond that limit. + +The postcondition has its own `--postcondition-fuel` limit. A false condition or +an evaluation error refutes the property. If the checker exhausts on a successful +computation, the result is `postcondition_exhausted` and the command fails; it +cannot verify the property. Solver unknowns and timeouts also remain inconclusive. +The computation, checker completion, and condition are checked independently so +an assertion failure cannot disappear behind a successful-return guard. + +Returned values can be native integers, Booleans, bytes, strings, unit, or +Data-represented types. Native lists, tuples, custom ADTs and functions cannot +cross this boundary yet. `fail` and `fail once` modifiers are rejected for +`Proof.returns`. Wrapping it in another function or placing it inside other +statements does not select partial correctness: only the direct body call is +recognized. Outside that position it is an ordinary strict assertion helper. + +## Coverage of existing tests + +The existing Base fixtures now include 15 proof declarations alongside their +runtime checks. They use the actual bundled functions and compile through the +same backend as validators. The three fixtures have also been formatted with +`nash format`. + +| Fixture | Proof coverage | Current verification | +|---|---|---| +| `DataConversions.nash` | arbitrary integer, Boolean and byte roundtrips; optional integer decoding; malformed scalar encodings | all seven verified with 500 execution steps | +| `Equality.nash` | Boolean equality, integer comparison, antisymmetry, min/max partition | Boolean equality verified; three integer obligations currently time out | +| `IntegerMath.nash` | adjacent-square root specification; greatest-common-divisor and least-common-multiple divisibility specifications; negative square-root rejection | compile/export checked; symbolic execution currently times out | + +The gcd and lcm specifications quantify over an additional candidate divisor or +multiple. They use arithmetic divisibility, not another call to the algorithm +under verification. Existing oracle examples, finite-grid tests, UTF-8 cases, +collection tests and cross-language Cardano goldens remain active while the +more expensive obligations need backend improvements. + +The real `Vesting` and `VestingParam` examples also have partial-correctness +proofs over `Proof.spendingV3`: accepting a ledger-valid context must imply the +claim deadline or owner-signature policy. They use the existing unchecked +datum/redeemer casts and do not establish full application encoding validation. Both export successfully; verification +currently times out. Their serialized-artifact tests now cover exact deadlines, +just-after deadlines, negative deadlines, different signers, cancellation after +the deadline, and malformed outer contexts. Artifact encoding, traces, budgets +and optimized/unoptimized comparisons remain runtime checks. + +The default optional regression suite runs the eight verified conversion/Boolean +obligations directly from their existing fixture files: + +```sh +NASH_PROOF_LEAN_PROJECT=/path/to/prebuilt/lean-project \ + cargo test -p nash-driver --test proofs live_existing_test_proofs -- --ignored +``` + +Use `NASH_PROOF_MATCH=Equality` or `NASH_PROOF_MATCH=IntegerMath` to attempt the +expensive candidates; `NASH_PROOF_MATCH=/` selects every fixture obligation. +`NASH_PROOF_FUEL` overrides the default 500 execution steps. Unverified results +fail the test. This optional suite requires Lean and Z3; regular workspace tests +always type-check and compile/export all 15 declarations. + ## Command and generated artifacts ```sh nash proof . nash verify . --match "integer identity" --fuel 10000 --timeout 30 +nash proof . --fuel 1000 --postcondition-fuel 2000 nash proof . --emit-only --output build/exported-proofs nash proof . --lean-project /path/to/prebuilt/lean-project --json ``` @@ -151,8 +242,8 @@ first use; this needs network access and an installed Lean/Lake toolchain. already built project without updating it; it must provide the pinned API. An appropriate Z3 must be on `PATH` for obligations requiring SMT reasoning. -Defaults: 10,000 CEK steps, 30 seconds per SMT query, 120 seconds total per -property (including preprocessing). Backend setup is outside that per-property +Defaults: 10,000 computation steps, 10,000 postcondition steps, 30 seconds per +SMT query, 120 seconds total per property (including preprocessing). Backend setup is outside that per-property limit. On Unix, a timed-out run terminates the Lake/Lean/solver process group. The command exits unsuccessfully on refutation, unknown, timeout, backend failure, no witness, or an empty selection. @@ -162,10 +253,28 @@ project and its dependency cache, while preserving each run in its own `run-...` directory with its Lean sources and `proofs.json`. Existing generated sources remain available for review or direct `lake env lean run-.../ProofN.lean` use. -`examples/proofs` demonstrates the complete flow. An optional integration test -runs the real backend: +`examples/proofs` demonstrates the complete flow: + +```sh +nash proof examples/proofs --fuel 80 --postcondition-fuel 150 +``` + +Increasing fuel can substantially increase preprocessing time, even when a +smaller execution limit already sufficed. A timeout is an unverified result, +not a refutation. + +An optional integration test runs the real backend and covers expected outcomes and execution exhaustion: ```sh NASH_PROOF_LEAN_PROJECT=/path/to/prebuilt/lean-project \ cargo test -p nash-driver --test proofs live_backend -- --ignored ``` + +Partial-correctness regressions exercise all six supported return representations, +incorrect results, computation errors/exhaustion, checker errors, and checker +exhaustion: + +```sh +NASH_PROOF_LEAN_PROJECT=/path/to/prebuilt/lean-project \ + cargo test -p nash-driver --test proofs live_partial_correctness -- --ignored +``` diff --git a/examples/proofs/src/Main.nash b/examples/proofs/src/Main.nash index 4f151f24..853b73fd 100644 --- a/examples/proofs/src/Main.nash +++ b/examples/proofs/src/Main.nash @@ -34,3 +34,10 @@ proof in do assert (x >= 0) + + prop "function result satisfies its postcondition" = + let + value via Proof.int + in + do + Proof.returns (addZero value) (\result -> result == value) diff --git a/examples/vesting/README.md b/examples/vesting/README.md index aa362609..8871c57f 100644 --- a/examples/vesting/README.md +++ b/examples/vesting/README.md @@ -26,3 +26,18 @@ The codegen snapshots and serialized artifact tests apply exactly one context and exercise successful and failed claims and cancellations. The snapshots compare unoptimized and optimized Core/UPLC and execution outcomes. Budget regressions run separately in `tools/optimizer-perf`. + +Both validators include a `proof` block for partial correctness: every successful +return within the execution limit must satisfy the deadline or owner-signature +policy. `Proof.spendingV3` quantifies over CardanoLedgerApiBlaster's ledger-valid +spending contexts. These are candidate obligations: export and type checking +pass, but the current symbolic evaluator times out during verification. + +```sh +nash proof examples/vesting --emit-only +nash proof examples/vesting --fuel 1000 --postcondition-fuel 1500 +``` + +The runtime suite retains artifact and optimizer checks and adds exact-deadline, +just-after-deadline, negative-deadline, wrong-signer and malformed-context cases. +See [proof semantics and current coverage](../../docs/proofs.md). diff --git a/examples/vesting/app/src/Vesting.nash b/examples/vesting/app/src/Vesting.nash index f6caf2d0..934f2f4e 100644 --- a/examples/vesting/app/src/Vesting.nash +++ b/examples/vesting/app/src/Vesting.nash @@ -40,3 +40,46 @@ check datum redeemer tx = Cancel -> assert (signedBy tx datum.owner) + + +proof + import Proof + + -- Verify authorization with the same unchecked application casts as main. + -- This does not claim that the datum/redeemer encoding is fully validated. + prop "accepted spending contexts satisfy authorization" = + let + ctx via Proof.spendingV3 + in + do + Proof.returns + (main ctx) + ( + \_ -> + case ctx of + Constr + pair(0, [ tx + , rawAction + , Constr + pair(1, [ _ + , Constr pair(0, [ rawDatum ]) + ]) + ]) -> + let + datum : Datum + datum = Primitive.coerce rawDatum + + action : Redeemer + action = Primitive.coerce rawAction + in + case action of + Claim -> + lower datum.deadline < + validAfter tx + + Cancel -> + signedBy tx datum.owner + + _ -> + False + ) diff --git a/examples/vesting/app/src/VestingParam.nash b/examples/vesting/app/src/VestingParam.nash index 5ad62dad..a19c7838 100644 --- a/examples/vesting/app/src/VestingParam.nash +++ b/examples/vesting/app/src/VestingParam.nash @@ -48,3 +48,48 @@ check minimumLock datum redeemer tx = Cancel -> assert (signedBy tx datum.owner) + + +proof + import Proof + + -- Verify authorization with the same unchecked application casts as main. + -- This does not claim that the datum/redeemer encoding is fully validated. + prop "accepted spending contexts satisfy authorization" = + let + minimumLock via Proof.int + ctx via Proof.spendingV3 + in + do + Proof.returns + (main minimumLock ctx) + ( + \_ -> + case ctx of + Constr + pair(0, [ tx + , rawAction + , Constr + pair(1, [ _ + , Constr pair(0, [ rawDatum ]) + ]) + ]) -> + let + datum : Datum + datum = Primitive.coerce rawDatum + + action : Redeemer + action = Primitive.coerce rawAction + in + case action of + Claim -> + lower datum.deadline + + minimumLock < + validAfter tx + + Cancel -> + signedBy tx datum.owner + + _ -> + False + ) From e97b5685f2eb0e3356a384be0d2ad3077dd7806a Mon Sep 17 00:00:00 2001 From: RSoulatIOHK Date: Thu, 1 Oct 2026 12:49:45 +0200 Subject: [PATCH 4/7] refactor: give proofs dedicated AST types and obligations --- .sampo/changesets/lean-blaster-proofs.md | 5 + crates/nash-ast/src/lib.rs | 44 ++++- crates/nash-can/src/error.rs | 7 +- crates/nash-can/src/expression.rs | 11 +- crates/nash-can/src/module.rs | 209 ++++++++++++++++++----- crates/nash-codegen/src/demand.rs | 12 ++ crates/nash-codegen/src/proofs.rs | 41 ++--- crates/nash-constrain/src/error.rs | 3 + crates/nash-driver/src/compile.rs | 10 +- crates/nash-driver/tests/proofs.rs | 51 ++++++ crates/nash-fmt/src/declarations.rs | 134 +++++++++++---- crates/nash-fmt/src/tests.rs | 12 ++ crates/nash-fmt/src/tests/semantic.rs | 4 + crates/nash-nitpick/src/check.rs | 15 +- crates/nash-parse/src/error.rs | 1 + crates/nash-parse/src/tests_block.rs | 114 +++++++++---- crates/nash-report/src/canonicalize.rs | 22 ++- crates/nash-report/src/syntax/module.rs | 7 + crates/nash-report/src/type_.rs | 5 + crates/nash-solve/src/solve/infer.rs | 186 ++++++++++++++------ crates/nash-source/src/lib.rs | 31 +++- docs/proofs.md | 7 + 22 files changed, 727 insertions(+), 204 deletions(-) diff --git a/.sampo/changesets/lean-blaster-proofs.md b/.sampo/changesets/lean-blaster-proofs.md index b8a6bcda..08376a27 100644 --- a/.sampo/changesets/lean-blaster-proofs.md +++ b/.sampo/changesets/lean-blaster-proofs.md @@ -23,3 +23,8 @@ limits, and explicit inconclusive checker exhaustion. Add universal properties to existing conversion, ordering and arithmetic fixtures and authorization properties to the vesting examples. Extend deadline, signer and malformed-context regressions while preserving runtime and oracle coverage. + +Represent proofs with dedicated source and canonical AST types, symbolic-domain +binders, and distinct execution and successful-return obligations. Reject proof +budgets during parsing and report proof-specific domain, expectation and +postcondition errors before code generation. diff --git a/crates/nash-ast/src/lib.rs b/crates/nash-ast/src/lib.rs index 87c588a4..3aa0a881 100644 --- a/crates/nash-ast/src/lib.rs +++ b/crates/nash-ast/src/lib.rs @@ -68,9 +68,51 @@ pub struct ViaBinder<'a> { pub generator: &'a Located>, } +/// A canonical proof never carries randomized generators or measured budgets. +#[derive(Debug)] +pub struct Proof<'a> { + pub region: Region, + pub name: &'a Located<&'a str>, + pub binders: &'a [ProofBinder<'a>], + pub obligation: ProofObligation<'a>, +} + +#[derive(Debug)] +pub struct ProofBinder<'a> { + pub pattern: &'a Located>, + pub domain: &'a Located>, +} + +#[derive(Debug)] +pub enum ProofObligation<'a> { + Execution { + expect: Expect, + body: &'a Located>, + }, + /// Successful returns must satisfy this Boolean postcondition. Failure + /// modifiers cannot be represented on a partial-correctness obligation. + Returns { + computation: &'a Located>, + postcondition: &'a Located>, + }, +} + +impl<'a> ProofObligation<'a> { + pub fn expressions(&self) -> impl Iterator>> { + let expressions = match *self { + Self::Execution { body, .. } => [Some(body), None], + Self::Returns { + computation, + postcondition, + } => [Some(computation), Some(postcondition)], + }; + expressions.into_iter().flatten() + } +} + pub struct Module<'a> { pub tests: &'a [Test<'a>], - pub proofs: &'a [Test<'a>], + pub proofs: &'a [Proof<'a>], pub traits: &'a [&'a Located>], pub impls: &'a [&'a Located>], pub kind: ModuleKind, diff --git a/crates/nash-can/src/error.rs b/crates/nash-can/src/error.rs index be3f570f..2eb54a85 100644 --- a/crates/nash-can/src/error.rs +++ b/crates/nash-can/src/error.rs @@ -38,9 +38,14 @@ pub enum Error<'a> { ProofDomain { region: Region, }, - ProofBudget { + ProofPartialExpectation { region: Region, }, + DuplicateProof { + name: &'a str, + first: Region, + second: Region, + }, DuplicateTest { name: &'a str, first: Region, diff --git a/crates/nash-can/src/expression.rs b/crates/nash-can/src/expression.rs index 437cb3a7..06926c9d 100644 --- a/crates/nash-can/src/expression.rs +++ b/crates/nash-can/src/expression.rs @@ -1712,8 +1712,8 @@ pub fn gather_typed_args<'a>( Ok((typed_args, current_type)) } -/// Only the outer test block is sequencing; nested expressions retain monadic do. -pub(crate) fn canonicalize_test_block<'a>( +/// Only the outer specification block is sequencing; nested expressions retain monadic do. +pub(crate) fn canonicalize_specification_block<'a>( bump: &'a Bump, env: &Scope<'_, 'a>, stmts: &[&'a Located>], @@ -1732,7 +1732,9 @@ pub(crate) fn canonicalize_test_block<'a>( stmt.region, free, warnings, - |env, free, warnings| canonicalize_test_block(bump, env, rest, last, free, warnings), + |env, free, warnings| { + canonicalize_specification_block(bump, env, rest, last, free, warnings) + }, ); } let (pattern, expr) = match stmt.value { @@ -1748,7 +1750,8 @@ pub(crate) fn canonicalize_test_block<'a>( pattern::verify(bump, env.module, DuplicatePatternContext::Destruct, pattern)?; let inner = env.add_locals(&bindings)?; let mut body_free = FreeLocals::new(); - let body = canonicalize_test_block(bump, &inner, rest, last, &mut body_free, warnings)?; + let body = + canonicalize_specification_block(bump, &inner, rest, last, &mut body_free, warnings)?; let outer = verify_bindings(WarningContext::Def, &bindings, body_free, warnings); merge_free_locals(free, outer, false); Ok(bump.alloc(Located::at( diff --git a/crates/nash-can/src/module.rs b/crates/nash-can/src/module.rs index 47689700..53afa04a 100644 --- a/crates/nash-can/src/module.rs +++ b/crates/nash-can/src/module.rs @@ -155,7 +155,7 @@ pub fn canonicalize<'a>( } } } - let tests = canonicalize_tests(bump, &test_env, module.tests, false, &mut warnings)?; + let tests = canonicalize_tests(bump, &test_env, module.tests, &mut warnings)?; let mut proof_env = env.clone(); if let Some(tests) = module.proofs { let defaults = crate::defaults::test_imports(bump, home, context.interfaces); @@ -191,7 +191,7 @@ pub fn canonicalize<'a>( } } } - let proofs = canonicalize_tests(bump, &proof_env, module.proofs, true, &mut warnings)?; + let proofs = canonicalize_proofs(bump, &proof_env, module.proofs, &mut warnings)?; let binops = canonicalize_binops(bump, &env, module.binops); let exports = canonicalize_exports(bump, module)?; if matches!(module.kind, nash_ast::ModuleKind::Validator(_)) @@ -249,7 +249,6 @@ fn canonicalize_tests<'a>( bump: &'a Bump, env: &Env<'a>, tests: Option<&nash_source::Tests<'a>>, - proof: bool, warnings: &mut Vec>, ) -> Result<&'a [nash_ast::Test<'a>], Vec>> { let Some(tests) = tests else { @@ -266,64 +265,167 @@ fn canonicalize_tests<'a>( second, }, )?; - let empty = BTreeMap::new(); - let base = environment::Scope::new(env, None, &empty)?; let mut result = Vec::new(); for test in tests.tests { - if proof && test.value.budget.is_some() { - return Err(vec![Error::ProofBudget { - region: test.region, - }]); - } let (binders, block) = match test.value.body { - nash_source::TestBody::Unit(body) => (&[][..], body), - nash_source::TestBody::Prop { binders, body } => (binders, body), + nash_source::TestBody::Unit(body) => (Vec::new(), body), + nash_source::TestBody::Prop { binders, body } => ( + binders + .iter() + .map(|b| (b.value.pattern, b.value.generator)) + .collect(), + body, + ), }; - let patterns: Vec<_> = binders.iter().map(|b| b.value.pattern).collect(); - let (patterns, bindings) = - pattern::verify_all(bump, env, DuplicatePatternContext::Destruct, &patterns)?; - let mut can_binders = Vec::new(); - for (binder, pattern) in binders.iter().zip(patterns) { - let generator = expression::canonicalize_expr( - bump, - &base, - binder.value.generator, - &mut expression::FreeLocals::new(), - warnings, - )?; - if proof - && !matches!(generator.value, nash_ast::Expr::VarForeign { reference, .. } - if reference.home.package == Some(nash_ast::primitives::BASE) && reference.home.name == "Proof") - { - return Err(vec![Error::ProofDomain { - region: binder.value.generator.region, - }]); - } - can_binders.push(nash_ast::ViaBinder { pattern, generator }); - } - let scope = base.add_locals(&bindings)?; - let mut free = expression::FreeLocals::new(); - let body = expression::canonicalize_test_block( - bump, - &scope, - block.stmts, - block.last, - &mut free, - warnings, - )?; - expression::verify_bindings(WarningContext::Def, &bindings, free, warnings); + let (binders, body) = + canonicalize_specification_body(bump, env, &binders, block, warnings)?; result.push(nash_ast::Test { region: test.region, name: test.value.name, expect: test.value.expect, budget: test.value.budget, - binders: bump.alloc_slice_fill_iter(can_binders), + binders: bump.alloc_slice_fill_iter( + binders + .into_iter() + .map(|(pattern, generator)| nash_ast::ViaBinder { pattern, generator }), + ), body, }); } Ok(bump.alloc_slice_fill_iter(result)) } +fn canonicalize_proofs<'a>( + bump: &'a Bump, + env: &Env<'a>, + proofs: Option<&nash_source::Proofs<'a>>, + warnings: &mut Vec>, +) -> Result<&'a [nash_ast::Proof<'a>], Vec>> { + let Some(proofs) = proofs else { + return Ok(&[]); + }; + dups::detect( + proofs + .proofs + .iter() + .map(|p| (p.value.name.value, p.value.name.region)), + |name, first, second| Error::DuplicateProof { + name, + first, + second, + }, + )?; + let mut result = Vec::new(); + for proof in proofs.proofs { + let (binders, block) = match proof.value.body { + nash_source::ProofBody::Unit(body) => (Vec::new(), body), + nash_source::ProofBody::Prop { binders, body } => ( + binders + .iter() + .map(|b| (b.value.pattern, b.value.domain)) + .collect(), + body, + ), + }; + let (binders, body) = + canonicalize_specification_body(bump, env, &binders, block, warnings)?; + for (_, domain) in &binders { + if !matches!(domain.value, nash_ast::Expr::VarForeign { reference, .. } + if reference.home.package == Some(nash_ast::primitives::BASE) + && reference.home.name == "Proof" + && matches!(reference.name, "int" | "integer" | "bool" | "bytes" | "byteString" | "string" | "data" | "spendingV1" | "spendingV2" | "spendingV3" | "mintingV1" | "mintingV2" | "mintingV3")) + { + return Err(vec![Error::ProofDomain { + region: domain.region, + }]); + } + } + // Resolve aliases before recognizing the partial-correctness marker. + let obligation = match body.value { + nash_ast::Expr::Call { + function, + arguments: [computation, postcondition], + } if matches!(function.value, nash_ast::Expr::VarForeign { reference, .. } + if reference.home.package == Some(nash_ast::primitives::BASE) + && reference.home.name == "Proof" && reference.name == "returns") => + { + if proof.value.expect != nash_ast::Expect::Pass { + return Err(vec![Error::ProofPartialExpectation { + region: proof.region, + }]); + } + nash_ast::ProofObligation::Returns { + computation, + postcondition, + } + } + _ => nash_ast::ProofObligation::Execution { + expect: proof.value.expect, + body, + }, + }; + result.push(nash_ast::Proof { + region: proof.region, + name: proof.value.name, + binders: bump.alloc_slice_fill_iter( + binders + .into_iter() + .map(|(pattern, domain)| nash_ast::ProofBinder { pattern, domain }), + ), + obligation, + }); + } + Ok(bump.alloc_slice_fill_iter(result)) +} + +type SourceBinder<'a> = ( + &'a Located>, + &'a Located>, +); +type CanonicalBinder<'a> = ( + &'a Located>, + &'a Located>, +); +type CanonicalSpecification<'a> = (Vec>, &'a Located>); + +/// Share name/pattern resolution and sequencing, without conflating declaration types. +fn canonicalize_specification_body<'a>( + bump: &'a Bump, + env: &Env<'a>, + binders: &[SourceBinder<'a>], + block: &nash_source::Block<'a>, + warnings: &mut Vec>, +) -> Result, Vec>> { + let empty = BTreeMap::new(); + let base = environment::Scope::new(env, None, &empty)?; + let patterns: Vec<_> = binders.iter().map(|(pattern, _)| *pattern).collect(); + let (patterns, bindings) = + pattern::verify_all(bump, env, DuplicatePatternContext::Destruct, &patterns)?; + let mut canonical = Vec::new(); + for ((_, value), pattern) in binders.iter().zip(patterns) { + let value = expression::canonicalize_expr( + bump, + &base, + value, + &mut expression::FreeLocals::new(), + warnings, + )?; + canonical.push((pattern, value)); + } + let scope = base.add_locals(&bindings)?; + let mut free = expression::FreeLocals::new(); + let body = expression::canonicalize_specification_block( + bump, + &scope, + block.stmts, + block.last, + &mut free, + warnings, + )?; + expression::verify_bindings(WarningContext::Def, &bindings, free, warnings); + Ok((canonical, body)) +} + fn canonicalize_decls<'a>( bump: &'a Bump, env: &Env<'a>, @@ -1221,13 +1323,26 @@ fn canonicalize_binops<'a>( fn collect_used_modules<'a>(module: &CanModule<'a>) -> BTreeSet<&'a str> { let mut used = BTreeSet::new(); let home = module.name; - for test in module.tests.iter().chain(module.proofs) { + for test in module.tests { collect_from_expr(&test.body.value, home, &mut used); for binder in test.binders { collect_from_expr(&binder.generator.value, home, &mut used); collect_from_pattern(&binder.pattern.value, home, &mut used); } } + for proof in module.proofs { + for body in proof.obligation.expressions() { + collect_from_expr(&body.value, home, &mut used); + } + for binder in proof.binders { + collect_from_expr(&binder.domain.value, home, &mut used); + collect_from_pattern(&binder.pattern.value, home, &mut used); + } + // The canonical partial obligation no longer contains its marker call. + if matches!(proof.obligation, nash_ast::ProofObligation::Returns { .. }) { + used.insert("Proof"); + } + } for binop in module.binops { add_if_foreign(home, binop.value.function.home, &mut used); } diff --git a/crates/nash-codegen/src/demand.rs b/crates/nash-codegen/src/demand.rs index 4bf1736b..3492ab4c 100644 --- a/crates/nash-codegen/src/demand.rs +++ b/crates/nash-codegen/src/demand.rs @@ -141,6 +141,18 @@ pub fn analyze<'a>(modules: &[(&Module<'a>, &SolvedTypes<'a>)]) -> Demands<'a> { } a.expr(test.body, owner, &scope, solved); } + for proof in module.proofs { + for body in proof.obligation.expressions() { + let owner = NodeId::expr(body); + a.owner(owner, None, solved); + let mut scope = env.clone(); + for binder in proof.binders { + a.expr(binder.domain, owner, &scope, solved); + bind(binder.pattern, None, &mut scope); + } + a.expr(body, owner, &scope, solved); + } + } } loop { let previous = a.demands.clone(); diff --git a/crates/nash-codegen/src/proofs.rs b/crates/nash-codegen/src/proofs.rs index a8c04ac8..e9149258 100644 --- a/crates/nash-codegen/src/proofs.rs +++ b/crates/nash-codegen/src/proofs.rs @@ -3,7 +3,7 @@ use crate::{ build::{Binding, Build, Engine, TraceConfig}, decision_tree::{self, MatchBranch, MatchInputs}, }; -use nash_ast::{Expr, ModuleName, NodeId, primitives}; +use nash_ast::{Expr, ModuleName, NodeId, ProofObligation, primitives}; use nash_ir::{ core::*, ty::{ConstTy, Ty}, @@ -20,14 +20,8 @@ pub enum Error<'a> { "proof input must use a domain from the bundled Proof module; random generators and arbitrary values are not proof domains" )] Domain, - #[error( - "execution-budget constraints are not supported in proof blocks; use tests for measured budgets" - )] - Budget, #[error("ledger domain version must match the proof's Plutus version")] LedgerVersion, - #[error("Proof.returns is a partial-correctness claim and cannot use fail or fail once")] - PartialExpectation, #[error( "Proof.returns requires an integer, Boolean, bytes, string, unit or Data-represented result" )] @@ -61,7 +55,7 @@ pub fn compile_proofs_matching<'a>( build: &Build<'a, '_>, module: ModuleName<'a>, version: nash_config::PlutusVersion, - mut include: impl FnMut(&nash_ast::Test<'a>) -> bool, + mut include: impl FnMut(&nash_ast::Proof<'a>) -> bool, ) -> Result, Error<'a>> { let input = build .inputs @@ -73,14 +67,11 @@ pub fn compile_proofs_matching<'a>( if !include(proof) { continue; } - if proof.budget.is_some() { - return Err(Error::Budget); - } let domains = proof .binders .iter() .map(|binder| { - let Expr::VarForeign { reference, .. } = binder.generator.value else { + let Expr::VarForeign { reference, .. } = binder.domain.value else { return Err(Error::Domain); }; if reference.home.package != Some(primitives::BASE) @@ -132,25 +123,17 @@ pub fn compile_proofs_matching<'a>( params.push(value); patterns.push((binder, value, records, literals, bindings)); } - let partial = match proof.body.value { - Expr::Call { - function, - arguments: [computation, condition], - } if matches!(function.value, Expr::VarForeign { reference, .. } - if reference.home.package == Some(primitives::BASE) - && reference.home.name == "Proof" && reference.name == "returns") => - { - Some((*computation, *condition)) - } - _ => None, + let (computation, condition, expect) = match proof.obligation { + ProofObligation::Execution { body, expect } => (body, None, expect), + ProofObligation::Returns { + computation, + postcondition, + } => (computation, Some(postcondition), nash_ast::Expect::Pass), }; - if partial.is_some() && proof.expect != nash_ast::Expect::Pass { - return Err(Error::PartialExpectation); - } - let body = engine.expr(partial.map_or(proof.body, |(value, _)| value), &ctx)?; + let body = engine.expr(computation, &ctx)?; let mut postcondition = None; let mut checker = None; - if let Some((_, condition)) = partial { + if let Some(condition) = condition { let result = match body.ty { Ty::Const(ConstTy::Int) => ReturnDomain::Int, Ty::Const(ConstTy::Bool) => ReturnDomain::Bool, @@ -225,7 +208,7 @@ pub fn compile_proofs_matching<'a>( programs.push(ProofProgram { module: module.name.to_owned(), name: proof.name.value.to_owned(), - expect: proof.expect, + expect, domains, flat: flat::encode(compiled.program).map_err(|e| Error::Encoding(e.to_string()))?, postcondition, diff --git a/crates/nash-constrain/src/error.rs b/crates/nash-constrain/src/error.rs index d760da93..65447462 100644 --- a/crates/nash-constrain/src/error.rs +++ b/crates/nash-constrain/src/error.rs @@ -194,6 +194,9 @@ pub enum Context<'a> { Destructure, TestBody, TestGenerator, + ProofBody, + ProofDomain, + ProofPostcondition, } #[derive(Clone, Copy, Debug)] diff --git a/crates/nash-driver/src/compile.rs b/crates/nash-driver/src/compile.rs index f7c10bc4..62940040 100644 --- a/crates/nash-driver/src/compile.rs +++ b/crates/nash-driver/src/compile.rs @@ -615,9 +615,15 @@ fn scan_module( module .tests .into_iter() - .chain(module.proofs) .filter(|_| include_tests) - .flat_map(|tests| tests.imports.iter()), + .flat_map(|tests| tests.imports.iter()) + .chain( + module + .proofs + .into_iter() + .filter(|_| include_tests) + .flat_map(|proofs| proofs.imports.iter()), + ), ) { if let Some(uri) = resolve_import(import.import.value, current, known_modules) && !imports.contains(&uri) diff --git a/crates/nash-driver/tests/proofs.rs b/crates/nash-driver/tests/proofs.rs index 0f2936f6..aa92f41f 100644 --- a/crates/nash-driver/tests/proofs.rs +++ b/crates/nash-driver/tests/proofs.rs @@ -455,3 +455,54 @@ async fn vesting_authorization_proofs_compile_and_export() { let files = nash_proof::export(&programs, &output, 1000, 1500, 30).unwrap(); assert_eq!(files.len(), 2); } + +#[tokio::test] +async fn proof_aliases_and_independent_name_scopes_survive_canonicalization() { + let programs = compile( + r#"module Main exposing (..) +tests + test "shared" = do + assert True +proof + import Proof as Universal exposing (returns) + prop "shared" = let value via Universal.int in do + returns value (\result -> result == value) +"#, + ) + .await + .unwrap(); + assert_eq!(programs.len(), 1); + assert_eq!(programs[0].domains, vec![nash_proof::Domain::Int]); + assert!(programs[0].postcondition.is_some()); +} + +#[tokio::test] +async fn proof_specific_errors_are_reported_before_codegen() { + for (declarations, expected) in [ + ( + "test \"duplicate\" = do\n assert True\n test \"duplicate\" = do\n assert True", + "duplicate_proof", + ), + ( + "prop \"not a domain\" = let value via Proof.returns in do\n assert True", + "invalid_domain", + ), + ( + "prop \"wrong condition\" = let value via Proof.int in do\n Proof.returns value (\\result -> result + 1)", + "postcondition", + ), + ( + "prop \"invalid existential modifier\" fail once = let value via Proof.int in do\n Proof.returns value (\\_ -> True)", + "invalid_expectation", + ), + ( + "test \"non-unit execution\" = do\n Proof.bool", + "proof body", + ), + ] { + let source = + format!("module Main exposing (..)\nproof\n import Proof\n {declarations}\n"); + let error = compile(&source).await.unwrap_err(); + assert!(error.contains(expected), "expected {expected}: {error}"); + } +} diff --git a/crates/nash-fmt/src/declarations.rs b/crates/nash-fmt/src/declarations.rs index bd90bc24..71b6c929 100644 --- a/crates/nash-fmt/src/declarations.rs +++ b/crates/nash-fmt/src/declarations.rs @@ -5,6 +5,21 @@ use crate::{ use nash_region::Position; use nash_source::*; +type SpecificationBinders<'a> = Vec<( + &'a nash_region::Located>, + &'a nash_region::Located>, +)>; + +/// A private layout view shares formatting without changing the source AST types. +struct SpecificationItem<'a> { + region: nash_region::Region, + name: &'a nash_region::Located<&'a str>, + expect: Expect, + budget: Option, + binders: Option>, + body: &'a Block<'a>, +} + impl Printer<'_> { pub fn exposing(&mut self, exposing: &Exposing<'_>, broken: bool) -> Doc { let doc = match exposing { @@ -189,60 +204,117 @@ impl Printer<'_> { cat([Doc::Hard, methods]).nest(), ]) } - fn tests(&mut self, tests: &Tests<'_>, proof: bool) -> Doc { + fn tests<'a>(&mut self, tests: &Tests<'a>) -> Doc { + let items = tests.tests.iter().map(|located| { + let test = &located.value; + let (binders, body) = match &test.body { + TestBody::Unit(body) => (None, *body), + TestBody::Prop { binders, body } => ( + Some( + binders + .iter() + .map(|b| (b.value.pattern, b.value.generator)) + .collect(), + ), + *body, + ), + }; + SpecificationItem { + region: located.region, + name: test.name, + expect: test.expect, + budget: test.budget, + binders, + body, + } + }); + self.specification("tests", tests.imports, items) + } + + fn proofs<'a>(&mut self, proofs: &Proofs<'a>) -> Doc { + let items = proofs.proofs.iter().map(|located| { + let proof = &located.value; + let (binders, body) = match &proof.body { + ProofBody::Unit(body) => (None, *body), + ProofBody::Prop { binders, body } => ( + Some( + binders + .iter() + .map(|b| (b.value.pattern, b.value.domain)) + .collect(), + ), + *body, + ), + }; + SpecificationItem { + region: located.region, + name: proof.name, + expect: proof.expect, + budget: None, + binders, + body, + } + }); + self.specification("proof", proofs.imports, items) + } + + fn specification<'a>( + &mut self, + keyword: &str, + imports: &[&Import<'a>], + items: impl Iterator>, + ) -> Doc { let mut docs = Vec::new(); - for import in tests.imports { + for import in imports { docs.push(self.import(import)); } - for located in tests.tests { - let before = self.before(located.region.start); - let test = &located.value; - let kind = if matches!(test.body, TestBody::Prop { .. }) { + for item in items { + let before = self.before(item.region.start); + let kind = if item.binders.is_some() { "prop" } else { "test" }; - let expectation = match test.expect { + let expectation = match item.expect { Expect::Pass => "", Expect::Fail => " fail", Expect::FailOnce => " fail once", }; - let budget = match test.budget { + let budget = match item.budget { None => String::new(), Some(Budget::Cpu(n)) => format!(" within (cpu {n})"), Some(Budget::Mem(n)) => format!(" within (mem {n})"), Some(Budget::Both { cpu, mem }) => format!(" within (cpu {cpu}, mem {mem})"), }; - let body = match &test.body { - TestBody::Unit(b) => self.block(b.stmts, b.last), - TestBody::Prop { binders, body } => { - let binders = join( - binders.iter().map(|b| { - let pattern = self.pattern(b.value.pattern, 0); - let generator = self.expr(b.value.generator, 0); - cat([pattern, text(" via "), generator]) - }), - Doc::Hard, - ); - cat([ - text("let"), - cat([Doc::Hard, binders]).nest(), - Doc::Hard, - text("in"), - cat([Doc::Hard, self.block(body.stmts, body.last)]).nest(), - ]) - } + let body = if let Some(binders) = item.binders { + let binders = join( + binders.into_iter().map(|(pattern, value)| { + let pattern = self.pattern(pattern, 0); + let value = self.expr(value, 0); + cat([pattern, text(" via "), value]) + }), + Doc::Hard, + ); + cat([ + text("let"), + cat([Doc::Hard, binders]).nest(), + Doc::Hard, + text("in"), + cat([Doc::Hard, self.block(item.body.stmts, item.body.last)]).nest(), + ]) + } else { + self.block(item.body.stmts, item.body.last) }; docs.push(cat([ before, text(format!("{kind} ")), - self.literal(test.name.region), + self.literal(item.name.region), text(format!("{expectation}{budget} =")), cat([Doc::Hard, body]).nest(), ])); } cat([ - text(if proof { "proof" } else { "tests" }), + text(keyword), cat([Doc::Hard, join(docs, cat([Doc::Hard, Doc::Hard]))]).nest(), ]) } @@ -353,10 +425,10 @@ impl Printer<'_> { chunks.push(cat([before, doc])); } if let Some(tests) = module.tests { - chunks.push(self.tests(tests, false)); + chunks.push(self.tests(tests)); } if let Some(proofs) = module.proofs { - chunks.push(self.tests(proofs, true)); + chunks.push(self.proofs(proofs)); } let trailing = self.before(Position::new(usize::MAX, usize::MAX)); if chunks.is_empty() { diff --git a/crates/nash-fmt/src/tests.rs b/crates/nash-fmt/src/tests.rs index b6beb001..a913112e 100644 --- a/crates/nash-fmt/src/tests.rs +++ b/crates/nash-fmt/src/tests.rs @@ -518,3 +518,15 @@ fn multiline_exposing() { "# ); } + +#[test] +fn proof_blocks_preserve_semantics_and_private_imports() { + for source in [ + "module Main exposing (..)\nproof\n import Proof as P exposing (returns)\n prop \"p\" = let x via P.int in do\n returns x (\\result -> result == x)\ntests\n test \"t\" within (cpu 100) = do\n assert True\n", + "module Main exposing (..)\ntests\n test \"t\" = do\n assert True\nproof\n import Proof\n test \"closed\" fail = do\n assert False\n prop \"p\" = let x via Proof.int in do\n assert (x == x)\n", + ] { + let formatted = super::format(source).unwrap(); + assert_eq!(formatted, super::format(&formatted).unwrap()); + assert_eq!(semantic::tree(source), semantic::tree(&formatted)); + } +} diff --git a/crates/nash-fmt/src/tests/semantic.rs b/crates/nash-fmt/src/tests/semantic.rs index 4459e167..1a5cc429 100644 --- a/crates/nash-fmt/src/tests/semantic.rs +++ b/crates/nash-fmt/src/tests/semantic.rs @@ -121,6 +121,10 @@ structure!(Impl { attributes }); structure!(Tests { imports, tests }); +structure!(Proofs { imports, proofs }); +structure!(Proof { name, expect, body }); +variants!(ProofBody{Unit(block),Prop{binders,body}}); +structure!(ProofBinder { pattern, domain }); structure!(Test { name, expect, diff --git a/crates/nash-nitpick/src/check.rs b/crates/nash-nitpick/src/check.rs index 703b94e6..f8b4f8dc 100644 --- a/crates/nash-nitpick/src/check.rs +++ b/crates/nash-nitpick/src/check.rs @@ -29,7 +29,7 @@ pub fn check<'a>(bump: &'a Bump, module: &Module<'a>) -> Result<(), Vec(bump: &'a Bump, module: &Module<'a>) -> Result<(), Vec { Name(StringError, Row, Col), NameStart(Row, Col), OnceOnUnitTest(Row, Col), + ProofBudget(Row, Col), WithinOpen(Row, Col), WithinKind(Row, Col), WithinNumber(Number, Row, Col), diff --git a/crates/nash-parse/src/tests_block.rs b/crates/nash-parse/src/tests_block.rs index d330ae9f..9758a6a4 100644 --- a/crates/nash-parse/src/tests_block.rs +++ b/crates/nash-parse/src/tests_block.rs @@ -1,20 +1,28 @@ use bumpalo::collections::Vec as BumpVec; use nash_region::{Located, Position, Region}; -use nash_source::{Block, Budget, Expect, Test, TestBody, Tests, ViaBinder}; +use nash_source::{ + Block, Budget, Expect, Proof, ProofBinder, ProofBody, Proofs, Test, TestBody, Tests, ViaBinder, +}; use crate::Parser; use crate::error::{self, Test as TestErr, Tests as TestsErr}; impl<'a> Parser<'a> { pub(crate) fn tests_block(&mut self) -> Result<&'a Tests<'a>, error::Module<'a>> { - self.specification_block(false) + let (imports, tests) = self.specification_block(false, Self::test_item)?; + Ok(self.alloc(Tests { imports, tests })) } - pub(crate) fn proofs_block(&mut self) -> Result<&'a Tests<'a>, error::Module<'a>> { - self.specification_block(true) + pub(crate) fn proofs_block(&mut self) -> Result<&'a Proofs<'a>, error::Module<'a>> { + let (imports, proofs) = self.specification_block(true, Self::proof_item)?; + Ok(self.alloc(Proofs { imports, proofs })) } - fn specification_block(&mut self, proof: bool) -> Result<&'a Tests<'a>, error::Module<'a>> { + fn specification_block( + &mut self, + proof: bool, + parse_item: fn(&mut Self) -> Result<&'a Located, TestErr<'a>>, + ) -> Result<(&'a [&'a nash_source::Import<'a>], &'a [&'a Located]), error::Module<'a>> { self.in_context( |bump, error, row, col| error::Module::Tests(bump.alloc(error), row, col), |parser| { @@ -25,53 +33,47 @@ impl<'a> Parser<'a> { } }, |parser| { - let tests_end = parser.get_position(); + let block_end = parser.get_position(); parser.chomp(TestsErr::Space)?; if parser.is_eof() || parser.col() == 1 { - return Ok(parser.alloc(Tests { - imports: &[], - tests: &[], - })); + return Ok((&[][..], &[][..])); } - parser.check_indent(tests_end.line, tests_end.column, TestsErr::IndentStart)?; + parser.check_indent(block_end.line, block_end.column, TestsErr::IndentStart)?; parser.with_indent(|parser| { let imports = parser.specialize( |bump, error, row, col| TestsErr::Import(bump.alloc(error), row, col), |parser| parser.imports(), )?; - let mut tests = BumpVec::new_in(parser.bump); - + let mut items = BumpVec::new_in(parser.bump); loop { if parser.is_eof() || parser.col() < parser.indent() { break; } parser.check_aligned(TestsErr::Alignment)?; - let test = parser.specialize( + let item = parser.specialize( |bump, error, row, col| TestsErr::Test(bump.alloc(error), row, col), - |parser| parser.test_item(), + parse_item, )?; - tests.push(test); + items.push(item); } - - Ok(parser.alloc(Tests { - imports, - tests: tests.into_bump_slice(), - })) + Ok((imports, items.into_bump_slice())) }) }, ) } - fn test_item(&mut self) -> Result<&'a Located>, TestErr<'a>> { + fn specification_header( + &mut self, + ) -> Result<(Position, bool, &'a Located<&'a str>, Expect), TestErr<'a>> { let start = self.get_position(); let is_prop = self.one_of( TestErr::NameStart, vec![ - Box::new(|parser: &mut Parser<'a>| { + Box::new(|parser: &mut Self| { parser.keyword_test(TestErr::NameStart)?; Ok(false) }), - Box::new(|parser: &mut Parser<'a>| { + Box::new(|parser: &mut Self| { parser.keyword_prop(TestErr::NameStart)?; Ok(true) }), @@ -83,11 +85,18 @@ impl<'a> Parser<'a> { let name = self.add_end(name_start, name_str); self.chomp_and_check_indent(TestErr::Space, TestErr::IndentEquals)?; let expect = self.test_expect(is_prop)?; + Ok((start, is_prop, name, expect)) + } + + fn test_item(&mut self) -> Result<&'a Located>, TestErr<'a>> { + let (start, is_prop, name, expect) = self.specification_header()?; let budget = self.test_budget()?; self.word1(b'=', TestErr::Equals)?; self.chomp_and_check_indent(TestErr::Space, TestErr::IndentBody)?; let (body, end) = if is_prop { - self.prop_body()? + let (binders, body, end) = + self.property_body(|pattern, generator| ViaBinder { pattern, generator })?; + (TestBody::Prop { binders, body }, end) } else { let (block, end) = self.test_block()?; (TestBody::Unit(block), end) @@ -104,6 +113,29 @@ impl<'a> Parser<'a> { ))) } + fn proof_item(&mut self) -> Result<&'a Located>, TestErr<'a>> { + let (start, is_prop, name, expect) = self.specification_header()?; + if self.next_is_keyword(b"within") { + let (row, col) = self.position(); + return Err(TestErr::ProofBudget(row, col)); + } + self.word1(b'=', TestErr::Equals)?; + self.chomp_and_check_indent(TestErr::Space, TestErr::IndentBody)?; + let (body, end) = if is_prop { + let (binders, body, end) = + self.property_body(|pattern, domain| ProofBinder { pattern, domain })?; + (ProofBody::Prop { binders, body }, end) + } else { + let (block, end) = self.test_block()?; + (ProofBody::Unit(block), end) + }; + self.chomp(TestErr::Space)?; + Ok(self.alloc(Located::at( + Region::new(start, end), + Proof { name, expect, body }, + ))) + } + fn test_block(&mut self) -> Result<(&'a Block<'a>, Position), TestErr<'a>> { self.keyword_do(TestErr::Do)?; self.chomp_and_check_indent(TestErr::Space, TestErr::IndentBody)?; @@ -201,13 +233,16 @@ impl<'a> Parser<'a> { Ok(constructor(number)) } - fn prop_body(&mut self) -> Result<(TestBody<'a>, Position), TestErr<'a>> { + fn property_body( + &mut self, + make: fn(&'a Located>, &'a Located>) -> T, + ) -> Result<(&'a [&'a Located], &'a Block<'a>, Position), TestErr<'a>> { self.keyword_let(TestErr::Let)?; let (binders, binders_end) = self.with_backset_indent(3, |parser| { parser.chomp_and_check_indent(TestErr::Space, TestErr::IndentBinder)?; parser.with_indent(|parser| { let mut binders = BumpVec::new_in(parser.bump); - let (first, mut end) = parser.via_binder()?; + let (first, mut end) = parser.via_binder(make)?; binders.push(first); loop { @@ -215,7 +250,7 @@ impl<'a> Parser<'a> { break; } parser.check_aligned(TestErr::BinderAlignment)?; - let (binder, binder_end) = parser.via_binder()?; + let (binder, binder_end) = parser.via_binder(make)?; binders.push(binder); end = binder_end; } @@ -226,10 +261,13 @@ impl<'a> Parser<'a> { self.keyword_in(TestErr::In)?; self.chomp_and_check_indent(TestErr::Space, TestErr::IndentBody)?; let (body, end) = self.test_block()?; - Ok((TestBody::Prop { binders, body }, end)) + Ok((binders, body, end)) } - fn via_binder(&mut self) -> Result<(&'a Located>, Position), TestErr<'a>> { + fn via_binder( + &mut self, + make: fn(&'a Located>, &'a Located>) -> T, + ) -> Result<(&'a Located, Position), TestErr<'a>> { let start = self.get_position(); let pattern = self.specialize( |bump, error, row, col| TestErr::Pattern(bump.alloc(error), row, col), @@ -245,7 +283,7 @@ impl<'a> Parser<'a> { Ok(( self.alloc(Located::at( Region::new(start, end), - ViaBinder { pattern, generator }, + make(pattern, generator), )), end, )) @@ -590,7 +628,7 @@ mod proof_tests { let bump = bumpalo::Bump::new(); let module = crate::Parser::new(&bump, source).module().unwrap(); assert_eq!(module.tests.unwrap().tests.len(), 1); - assert_eq!(module.proofs.unwrap().tests.len(), 1); + assert_eq!(module.proofs.unwrap().proofs.len(), 1); } } #[test] @@ -602,4 +640,16 @@ mod proof_tests { .is_err() ); } + #[test] + fn proof_budget_is_rejected_during_parsing() { + let bump = bumpalo::Bump::new(); + let error = crate::Parser::new(&bump, "module Main exposing (..)\nproof\n test \"p\" within (cpu 100) = do\n assert True\n").module().unwrap_err(); + let crate::error::Module::Tests(error, ..) = error else { + panic!("{error:?}"); + }; + let crate::error::Tests::Test(error, ..) = error else { + panic!("{error:?}"); + }; + assert!(matches!(error, crate::error::Test::ProofBudget(..))); + } } diff --git a/crates/nash-report/src/canonicalize.rs b/crates/nash-report/src/canonicalize.rs index b1e43900..4395e4e2 100644 --- a/crates/nash-report/src/canonicalize.rs +++ b/crates/nash-report/src/canonicalize.rs @@ -295,14 +295,25 @@ pub fn to_report_with_name(source: &Source<'_>, error: &Error<'_>, expected_name "Use `x via Proof.int` or a ledger domain such as `ctx via Proof.spendingV3`. Random generators are only available in tests.", ), ), - Error::ProofBudget { region } => Report::snippet( - "UNSUPPORTED PROOF BUDGET", + Error::ProofPartialExpectation { region } => Report::snippet( + "INVALID PARTIAL CORRECTNESS EXPECTATION", *region, None, - Doc::text("Execution-budget constraints are not supported in proof blocks."), Doc::text( - "Use tests to measure execution budgets. Proofs use a separately checked CEK step bound.", + "Proof.returns is a partial-correctness claim and cannot use fail or fail once.", ), + Doc::text( + "Remove the failure modifier. The postcondition must hold on every successful return.", + ), + ), + Error::DuplicateProof { + name, + first, + second, + } => name_clash( + *first, + *second, + &format!("This file has multiple proofs named `{name}`."), ), Error::DuplicateTest { name, @@ -998,7 +1009,8 @@ pub fn to_report_with_name(source: &Source<'_>, error: &Error<'_>, expected_name Error::ExportNotFound { .. } => "nash::names::export_not_found", Error::ExportOpenAlias { .. } => "nash::names::export_open_alias", Error::ProofDomain { .. } => "nash::proof::invalid_domain", - Error::ProofBudget { .. } => "nash::proof::unsupported_budget", + Error::ProofPartialExpectation { .. } => "nash::proof::invalid_expectation", + Error::DuplicateProof { .. } => "nash::names::duplicate_proof", Error::DuplicateTest { .. } => "nash::names::duplicate_test", Error::DuplicateDecl { .. } => "nash::names::duplicate_decl", Error::DuplicateType { .. } => "nash::names::duplicate_type", diff --git a/crates/nash-report/src/syntax/module.rs b/crates/nash-report/src/syntax/module.rs index d8a42a64..84574abe 100644 --- a/crates/nash-report/src/syntax/module.rs +++ b/crates/nash-report/src/syntax/module.rs @@ -273,6 +273,13 @@ pub(super) fn to_test_report(source: &Source<'_>, error: &Test<'_>, sr: Row, sc: "Expected a budget kind.", "Use `cpu` or `mem`, followed by an integer limit.", ), + Test::ProofBudget(r, c) => problem( + "UNSUPPORTED PROOF BUDGET", + r, + c, + "Execution-budget constraints are not supported in proof blocks.", + "Use tests to measure cpu/mem budgets; proofs use the configured CEK fuel limit.", + ), Test::WithinDuplicate(r, c) => problem( "DUPLICATE TEST BUDGET", r, diff --git a/crates/nash-report/src/type_.rs b/crates/nash-report/src/type_.rs index 859ac8f4..8fac5051 100644 --- a/crates/nash-report/src/type_.rs +++ b/crates/nash-report/src/type_.rs @@ -391,6 +391,11 @@ fn to_expr_report( Context::Destructure => "destructuring pattern".into(), Context::TestBody => "test body (which must return unit)".into(), Context::TestGenerator => "property generator (which must be a generator)".into(), + Context::ProofBody => "proof body (which must return unit)".into(), + Context::ProofDomain => "symbolic proof domain".into(), + Context::ProofPostcondition => { + "proof postcondition (which must map the returned value to bool)".into() + } }; (*surroundings, label, *expected, hint) } diff --git a/crates/nash-solve/src/solve/infer.rs b/crates/nash-solve/src/solve/infer.rs index 83506be9..c348b3ac 100644 --- a/crates/nash-solve/src/solve/infer.rs +++ b/crates/nash-solve/src/solve/infer.rs @@ -30,6 +30,34 @@ struct PreparedDefinition<'a> { rigids: &'a [Variable], } +/// A shared inference path accepts distinct declaration types, rather than +/// manufacturing tests from proofs. Only test binders receive the PRNG type. +enum Specification<'s, 'a> { + Test(&'s nash_ast::Test<'a>), + Proof(&'s nash_ast::Proof<'a>), +} + +impl<'a> Specification<'_, 'a> { + fn name(&self) -> &'a Located<&'a str> { + match self { + Self::Test(t) => t.name, + Self::Proof(p) => p.name, + } + } + fn region(&self) -> Region { + match self { + Self::Test(t) => t.region, + Self::Proof(p) => p.region, + } + } + fn binders(&self) -> Vec<(&'a Located>, &'a Located>)> { + match self { + Self::Test(t) => t.binders.iter().map(|b| (b.pattern, b.generator)).collect(), + Self::Proof(p) => p.binders.iter().map(|b| (b.pattern, b.domain)).collect(), + } + } +} + impl<'a> Solver<'a, '_> { pub(super) fn fresh(&mut self, uf: &mut UnionFind<'a>, rank: usize) -> Variable { self.register(uf, rank, Content::FlexVar(None)) @@ -688,12 +716,13 @@ impl<'a> Solver<'a, '_> { self.infer_definition(uf, env, rank, state, &Rtv::new(), definition, false); state = self.close_locals(uf, scope.state, scope.locals); } - for (test, proof) in module + for specification in module .tests .iter() - .map(|t| (t, false)) - .chain(module.proofs.iter().map(|t| (t, true))) + .map(Specification::Test) + .chain(module.proofs.iter().map(Specification::Proof)) { + let proof = matches!(specification, Specification::Proof(_)); self.test_scope = if proof { SpecificationScope::Proof } else { @@ -702,7 +731,7 @@ impl<'a> Solver<'a, '_> { let young = self.young_pool(rank); let start = self.wanted.len(); let errors_before = state.errors.len(); - let binder = Binder::Named(test.name); + let binder = Binder::Named(specification.name()); let unit = self.structure( uf, young, @@ -716,70 +745,127 @@ impl<'a> Solver<'a, '_> { let owners = self.owners.len(); self.owners.push(binder.node()); let mut patterns = Vec::new(); - for binder in test.binders { + for (pattern, value) in specification.binders() { let element = self.fresh(uf, young); - let base = Some(nash_ast::primitives::BASE); - let prng = self.structure( - uf, - young, - FlatType::App1( - nash_ast::ModuleName { - package: base, - name: "Prop", - }, - "prng", - vec![], - ), - ); - let pair = - self.structure(uf, young, FlatType::Tuple1(element, prng, vec![])); - let result = self.structure( - uf, - young, - FlatType::App1( - nash_ast::ModuleName { - package: base, - name: "Option", - }, - "option", - vec![pair], - ), - ); - let generator = if proof { + let expected = if proof { element } else { + let base = Some(nash_ast::primitives::BASE); + let prng = self.structure( + uf, + young, + FlatType::App1( + nash_ast::ModuleName { + package: base, + name: "Prop", + }, + "prng", + vec![], + ), + ); + let pair = + self.structure(uf, young, FlatType::Tuple1(element, prng, vec![])); + let result = self.structure( + uf, + young, + FlatType::App1( + nash_ast::ModuleName { + package: base, + name: "Option", + }, + "option", + vec![pair], + ), + ); self.structure(uf, young, FlatType::Fun1(prng, result)) }; + let context = if proof { + Context::ProofDomain + } else { + Context::TestGenerator + }; state = self.infer_expr( uf, env, young, state, &Rtv::new(), - binder.generator, - Expected::FromContext( - binder.generator.region, - Context::TestGenerator, - generator, - ), + value, + Expected::FromContext(value.region, context, expected), ); - patterns.push((binder.pattern, PExpected::NoExpectation(element))); + patterns.push((pattern, PExpected::NoExpectation(element))); } let scope = self.infer_patterns(uf, env, young, state, &patterns); - state = self.infer_expr( - uf, - &scope.env, - young, - scope.state, - &Rtv::new(), - test.body, - Expected::FromContext(test.region, Context::TestBody, unit), - ); + state = match &specification { + Specification::Test(test) => self.infer_expr( + uf, + &scope.env, + young, + scope.state, + &Rtv::new(), + test.body, + Expected::FromContext(test.region, Context::TestBody, unit), + ), + Specification::Proof(proof) => match proof.obligation { + nash_ast::ProofObligation::Execution { body, .. } => self.infer_expr( + uf, + &scope.env, + young, + scope.state, + &Rtv::new(), + body, + Expected::FromContext( + specification.region(), + Context::ProofBody, + unit, + ), + ), + nash_ast::ProofObligation::Returns { + computation, + postcondition, + } => { + let returned = self.fresh(uf, young); + let state = self.infer_expr( + uf, + &scope.env, + young, + scope.state, + &Rtv::new(), + computation, + Expected::NoExpectation(returned), + ); + let boolean = self.structure( + uf, + young, + FlatType::App1( + nash_ast::primitives::primitive_home(), + "bool", + vec![], + ), + ); + let predicate = + self.structure(uf, young, FlatType::Fun1(returned, boolean)); + self.infer_expr( + uf, + &scope.env, + young, + state, + &Rtv::new(), + postcondition, + Expected::FromContext( + postcondition.region, + Context::ProofPostcondition, + predicate, + ), + ) + } + }, + }; self.retry_fields(uf, young, &mut state.errors); state = self.resolve_wanted(uf, young, state, start, Some(binder), false); state = self.close_locals(uf, state, scope.locals); self.owners.truncate(owners); - // Tests own solved metadata and evidence without entering the value environment. + // Specifications own solved metadata and evidence without entering the value environment. state = self .finish_bindings( uf, diff --git a/crates/nash-source/src/lib.rs b/crates/nash-source/src/lib.rs index 3ba7d624..5b5abf03 100644 --- a/crates/nash-source/src/lib.rs +++ b/crates/nash-source/src/lib.rs @@ -14,7 +14,7 @@ pub struct Module<'a> { pub traits: &'a [&'a Located>], pub impls: &'a [&'a Located>], pub tests: Option<&'a Tests<'a>>, - pub proofs: Option<&'a Tests<'a>>, + pub proofs: Option<&'a Proofs<'a>>, pub binops: &'a [&'a Located>], } @@ -87,6 +87,35 @@ pub struct Test<'a> { pub body: TestBody<'a>, } +/// A proof block has its own declarations and private imports. +#[derive(Debug)] +pub struct Proofs<'a> { + pub imports: &'a [&'a Import<'a>], + pub proofs: &'a [&'a Located>], +} + +#[derive(Debug)] +pub struct Proof<'a> { + pub name: &'a Located<&'a str>, + pub expect: Expect, + pub body: ProofBody<'a>, +} + +#[derive(Debug)] +pub enum ProofBody<'a> { + Unit(&'a Block<'a>), + Prop { + binders: &'a [&'a Located>], + body: &'a Block<'a>, + }, +} + +#[derive(Debug)] +pub struct ProofBinder<'a> { + pub pattern: &'a Located>, + pub domain: &'a Located>, +} + #[derive(Debug)] pub struct Block<'a> { pub stmts: &'a [&'a Located>], diff --git a/docs/proofs.md b/docs/proofs.md index 522b41f3..4d7d6308 100644 --- a/docs/proofs.md +++ b/docs/proofs.md @@ -9,6 +9,13 @@ unfinished states at zero fuel, rather than using the upstream convention that turns fuel exhaustion into an error. Lean-blaster checks the resulting obligation. No independently translated Nash semantics are introduced. +The source and canonical ASTs keep proofs separate from tests: `ProofBinder` +names a symbolic `domain`, and `ProofObligation` distinguishes execution claims +from successful-return postconditions. Partial-correctness obligations cannot +carry failure modifiers. Shared parser and formatter helpers preserve the common +syntax; proof budgets are rejected during parsing and invalid domains or partial +failure modifiers during canonicalization. + ```elm module Identity exposing (identity) From 0f6cccbd2bea55bc0d36a9d07e2be9948a187b1d Mon Sep 17 00:00:00 2001 From: RSoulatIOHK Date: Thu, 1 Oct 2026 18:36:30 +0200 Subject: [PATCH 5/7] test: certify compilation of all builtin references and application prefixes --- .sampo/changesets/lean-blaster-proofs.md | 5 + .../nash-driver/tests/builtin_compilation.rs | 352 ++++++++++++++++++ .../builtin-compilation/Certificate.lean | 70 ++++ .../fixtures/builtin-compilation/spec.tsv | 109 ++++++ docs/builtin-compilation.md | 95 +++++ docs/proofs.md | 8 + 6 files changed, 639 insertions(+) create mode 100644 crates/nash-driver/tests/builtin_compilation.rs create mode 100644 crates/nash-driver/tests/fixtures/builtin-compilation/Certificate.lean create mode 100644 crates/nash-driver/tests/fixtures/builtin-compilation/spec.tsv create mode 100644 docs/builtin-compilation.md diff --git a/.sampo/changesets/lean-blaster-proofs.md b/.sampo/changesets/lean-blaster-proofs.md index 08376a27..2f121fcc 100644 --- a/.sampo/changesets/lean-blaster-proofs.md +++ b/.sampo/changesets/lean-blaster-proofs.md @@ -28,3 +28,8 @@ Represent proofs with dedicated source and canonical AST types, symbolic-domain binders, and distinct execution and successful-return obligations. Reject proof budgets during parsing and report proof-specific domain, expectation and postcondition errors before code generation. + +Add independent compilation contracts for all 101 builtins and all application +prefixes. Export kernel-checked syntax certificates and universal CEK equality +corollaries for the 91 builtins supported by pinned PlutusCoreBlaster. Document +the ten missing array/value models and the translation-validation scope. diff --git a/crates/nash-driver/tests/builtin_compilation.rs b/crates/nash-driver/tests/builtin_compilation.rs new file mode 100644 index 00000000..223f5b7d --- /dev/null +++ b/crates/nash-driver/tests/builtin_compilation.rs @@ -0,0 +1,352 @@ +//! Translation validation against an independent UPLC calling convention. +//! No expected term is compiled through Nash or read from runtime metadata. +use nash_ast::{QualifiedName, Type, primitives}; +use nash_codegen::build::{Build, Input, TraceConfig}; +use nash_driver::{Database, InMemorySource, build_graph, build_with, bundled_base}; +use nash_plutus::{arena::Arena, binder::Eval, flat, term::Term}; +use std::{collections::BTreeSet, fmt::Write, sync::Arc}; +use tokio::sync::Mutex; +use url::Url; + +#[derive(Clone, Debug)] +struct Contract { + name: &'static str, + tag: usize, + forces: usize, + arity: usize, + bind_result: bool, + lean: Option<&'static str>, +} + +fn contracts() -> Vec { + include_str!("fixtures/builtin-compilation/spec.tsv") + .lines() + .filter(|line| !line.starts_with('#') && !line.is_empty()) + .map(|line| { + let fields = line.split('\t').collect::>(); + assert_eq!(fields.len(), 6, "{line}"); + Contract { + name: fields[0], + tag: fields[1].parse().unwrap(), + forces: fields[2].parse().unwrap(), + arity: fields[3].parse().unwrap(), + bind_result: fields[5] == "1", + lean: (fields[4] != "-").then_some(fields[4]), + } + }) + .collect() +} + +#[derive(Clone, Debug, PartialEq, Eq)] +enum Syntax { + Var(usize), + Lam(Box), + Apply(Box, Box), + Force(Box), + Builtin(usize), +} + +impl Syntax { + fn lean(&self) -> String { + match self { + Self::Var(index) => format!("(.var {index})"), + Self::Lam(body) => format!("(.lam {})", body.lean()), + Self::Apply(function, argument) => { + format!("(.apply {} {})", function.lean(), argument.lean()) + } + Self::Force(body) => format!("(.force {})", body.lean()), + Self::Builtin(tag) => format!("(.builtin {tag})"), + } + } +} + +fn captured(term: &Term<'_, nash_plutus::binder::DeBruijn>) -> Syntax { + match term { + // Flat and Nash use one-based indices; the certificate uses zero-based. + Term::Var(index) => Syntax::Var(index.index().checked_sub(1).unwrap()), + Term::Lambda { body, .. } => Syntax::Lam(Box::new(captured(body))), + Term::Apply { function, argument } => { + Syntax::Apply(Box::new(captured(function)), Box::new(captured(argument))) + } + Term::Force(body) => Syntax::Force(Box::new(captured(body))), + Term::Builtin(builtin) => Syntax::Builtin(**builtin as usize), + other => panic!("unexpected builtin wrapper: {other:?}"), + } +} + +fn reference(contract: &Contract, supplied: usize) -> Syntax { + let mut body = Syntax::Builtin(contract.tag); + for _ in 0..contract.forces { + body = Syntax::Force(Box::new(body)); + } + for position in 0..supplied { + body = Syntax::Apply( + Box::new(body), + Box::new(Syntax::Var(supplied - position - 1)), + ); + } + if contract.bind_result && supplied == contract.arity { + body = Syntax::Apply( + Box::new(Syntax::Lam(Box::new(Syntax::Var(0)))), + Box::new(body), + ); + } + for _ in 0..supplied { + body = Syntax::Lam(Box::new(body)); + } + // Ordinary O0 assembly retains the root declaration's administrative let. + Syntax::Apply( + Box::new(Syntax::Lam(Box::new(Syntax::Var(0)))), + Box::new(body), + ) +} + +struct Case { + contract: Contract, + supplied: usize, + flat: Vec, + syntax: Syntax, +} + +async fn compile_cases() -> Vec { + let contracts = contracts(); + assert_eq!(contracts.len(), 101); + assert_eq!(contracts.iter().filter(|c| c.lean.is_some()).count(), 91); + let names = contracts.iter().map(|c| c.name).collect::>(); + assert_eq!(names.len(), contracts.len(), "duplicate specification name"); + assert_eq!( + names, + primitives::BUILTINS.iter().map(|b| b.name).collect(), + "new, removed or renamed builtins require an independent contract" + ); + assert_eq!( + contracts.iter().map(|c| c.tag).collect::>(), + nash_plutus::builtin::DefaultFunction::ALL + .iter() + .map(|f| *f as usize) + .collect(), + "the calling-convention table must cover every runtime tag" + ); + let mut source = String::from("module BuiltinCompilation exposing (..)\nimport Builtin\n"); + for contract in &contracts { + for supplied in 0..=contract.arity { + let parameters = (0..supplied).map(|i| format!(" p{i}")).collect::(); + writeln!( + source, + "builtin{}x{supplied}{parameters} = Builtin.{}{parameters}", + contract.tag, contract.name + ) + .unwrap(); + } + } + let memory = InMemorySource::new(); + let main = Url::parse("file:///builtin-certificates/BuiltinCompilation.nash").unwrap(); + memory.insert(main.clone(), source); + let mut modules = bundled_base::modules(); + modules.insert(main.clone(), None); + let db = Arc::new(Mutex::new(Database::new(memory))); + let graph = build_graph(db.clone(), &modules.keys().cloned().collect::>()) + .await + .unwrap(); + let (report, cases) = build_with(db, &graph, &modules, move |solved| { + let arena = Arena::new(); + let build = Build::new(solved.modules.iter().map(|m| Input { + module: m.module, + types: &m.types, + tables: &m.tables, + })); + let module = solved.modules.iter().find(|m| m.uri == main).unwrap(); + let data = arena.alloc(nash_region::Located::at_zero(Type::Named { + reference: QualifiedName { + home: primitives::primitive_home(), + name: "Data", + }, + args: &[], + })); + let mut cases = Vec::new(); + for contract in contracts { + let function = nash_codegen::builtins::by_name(contract.name).unwrap(); + assert_eq!(function as usize, contract.tag, "{} opcode", contract.name); + assert_eq!( + function.force_count(), + contract.forces, + "{} forces", + contract.name + ); + assert_eq!(function.arity(), contract.arity, "{} arity", contract.name); + for supplied in 0..=contract.arity { + let name = format!("builtin{}x{supplied}", contract.tag); + let annotation = &module.annotations[name.as_str()]; + // Instantiate representation-polymorphic parameters as Data; + // their emitted UPLC calling convention is representation-free. + let args = vec![&*data; annotation.free_vars.len()]; + let core = build + .compile( + &arena, + QualifiedName { + home: module.module.name, + name: &name, + }, + Some(&args), + TraceConfig::default(), + ) + .unwrap(); + let compiled = nash_codegen::program::assemble_core(&arena, core.core).unwrap(); + let syntax = captured(compiled.program.term); + assert_eq!( + syntax, + reference(&contract, supplied), + "{} with {supplied} supplied arguments", + contract.name + ); + let flat = flat::encode(compiled.program).unwrap(); + let decoded = flat::decode::(&arena, &flat).unwrap(); + assert_eq!( + captured(decoded.term), + syntax, + "{} Flat roundtrip", + contract.name + ); + cases.push(Case { + contract: contract.clone(), + supplied, + flat, + syntax, + }); + } + } + cases + }) + .await; + assert!(report.is_success(), "{:#?}", report.ordered_reports()); + cases.unwrap() +} + +fn certificate(cases: &[Case]) -> String { + let mut text = include_str!("fixtures/builtin-compilation/Certificate.lean").to_owned(); + for case in cases { + let contract = &case.contract; + let label = format!("case_{}_{}", contract.tag, case.supplied); + let bind_result = contract.bind_result && case.supplied == contract.arity; + writeln!( + text, + "\n-- Builtin.{} with {} supplied arguments", + contract.name, case.supplied + ) + .unwrap(); + writeln!(text, "def {label} : Syntax := {}", case.syntax.lean()).unwrap(); + writeln!( + text, + "theorem {label}_syntax : {label} = reference {} {} {} {bind_result} := by rfl", + contract.tag, contract.forces, case.supplied + ) + .unwrap(); + writeln!(text, "theorem {label}_observation {{α : Sort u}} (observe : Syntax → α) (args : List Syntax) : observe (args.foldl Syntax.apply {label}) = observe (args.foldl Syntax.apply (reference {} {} {} {bind_result})) := by rw [{label}_syntax]", contract.tag, contract.forces, case.supplied).unwrap(); + if let Some(builtin) = contract.lean { + if case.supplied == 0 { + writeln!(text, "theorem convention_{} : callingConvention (expectedArgs .{builtin}) = ({}, {}) := by rfl", contract.tag, contract.forces, contract.arity).unwrap(); + } + writeln!( + text, + "def {label}_program : Program := flatEncodedScriptFromHexM \"{}\"", + hex::encode(&case.flat) + ) + .unwrap(); + writeln!(text, "theorem {label}_model : body {label}_program = referenceTerm .{builtin} {} {} {bind_result} := by rfl", contract.forces, case.supplied).unwrap(); + writeln!(text, "theorem {label}_cek (variant : PlutusCore.Default.BuiltinSemanticsVariant) (args : List Term) (fuel : Nat) : runKeepingFuel variant (initialState (applyParams (body {label}_program) args)) fuel = runKeepingFuel variant (initialState (applyParams (referenceTerm .{builtin} {} {} {bind_result}) args)) fuel := by rw [{label}_model]", contract.forces, case.supplied).unwrap(); + writeln!(text, "#print axioms {label}_cek").unwrap(); + } + writeln!(text, "#print axioms {label}_observation").unwrap(); + } + text.push_str("\nend NashBuiltinCompilation\n"); + text +} + +#[tokio::test] +async fn all_builtin_references_and_application_prefixes_match_the_contract() { + let cases = compile_cases().await; + assert_eq!(cases.len(), 283); + assert_eq!( + cases.iter().filter(|c| c.contract.lean.is_some()).count(), + 254 + ); + // The independent specification catches opcode, forcing, and argument-order errors. + let add = &cases + .iter() + .find(|c| c.contract.tag == 0 && c.supplied == 2) + .unwrap() + .contract; + let mut wrong_tag = add.clone(); + wrong_tag.tag = 1; + assert_ne!(reference(add, 2), reference(&wrong_tag, 2)); + let mut wrong_forces = add.clone(); + wrong_forces.forces = 1; + assert_ne!(reference(add, 2), reference(&wrong_forces, 2)); + let reversed = Syntax::Apply( + Box::new(Syntax::Apply( + Box::new(Syntax::Builtin(0)), + Box::new(Syntax::Var(0)), + )), + Box::new(Syntax::Var(1)), + ); + let wrapped_reversed = Syntax::Apply( + Box::new(Syntax::Lam(Box::new(Syntax::Var(0)))), + Box::new(Syntax::Lam(Box::new(Syntax::Lam(Box::new(reversed))))), + ); + assert_ne!(reference(add, 2), wrapped_reversed); + // Always render the complete proof export, even without the external toolchain. + let text = certificate(&cases); + assert_eq!(text.matches("_syntax :").count(), cases.len()); + assert!(!text.contains("sorry")); + assert!(!text.contains("native_decide")); +} + +#[tokio::test] +#[ignore = "requires NASH_PROOF_LEAN_PROJECT with pinned PlutusCoreBlaster and Lean 4.24"] +async fn kernel_checks_builtin_compilation_certificates() { + let cases = compile_cases().await; + let directory = + std::env::temp_dir().join(format!("nash-builtin-certificates-{}", std::process::id())); + std::fs::create_dir_all(&directory).unwrap(); + let path = directory.join("BuiltinCompilation.lean"); + std::fs::write(&path, certificate(&cases)).unwrap(); + let result = std::process::Command::new("lake") + .args(["env", "lean"]) + .arg(&path) + .current_dir(std::env::var_os("NASH_PROOF_LEAN_PROJECT").expect("Lean project")) + .output() + .unwrap(); + let diagnostics = format!( + "{}\n{}", + String::from_utf8_lossy(&result.stdout), + String::from_utf8_lossy(&result.stderr) + ); + std::fs::write(directory.join("diagnostics.txt"), &diagnostics).unwrap(); + assert!( + result.status.success(), + "{diagnostics}\nCertificate preserved at {}", + path.display() + ); + assert!(!diagnostics.contains("sorryAx"), "{diagnostics}"); + assert!(!diagnostics.contains("ofReduceBool"), "{diagnostics}"); + assert_eq!( + diagnostics.matches("does not depend on any axioms").count(), + 283, + "{diagnostics}" + ); + let cek_audits = diagnostics + .lines() + .filter(|line| line.contains("_cek' ")) + .collect::>(); + assert_eq!(cek_audits.len(), 254, "{diagnostics}"); + for audit in cek_audits { + assert!( + audit.ends_with("depends on axioms: [propext, Classical.choice, Quot.sound]"), + "unexpected model assumptions: {audit}" + ); + } + println!( + "101 builtins, 283 syntax certificates, 254 CEK certificates (91 modeled builtins); preserved at {}", + path.display() + ); +} diff --git a/crates/nash-driver/tests/fixtures/builtin-compilation/Certificate.lean b/crates/nash-driver/tests/fixtures/builtin-compilation/Certificate.lean new file mode 100644 index 00000000..d154bab0 --- /dev/null +++ b/crates/nash-driver/tests/fixtures/builtin-compilation/Certificate.lean @@ -0,0 +1,70 @@ +import PlutusCore.UPLC.ScriptEncoding +import PlutusCore.UPLC.Builtins +import PlutusCore.UPLC.CekMachine + +/- Kernel certificates for captured O0 output, not a formalization of the Rust compiler. + The reference is independent of Nash's builtin metadata. No SMT or admitted theorem + is used. The generic syntax includes tags missing from PlutusCoreBlaster's model. -/ +namespace NashBuiltinCompilation + +open PlutusCore.UPLC.Term +open PlutusCore.UPLC.ScriptEncoding +open PlutusCore.UPLC.Builtins +open PlutusCore.UPLC.CekMachine + +inductive Syntax where + | var : Nat → Syntax + | lam : Syntax → Syntax + | apply : Syntax → Syntax → Syntax + | force : Syntax → Syntax + | builtin : Nat → Syntax + +def forced (tag : Nat) : Nat → Syntax + | 0 => .builtin tag + | n + 1 => .force (forced tag n) + +def lambdas : Nat → Syntax → Syntax + | 0, term => term + | n + 1, term => .lam (lambdas n term) + +def reference (tag forces supplied : Nat) (bindResult : Bool) : Syntax := + let call := (List.range supplied).foldl + (fun term i => .apply term (.var (supplied - i - 1))) (forced tag forces) + let call := if bindResult then .apply (.lam (.var 0)) call else call + .apply (.lam (.var 0)) (lambdas supplied call) + +def callingConvention : ExpectedBuiltinArgs → Nat × Nat + | .One .ArgQ => (1, 0) + | .One .ArgV => (0, 1) + | .More arg rest => + let (forces, values) := callingConvention rest + match arg with + | .ArgQ => (forces + 1, values) + | .ArgV => (forces, values + 1) + +def forcedTerm (builtin : BuiltinFun) : Nat → Term + | 0 => .Builtin builtin + | n + 1 => .Force (forcedTerm builtin n) + +-- Match the Flat decoder's display-only binder names as well as its relative indices. +def lambdaTerms : Nat → Nat → Term → Term + | 0, _, term => term + | n + 1, depth, term => .Lam s!"dbi_{depth}" (lambdaTerms n (depth + 1) term) + +def referenceTerm (builtin : BuiltinFun) (forces supplied : Nat) (bindResult : Bool) : Term := + let call := (List.range supplied).foldl + (fun term i => .Apply term (.Var (supplied - i - 1))) (forcedTerm builtin forces) + let call := if bindResult then .Apply (.Lam s!"dbi_{supplied}" (.Var 0)) call else call + .Apply (.Lam "dbi_0" (.Var 0)) (lambdaTerms supplied 0 call) + +def body : Program → Term + | .Program _ term => term + +-- Preserve unfinished states at exhaustion, as Nash's proof backend does. +def runKeepingFuel (variant : PlutusCore.Default.BuiltinSemanticsVariant) + (state : State) (fuel : Nat) : State := + match fuel, state with + | _, .Halt _ => state + | _, .Error => state + | 0, _ => state + | n + 1, _ => runKeepingFuel variant (step variant state) n diff --git a/crates/nash-driver/tests/fixtures/builtin-compilation/spec.tsv b/crates/nash-driver/tests/fixtures/builtin-compilation/spec.tsv new file mode 100644 index 00000000..a8a05f9d --- /dev/null +++ b/crates/nash-driver/tests/fixtures/builtin-compilation/spec.tsv @@ -0,0 +1,109 @@ +# Frozen UPLC compilation contract: Nash name, Flat tag, forces, value arity, Lean constructor. +# Tags and modeled calling conventions: PlutusCoreBlaster 41fe7ead, +# PlutusCore/UPLC/FlatEncoding/Basic.lean and PlutusCore/UPLC/Builtins.lean. +# Array/value signatures and tags: Plutus Core specification, Tables 4.18, 4.20, C.8. +# https://plutus.cardano.intersectmbo.org/resources/plutus-core-spec.pdf +# Array/value entries are absent from the pinned Lean model. +# Final column: strict result binding for saturated decoder/projection calls in O0 assembly. +# Keep this independent of the compiler inventory and runtime metadata at test time. +addInteger 0 0 2 AddInteger 0 +subtractInteger 1 0 2 SubtractInteger 0 +multiplyInteger 2 0 2 MultiplyInteger 0 +divideInteger 3 0 2 DivideInteger 0 +quotientInteger 4 0 2 QuotientInteger 0 +remainderInteger 5 0 2 RemainderInteger 0 +modInteger 6 0 2 ModInteger 0 +equalsInteger 7 0 2 EqualsInteger 0 +lessThanInteger 8 0 2 LessThanInteger 0 +lessThanEqualsInteger 9 0 2 LessThanEqualsInteger 0 +appendByteString 10 0 2 AppendByteString 0 +consByteString 11 0 2 ConsByteString 0 +sliceByteString 12 0 3 SliceByteString 0 +lengthOfByteString 13 0 1 LengthOfByteString 0 +indexByteString 14 0 2 IndexByteString 0 +equalsByteString 15 0 2 EqualsByteString 0 +lessThanByteString 16 0 2 LessThanByteString 0 +lessThanEqualsByteString 17 0 2 LessThanEqualsByteString 0 +sha2_256 18 0 1 Sha2_256 0 +sha3_256 19 0 1 Sha3_256 0 +blake2b_256 20 0 1 Blake2b_256 0 +verifyEd25519Signature 21 0 3 VerifyEd25519Signature 0 +appendString 22 0 2 AppendString 0 +equalsString 23 0 2 EqualsString 0 +encodeUtf8 24 0 1 EncodeUtf8 0 +decodeUtf8 25 0 1 DecodeUtf8 0 +ifThenElse 26 1 3 IfThenElse 0 +chooseUnit 27 1 2 ChooseUnit 0 +trace 28 1 2 Trace 0 +fstPair 29 2 1 FstPair 1 +sndPair 30 2 1 SndPair 1 +chooseList 31 2 3 ChooseList 0 +mkCons 32 1 2 MkCons 0 +headList 33 1 1 HeadList 1 +tailList 34 1 1 TailList 1 +nullList 35 1 1 NullList 0 +chooseData 36 1 6 ChooseData 0 +constrData 37 0 2 ConstrData 0 +mapData 38 0 1 MapData 0 +listData 39 0 1 ListData 0 +iData 40 0 1 IData 0 +bData 41 0 1 BData 0 +unConstrData 42 0 1 UnConstrData 1 +unMapData 43 0 1 UnMapData 0 +unListData 44 0 1 UnListData 1 +unIData 45 0 1 UnIData 0 +unBData 46 0 1 UnBData 0 +equalsData 47 0 2 EqualsData 0 +mkPairData 48 0 2 MkPairData 0 +mkNilData 49 0 1 MkNilData 0 +mkNilPairData 50 0 1 MkNilPairData 0 +serialiseData 51 0 1 SerializeData 0 +verifyEcdsaSecp256k1Signature 52 0 3 VerifyEcdsaSecp256k1Signature 0 +verifySchnorrSecp256k1Signature 53 0 3 VerifySchnorrSecp256k1Signature 0 +bls12_381_g1_add 54 0 2 Bls12_381_G1_add 0 +bls12_381_g1_neg 55 0 1 Bls12_381_G1_neg 0 +bls12_381_g1_scalarMul 56 0 2 Bls12_381_G1_scalarMul 0 +bls12_381_g1_equal 57 0 2 Bls12_381_G1_equal 0 +bls12_381_g1_compress 58 0 1 Bls12_381_G1_compress 0 +bls12_381_g1_uncompress 59 0 1 Bls12_381_G1_uncompress 0 +bls12_381_g1_hashToGroup 60 0 2 Bls12_381_G1_hashToGroup 0 +bls12_381_g2_add 61 0 2 Bls12_381_G2_add 0 +bls12_381_g2_neg 62 0 1 Bls12_381_G2_neg 0 +bls12_381_g2_scalarMul 63 0 2 Bls12_381_G2_scalarMul 0 +bls12_381_g2_equal 64 0 2 Bls12_381_G2_equal 0 +bls12_381_g2_compress 65 0 1 Bls12_381_G2_compress 0 +bls12_381_g2_uncompress 66 0 1 Bls12_381_G2_uncompress 0 +bls12_381_g2_hashToGroup 67 0 2 Bls12_381_G2_hashToGroup 0 +bls12_381_millerLoop 68 0 2 Bls12_381_millerLoop 0 +bls12_381_mulMlResult 69 0 2 Bls12_381_mulMlResult 0 +bls12_381_finalVerify 70 0 2 Bls12_381_finalVerify 0 +keccak_256 71 0 1 Keccak_256 0 +blake2b_224 72 0 1 Blake2b_224 0 +integerToByteString 73 0 3 IntegerToByteString 0 +byteStringToInteger 74 0 2 ByteStringToInteger 0 +andByteString 75 0 3 AndByteString 0 +orByteString 76 0 3 OrByteString 0 +xorByteString 77 0 3 XorByteString 0 +complementByteString 78 0 1 ComplementByteString 0 +readBit 79 0 2 ReadBit 0 +writeBits 80 0 3 WriteBits 0 +replicateByte 81 0 2 ReplicateByte 0 +shiftByteString 82 0 2 ShiftByteString 0 +rotateByteString 83 0 2 RotateByteString 0 +countSetBits 84 0 1 CountSetBits 0 +findFirstSetBit 85 0 1 FindFirstSetBit 0 +ripemd_160 86 0 1 Ripemd_160 0 +expModInteger 87 0 3 ExpModInteger 0 +dropList 88 1 2 DropList 0 +lengthOfArray 89 1 1 - 0 +listToArray 90 1 1 - 0 +indexArray 91 1 2 - 0 +bls12_381_g1_multiScalarMul 92 0 2 Bls12_381_G1_multiScalarMul 0 +bls12_381_g2_multiScalarMul 93 0 2 Bls12_381_G2_multiScalarMul 0 +insertCoin 94 0 4 - 0 +lookupCoin 95 0 3 - 0 +unionValue 96 0 2 - 0 +valueContains 97 0 2 - 0 +valueData 98 0 1 - 0 +unValueData 99 0 1 - 0 +scaleValue 100 0 2 - 0 diff --git a/docs/builtin-compilation.md b/docs/builtin-compilation.md new file mode 100644 index 00000000..82b6fdb6 --- /dev/null +++ b/docs/builtin-compilation.md @@ -0,0 +1,95 @@ +# Builtin compilation certificates + +The builtin regression validates all **101 entries** in Nash's synthetic +`Builtin` module against a frozen UPLC compilation contract. It generates +**283 source declarations**: a first-class reference and every application +prefix up to full saturation for each builtin. Each declaration passes through +ordinary parsing, canonicalization, inference, specialization, O0 assembly, +and Flat encoding/decoding. + +The expected opcode and calling convention are independent of Nash's compiler +inventory and runtime metadata. The contract is recorded in +`crates/nash-driver/tests/fixtures/builtin-compilation/spec.tsv`. Its tags and +signatures come from the [Plutus Core specification](https://plutus.cardano.intersectmbo.org/resources/plutus-core-spec.pdf) +(Tables 4.18, 4.20 and C.8); modeled calling conventions also agree with the pinned +PlutusCoreBlaster `expectedArgs` definition. Inventory changes fail the regression +until the independent contract is updated. + +For every declaration, the emitted term must contain the correct builtin, +required forces, supplied arguments in their original order, and the expected +lambda and strict administrative bindings. Reference terms are constructed +directly; the oracle is never compiled through Nash. The regression checks the +opcode, forces and arity separately and rejects swapped opcodes, extra forces +and reversed arguments. The six saturated decoder/projection calls retain a +strict result binding, and all roots retain their O0 declaration binding. +These bindings remain in both terms, so their fuel overhead is included. + +## What Lean checks + +The opt-in suite preserves a generated `BuiltinCompilation.lean` and diagnostic +log in the system temporary directory. The reusable definitions are in +`crates/nash-driver/tests/fixtures/builtin-compilation/Certificate.lean`. + +| Coverage | Certificate | +|---|---| +| All 101 builtins, 283 declarations | Captured syntax equals the independent reference; applying arbitrary argument syntax gives equal results under any observation function | +| 91 builtins supported by pinned PlutusCoreBlaster, 254 declarations | Import the actual Flat bytes with PlutusCoreBlaster's independent decoder and prove the resulting term equals the reference UPLC term | +| Those same 254 declarations | Equal CEK states for every argument term list, semantics variant and fuel limit, preserving unfinished states at exhaustion | +| 91 modeled calling conventions | Force count and value arity agree with PlutusCoreBlaster's `expectedArgs` | + +These are Lean kernel-checked equality proofs, using `rfl` and equality rewriting; +no SMT solver, `native_decide`, or admitted Nash theorem is used. The assumption +audit requires all 283 generic observation certificates to be axiom-free. +The CEK corollaries depend only on Lean's standard `propext`, `Classical.choice` +and `Quot.sound` axioms through the imported evaluator; the regression rejects +other dependencies, including `sorryAx` and reduction-oracle axioms. + +The statements quantify over arbitrary UPLC argument terms, including errors, +function values and malformed inputs. They do not use a successful-return guard, +so rejection and exhaustion cannot make a wrong compilation pass vacuously. +They compare the complete CEK state at the same fuel, rather than only successful +return values. Crypto builtins are covered as calls to the same primitive; +this does not establish correctness of cryptographic implementations. + +## Limits + +This is **translation validation of the captured builtin declarations**, not a +formal verification of the Rust compiler for all Nash programs. The source +fixtures instantiate representation-polymorphic parameters as Data. Certificates +do not establish correctness of all type schemes, representation specializations, +compiler intrinsics, or optimizer passes. Those require separate contracts and +simulation theorems. Source `Primitive.coerce` is an intrinsic outside the 101-entry +runtime inventory and is not included. + +The pinned Lean model lacks `lengthOfArray`, `listToArray`, `indexArray`, +`insertCoin`, `lookupCoin`, `unionValue`, `valueContains`, `valueData`, `unValueData` +and `scaleValue`. Their compilation syntax is certified, but no PlutusCoreBlaster +CEK theorem is claimed for them. Completing that connection requires array/value +types, Flat decoding, calling conventions, evaluation and corresponding cost +models in PlutusCoreBlaster. CardanoLedgerApiBlaster is not needed for the current +compilation certificates because no ledger-context assumptions are involved. + +The link from the actual bytes to Lean terms uses the library's compile-time +Flat decoder. That importer, the Rust syntax capture and the compiler executable +are part of the validation setup; the decoder itself has not been formally +verified. Mathematical fidelity of the pinned builtin models and Cardano's +implementation remains a separate obligation. + +## Run + +The ordinary compilation regression needs only the Rust toolchain: + +```sh +cargo test -p nash-driver --test builtin_compilation +``` + +With the pinned Lean project already built: + +```sh +NASH_PROOF_LEAN_PROJECT=/path/to/built/CardanoLedgerApiBlaster \ + cargo test -p nash-driver --test builtin_compilation -- --ignored --nocapture +``` + +This follows the same opt-in toolchain arrangement as the live Nash proof +regressions. It does not require Z3 because the certificates use structural +equality rather than SMT verification. diff --git a/docs/proofs.md b/docs/proofs.md index 4d7d6308..7f1d8be9 100644 --- a/docs/proofs.md +++ b/docs/proofs.md @@ -177,6 +177,14 @@ cross this boundary yet. `fail` and `fail once` modifiers are rejected for statements does not select partial correctness: only the direct body call is recognized. Outside that position it is an ordinary strict assertion helper. +## Compiler validation + +A separate [builtin compilation suite](builtin-compilation.md) validates all +101 builtin entries across 283 reference/application forms. Its Lean kernel +certificates establish syntax equality and, for 91 modeled builtins, equality +of CEK states for arbitrary arguments and fuel. This uses an independent UPLC +reference rather than a Nash postcondition compiled through the same backend. + ## Coverage of existing tests The existing Base fixtures now include 15 proof declarations alongside their From c7e2f6ea425639f5c404b0ab577a9b77a10b6787 Mon Sep 17 00:00:00 2001 From: RSoulatIOHK Date: Thu, 1 Oct 2026 22:06:34 +0200 Subject: [PATCH 6/7] feat: specify builtin semantics and rejection modes in Nash proofs --- .sampo/changesets/lean-blaster-proofs.md | 7 + crates/nash-driver/tests/builtin_proofs.rs | 409 +++ crates/nash-plutus/src/machine/runtime.rs | 27 +- docs/builtin-compilation.md | 4 + docs/proofs.md | 11 + proofs/README.md | 225 ++ proofs/builtins/AddInteger.nash | 103 + proofs/builtins/AndByteString.nash | 35 + proofs/builtins/AppendByteString.nash | 46 + proofs/builtins/AppendString.nash | 32 + proofs/builtins/BData.nash | 18 + proofs/builtins/Blake2b224.nash | 27 + proofs/builtins/Blake2b256.nash | 27 + proofs/builtins/Bls12381FinalVerify.nash | 32 + proofs/builtins/Bls12381G1Add.nash | 30 + proofs/builtins/Bls12381G1Compress.nash | 25 + proofs/builtins/Bls12381G1Equal.nash | 23 + proofs/builtins/Bls12381G1HashToGroup.nash | 34 + proofs/builtins/Bls12381G1Neg.nash | 29 + proofs/builtins/Bls12381G1ScalarMul.nash | 29 + proofs/builtins/Bls12381G1Uncompress.nash | 47 + proofs/builtins/Bls12381G2Add.nash | 30 + proofs/builtins/Bls12381G2Compress.nash | 25 + proofs/builtins/Bls12381G2Equal.nash | 23 + proofs/builtins/Bls12381G2HashToGroup.nash | 34 + proofs/builtins/Bls12381G2Neg.nash | 29 + proofs/builtins/Bls12381G2ScalarMul.nash | 29 + proofs/builtins/Bls12381G2Uncompress.nash | 47 + proofs/builtins/Bls12381MillerLoop.nash | 34 + proofs/builtins/Bls12381MulMlResult.nash | 40 + proofs/builtins/ByteStringToInteger.nash | 47 + proofs/builtins/ChooseData.nash | 65 + proofs/builtins/ChooseList.nash | 39 + proofs/builtins/ChooseUnit.nash | 22 + proofs/builtins/ComplementByteString.nash | 33 + proofs/builtins/ConsByteString.nash | 97 + proofs/builtins/ConstrData.nash | 33 + proofs/builtins/CountSetBits.nash | 38 + proofs/builtins/DecodeUtf8.nash | 57 + proofs/builtins/DivideInteger.nash | 54 + proofs/builtins/DropList.nash | 48 + proofs/builtins/EncodeUtf8.nash | 38 + proofs/builtins/EqualsByteString.nash | 46 + proofs/builtins/EqualsData.nash | 53 + proofs/builtins/EqualsInteger.nash | 51 + proofs/builtins/EqualsString.nash | 45 + proofs/builtins/ExpModInteger.nash | 47 + proofs/builtins/FindFirstSetBit.nash | 30 + proofs/builtins/FstPair.nash | 19 + proofs/builtins/HeadList.nash | 27 + proofs/builtins/IData.nash | 18 + proofs/builtins/IfThenElse.nash | 62 + proofs/builtins/IndexArray.nash | 66 + proofs/builtins/IndexByteString.nash | 73 + proofs/builtins/InsertCoin.nash | 106 + proofs/builtins/IntegerToByteString.nash | 73 + proofs/builtins/Keccak256.nash | 19 + proofs/builtins/LengthOfArray.nash | 26 + proofs/builtins/LengthOfByteString.nash | 38 + proofs/builtins/LessThanByteString.nash | 30 + proofs/builtins/LessThanEqualsByteString.nash | 33 + proofs/builtins/LessThanEqualsInteger.nash | 51 + proofs/builtins/LessThanInteger.nash | 52 + proofs/builtins/ListData.nash | 33 + proofs/builtins/ListToArray.nash | 27 + proofs/builtins/LookupCoin.nash | 39 + proofs/builtins/MapData.nash | 46 + proofs/builtins/MkCons.nash | 43 + proofs/builtins/MkNilData.nash | 20 + proofs/builtins/MkNilPairData.nash | 20 + proofs/builtins/MkPairData.nash | 25 + proofs/builtins/ModInteger.nash | 71 + proofs/builtins/MultiplyInteger.nash | 71 + proofs/builtins/NullList.nash | 23 + proofs/builtins/OrByteString.nash | 35 + proofs/builtins/QuotientInteger.nash | 54 + proofs/builtins/ReadBit.nash | 56 + proofs/builtins/RemainderInteger.nash | 73 + proofs/builtins/ReplicateByte.nash | 49 + proofs/builtins/Ripemd160.nash | 24 + proofs/builtins/RotateByteString.nash | 61 + proofs/builtins/ScaleValue.nash | 53 + proofs/builtins/SerialiseData.nash | 20 + proofs/builtins/Sha2256.nash | 27 + proofs/builtins/Sha3256.nash | 27 + proofs/builtins/ShiftByteString.nash | 69 + proofs/builtins/SliceByteString.nash | 48 + proofs/builtins/SndPair.nash | 19 + proofs/builtins/SubtractInteger.nash | 48 + proofs/builtins/TailList.nash | 39 + proofs/builtins/Trace.nash | 24 + proofs/builtins/UnBData.nash | 35 + proofs/builtins/UnConstrData.nash | 53 + proofs/builtins/UnIData.nash | 35 + proofs/builtins/UnListData.nash | 47 + proofs/builtins/UnMapData.nash | 61 + proofs/builtins/UnValueData.nash | 139 + proofs/builtins/UnionValue.nash | 39 + proofs/builtins/ValueContains.nash | 46 + proofs/builtins/ValueData.nash | 31 + .../VerifyEcdsaSecp256k1Signature.nash | 33 + proofs/builtins/VerifyEd25519Signature.nash | 56 + .../VerifySchnorrSecp256k1Signature.nash | 33 + proofs/builtins/WriteBits.nash | 61 + proofs/builtins/XorByteString.nash | 35 + proofs/nash.jsonc | 1 + .../builtins/Bls12381G1MultiScalarMul.nash | 63 + .../builtins/Bls12381G2MultiScalarMul.nash | 63 + proofs/pending/nash.jsonc | 1 + proofs/verification.json | 2342 +++++++++++++++++ proofs/verified.tsv | 66 + 111 files changed, 7414 insertions(+), 17 deletions(-) create mode 100644 crates/nash-driver/tests/builtin_proofs.rs create mode 100644 proofs/README.md create mode 100644 proofs/builtins/AddInteger.nash create mode 100644 proofs/builtins/AndByteString.nash create mode 100644 proofs/builtins/AppendByteString.nash create mode 100644 proofs/builtins/AppendString.nash create mode 100644 proofs/builtins/BData.nash create mode 100644 proofs/builtins/Blake2b224.nash create mode 100644 proofs/builtins/Blake2b256.nash create mode 100644 proofs/builtins/Bls12381FinalVerify.nash create mode 100644 proofs/builtins/Bls12381G1Add.nash create mode 100644 proofs/builtins/Bls12381G1Compress.nash create mode 100644 proofs/builtins/Bls12381G1Equal.nash create mode 100644 proofs/builtins/Bls12381G1HashToGroup.nash create mode 100644 proofs/builtins/Bls12381G1Neg.nash create mode 100644 proofs/builtins/Bls12381G1ScalarMul.nash create mode 100644 proofs/builtins/Bls12381G1Uncompress.nash create mode 100644 proofs/builtins/Bls12381G2Add.nash create mode 100644 proofs/builtins/Bls12381G2Compress.nash create mode 100644 proofs/builtins/Bls12381G2Equal.nash create mode 100644 proofs/builtins/Bls12381G2HashToGroup.nash create mode 100644 proofs/builtins/Bls12381G2Neg.nash create mode 100644 proofs/builtins/Bls12381G2ScalarMul.nash create mode 100644 proofs/builtins/Bls12381G2Uncompress.nash create mode 100644 proofs/builtins/Bls12381MillerLoop.nash create mode 100644 proofs/builtins/Bls12381MulMlResult.nash create mode 100644 proofs/builtins/ByteStringToInteger.nash create mode 100644 proofs/builtins/ChooseData.nash create mode 100644 proofs/builtins/ChooseList.nash create mode 100644 proofs/builtins/ChooseUnit.nash create mode 100644 proofs/builtins/ComplementByteString.nash create mode 100644 proofs/builtins/ConsByteString.nash create mode 100644 proofs/builtins/ConstrData.nash create mode 100644 proofs/builtins/CountSetBits.nash create mode 100644 proofs/builtins/DecodeUtf8.nash create mode 100644 proofs/builtins/DivideInteger.nash create mode 100644 proofs/builtins/DropList.nash create mode 100644 proofs/builtins/EncodeUtf8.nash create mode 100644 proofs/builtins/EqualsByteString.nash create mode 100644 proofs/builtins/EqualsData.nash create mode 100644 proofs/builtins/EqualsInteger.nash create mode 100644 proofs/builtins/EqualsString.nash create mode 100644 proofs/builtins/ExpModInteger.nash create mode 100644 proofs/builtins/FindFirstSetBit.nash create mode 100644 proofs/builtins/FstPair.nash create mode 100644 proofs/builtins/HeadList.nash create mode 100644 proofs/builtins/IData.nash create mode 100644 proofs/builtins/IfThenElse.nash create mode 100644 proofs/builtins/IndexArray.nash create mode 100644 proofs/builtins/IndexByteString.nash create mode 100644 proofs/builtins/InsertCoin.nash create mode 100644 proofs/builtins/IntegerToByteString.nash create mode 100644 proofs/builtins/Keccak256.nash create mode 100644 proofs/builtins/LengthOfArray.nash create mode 100644 proofs/builtins/LengthOfByteString.nash create mode 100644 proofs/builtins/LessThanByteString.nash create mode 100644 proofs/builtins/LessThanEqualsByteString.nash create mode 100644 proofs/builtins/LessThanEqualsInteger.nash create mode 100644 proofs/builtins/LessThanInteger.nash create mode 100644 proofs/builtins/ListData.nash create mode 100644 proofs/builtins/ListToArray.nash create mode 100644 proofs/builtins/LookupCoin.nash create mode 100644 proofs/builtins/MapData.nash create mode 100644 proofs/builtins/MkCons.nash create mode 100644 proofs/builtins/MkNilData.nash create mode 100644 proofs/builtins/MkNilPairData.nash create mode 100644 proofs/builtins/MkPairData.nash create mode 100644 proofs/builtins/ModInteger.nash create mode 100644 proofs/builtins/MultiplyInteger.nash create mode 100644 proofs/builtins/NullList.nash create mode 100644 proofs/builtins/OrByteString.nash create mode 100644 proofs/builtins/QuotientInteger.nash create mode 100644 proofs/builtins/ReadBit.nash create mode 100644 proofs/builtins/RemainderInteger.nash create mode 100644 proofs/builtins/ReplicateByte.nash create mode 100644 proofs/builtins/Ripemd160.nash create mode 100644 proofs/builtins/RotateByteString.nash create mode 100644 proofs/builtins/ScaleValue.nash create mode 100644 proofs/builtins/SerialiseData.nash create mode 100644 proofs/builtins/Sha2256.nash create mode 100644 proofs/builtins/Sha3256.nash create mode 100644 proofs/builtins/ShiftByteString.nash create mode 100644 proofs/builtins/SliceByteString.nash create mode 100644 proofs/builtins/SndPair.nash create mode 100644 proofs/builtins/SubtractInteger.nash create mode 100644 proofs/builtins/TailList.nash create mode 100644 proofs/builtins/Trace.nash create mode 100644 proofs/builtins/UnBData.nash create mode 100644 proofs/builtins/UnConstrData.nash create mode 100644 proofs/builtins/UnIData.nash create mode 100644 proofs/builtins/UnListData.nash create mode 100644 proofs/builtins/UnMapData.nash create mode 100644 proofs/builtins/UnValueData.nash create mode 100644 proofs/builtins/UnionValue.nash create mode 100644 proofs/builtins/ValueContains.nash create mode 100644 proofs/builtins/ValueData.nash create mode 100644 proofs/builtins/VerifyEcdsaSecp256k1Signature.nash create mode 100644 proofs/builtins/VerifyEd25519Signature.nash create mode 100644 proofs/builtins/VerifySchnorrSecp256k1Signature.nash create mode 100644 proofs/builtins/WriteBits.nash create mode 100644 proofs/builtins/XorByteString.nash create mode 100644 proofs/nash.jsonc create mode 100644 proofs/pending/builtins/Bls12381G1MultiScalarMul.nash create mode 100644 proofs/pending/builtins/Bls12381G2MultiScalarMul.nash create mode 100644 proofs/pending/nash.jsonc create mode 100644 proofs/verification.json create mode 100644 proofs/verified.tsv diff --git a/.sampo/changesets/lean-blaster-proofs.md b/.sampo/changesets/lean-blaster-proofs.md index 2f121fcc..f9e56cff 100644 --- a/.sampo/changesets/lean-blaster-proofs.md +++ b/.sampo/changesets/lean-blaster-proofs.md @@ -9,6 +9,7 @@ cargo/nash-nitpick: patch cargo/nash-report: minor cargo/nash-fmt: minor cargo/nash-codegen: minor +cargo/nash-plutus: patch cargo/nash-driver: minor cargo/nash-cli: minor cargo/nash-proof: minor @@ -33,3 +34,9 @@ Add independent compilation contracts for all 101 builtins and all application prefixes. Export kernel-checked syntax certificates and universal CEK equality corollaries for the 91 builtins supported by pinned PlutusCoreBlaster. Document the ten missing array/value models and the translation-validation scope. + +Add one Nash semantic specification per builtin with algebraic laws, representation +roundtrips, boundary cases and invalid-input rejection obligations. Record verified +and pending results explicitly and execute the source obligations in regressions, +including negative controls. Fix host-word index truncation, oversized index panics, +large shift/rotation panics and whole-byte left-shift copying in the local evaluator. diff --git a/crates/nash-driver/tests/builtin_proofs.rs b/crates/nash-driver/tests/builtin_proofs.rs new file mode 100644 index 00000000..cb02d0c4 --- /dev/null +++ b/crates/nash-driver/tests/builtin_proofs.rs @@ -0,0 +1,409 @@ +//! Execute and export the checked-in semantic obligations, not copies in Rust. +use nash_driver::{Database, FileSystemSource, Project, build_graph_with_tests, test_with}; +use nash_plutus::{ + arena::Arena, binder::DeBruijn, data::PlutusData, flat, machine::ExBudget, term::Term, +}; +use nash_proof::{Domain, ProofProgram}; +use std::{ + collections::BTreeSet, + path::{Path, PathBuf}, + sync::Arc, +}; +use tokio::sync::Mutex; + +fn root() -> PathBuf { + Path::new(env!("CARGO_MANIFEST_DIR")).join("../../proofs") +} + +fn verified_obligations() -> BTreeSet<(String, String)> { + include_str!("../../../proofs/verified.tsv") + .lines() + .filter(|line| !line.starts_with('#') && !line.is_empty()) + .map(|line| { + let (module, name) = line.split_once('\t').unwrap(); + (module.to_owned(), name.to_owned()) + }) + .collect() +} + +async fn compile(directory: &Path) -> Result, String> { + let project = Project::load(directory).await.unwrap(); + let db = Arc::new(Mutex::new(Database::new(FileSystemSource::new()))); + let modules = project.discover_modules(&*db.lock().await).await.unwrap(); + let roots = project + .discover_own_modules(&*db.lock().await) + .await + .unwrap(); + let graph = build_graph_with_tests( + db.clone(), + &modules.keys().cloned().collect::>(), + &roots.keys().cloned().collect::>(), + ) + .await + .unwrap(); + let (report, programs) = test_with(db, &graph, &modules, move |solved| { + nash_driver::build::compile_proofs_matching_with( + solved, + |uri| roots.contains_key(uri).then(nash_config::Build::default), + |_, _| true, + ) + }) + .await; + if !report.is_success() { + return Err(format!("{:#?}", report.ordered_reports())); + } + programs.unwrap().map_err(|e| e.message) +} + +fn sample<'a>( + arena: &'a Arena, + program: &ProofProgram, + domain: Domain, + index: usize, + case: usize, +) -> &'a Term<'a, DeBruijn> { + let integers = [-7, 0, 1, 17, 18446744073709551616, -9223372036854775809]; + let n = integers[(case + index) % integers.len()]; + match domain { + // The host Data representation stores constructor tags as u64. Lean's + // Data model has arbitrary integer tags; that separate host-model gap + // is documented and must not change the universal Nash obligation. + Domain::Int + if matches!(program.module.as_str(), "ConstrData" | "UnConstrData") && index == 0 => + { + Term::integer_from(arena, [0, 1, 17, 255, 65536, 18446744073709551615][case]) + } + Domain::Int => Term::integer_from(arena, n), + Domain::Integer => Term::data_integer_from(arena, n), + Domain::Bool => Term::bool(arena, (case + index).is_multiple_of(2)), + Domain::String => Term::string(arena, ["", "éλ", "a\0b"][(case + index) % 3]), + Domain::Bytes | Domain::ByteString => { + let bytes: &[u8] = if case == 4 && program.module.starts_with("Bls12381") { + let g2 = program.module.contains("G2") + || (!program.module.contains("G1") && index % 2 == 1); + let mut encoded = vec![0; if g2 { 96 } else { 48 }]; + encoded[0] = 0xc0; + arena.alloc_slice_copy(&encoded) + } else { + [b"".as_slice(), &[0], &[0, 1, 128, 255], &[255]][(case + index) % 4] + }; + if domain == Domain::Bytes { + Term::byte_string(arena, bytes) + } else { + Term::data_byte_string(arena, bytes) + } + } + Domain::Data => { + let value = match (case + index) % 5 { + 0 => PlutusData::integer_from(arena, n), + 1 => PlutusData::byte_string(arena, &[0, 255]), + 2 => PlutusData::constr(arena, 0, &[]), + 3 => PlutusData::list(arena, &[]), + _ => PlutusData::map(arena, &[]), + }; + Term::data(arena, value) + } + other => panic!("unexpected builtin domain {other:?}"), + } +} + +#[tokio::test] +async fn checked_in_builtin_obligations_compile_export_and_execute() { + let programs = compile(&root()).await.unwrap(); + assert_eq!( + programs + .iter() + .map(|p| &p.module) + .collect::>() + .len(), + 99 + ); + assert!(programs.len() > 300, "failure-mode obligations disappeared"); + let names = programs + .iter() + .map(|p| (p.module.clone(), p.name.clone())) + .collect::>(); + assert!( + verified_obligations().is_subset(&names), + "a previously verified obligation disappeared" + ); + + let output = + std::env::temp_dir().join(format!("nash-semantic-builtins-{}", std::process::id())); + let files = nash_proof::export(&programs, &output, 300, 300, 10).unwrap(); + assert_eq!(files.len(), programs.len()); + let mut failures = Vec::new(); + for proof in &programs { + let mut successful_returns = 0; + for case in 0..6 { + let arena = Arena::new(); + let mut computation = flat::decode::(&arena, &proof.flat).unwrap(); + let arguments = proof + .domains + .iter() + .enumerate() + .map(|(index, domain)| sample(&arena, proof, *domain, index, case)) + .collect::>(); + for argument in &arguments { + computation = computation.apply(&arena, argument); + } + let result = match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + computation.eval_version_budget( + &arena, + nash_plutus::machine::PlutusVersion::V3, + ExBudget::max(), + ) + })) { + Ok(evaluated) => evaluated.term, + Err(_) => { + failures.push(format!( + "{} / {} / sample {case}: evaluator panicked", + proof.module, proof.name + )); + continue; + } + }; + let passed = if let Some(checker) = &proof.postcondition { + if let Ok(value) = result { + successful_returns += 1; + let mut condition = flat::decode::(&arena, &checker.flat).unwrap(); + for argument in &arguments { + condition = condition.apply(&arena, argument); + } + let checked = condition.apply(&arena, value).eval_version_budget( + &arena, + nash_plutus::machine::PlutusVersion::V3, + ExBudget::max(), + ); + checked + .term + .is_ok_and(|value| value == Term::bool(&arena, true)) + } else { + true + } + } else { + match proof.expect { + nash_source::Expect::Pass => { + result.is_ok_and(|value| value == Term::unit(&arena)) + } + nash_source::Expect::Fail => result.is_err(), + nash_source::Expect::FailOnce => { + panic!("builtin suite must not search for witnesses") + } + } + }; + if !passed { + failures.push(format!("{} / {} / sample {case}", proof.module, proof.name)); + } + if proof.domains.is_empty() { + break; + } + } + if proof.postcondition.is_some() && successful_returns == 0 { + failures.push(format!( + "{} / {}: all samples vacuous; provide a valid input", + proof.module, proof.name + )); + } + } + assert!(failures.is_empty(), "{}", failures.join("\n")); +} + +#[test] +fn every_builtin_has_its_own_nash_specification() { + let mut covered = BTreeSet::new(); + for builtin in nash_ast::primitives::BUILTINS { + let module = builtin + .name + .split('_') + .map(|part| format!("{}{}", part[..1].to_uppercase(), &part[1..])) + .collect::(); + let file = format!("{module}.nash"); + let active = root().join("builtins").join(&file); + let pending = root().join("pending/builtins").join(&file); + assert_ne!( + active.is_file(), + pending.is_file(), + "exactly one spec for {}", + builtin.name + ); + let source = + std::fs::read_to_string(if active.is_file() { active } else { pending }).unwrap(); + assert!( + source.contains(&format!("Builtin.{}", builtin.name)) && source.contains("\nproof\n"), + "{} has no executable obligation", + builtin.name + ); + covered.insert(module); + } + assert_eq!(covered.len(), 101); +} + +#[tokio::test] +async fn pending_multi_scalar_obligations_fail_explicitly_in_codegen() { + let error = compile(&root().join("pending")).await.unwrap_err(); + assert!( + error.contains("Bls12_381G1Element") && error.contains("unavailable"), + "{error}" + ); +} + +#[tokio::test] +async fn semantic_obligations_detect_wrong_opcodes_argument_order_and_wrapping() { + let mutations = [ + ( + "AddInteger", + "add = Builtin.addInteger", + "add = Builtin.subtractInteger", + "commutative", + 0, + ), + ( + "AddInteger", + "add = Builtin.addInteger", + "add = Builtin.multiplyInteger", + "left identity", + 1, + ), + ( + "AddInteger", + "add = Builtin.addInteger", + "add x _ = x", + "commutative", + 0, + ), + ( + "AddInteger", + "add = Builtin.addInteger", + "add x y = Builtin.modInteger (Builtin.addInteger x y) 18446744073709551616", + "successor never wraps at any integer width", + 18446744073709551615, + ), + ( + "AddInteger", + "add = Builtin.addInteger", + "add x y = let sum = Builtin.addInteger x y in if sum > 9223372036854775807 then 9223372036854775807 else sum", + "successor never wraps at any integer width", + 9223372036854775807, + ), + ( + "AppendByteString", + "Builtin.appendByteString p0 p1", + "Builtin.appendByteString p1 p0", + "preserves byte order", + 0, + ), + ( + "FstPair", + "Builtin.fstPair p0", + "Builtin.sndPair p0", + "projects the correct component", + 0, + ), + ]; + let directory = + std::env::temp_dir().join(format!("nash-builtin-mutants-{}", std::process::id())); + std::fs::create_dir_all(directory.join("src")).unwrap(); + std::fs::write(directory.join("nash.jsonc"), "{\"type\":\"application\"}\n").unwrap(); + for (i, (module, original, mutant, _, _)) in mutations.iter().enumerate() { + let source = + std::fs::read_to_string(root().join("builtins").join(format!("{module}.nash"))) + .unwrap(); + assert!( + source.contains(original), + "mutation no longer applies to {module}" + ); + let source = source + .replacen( + &format!("module {module} "), + &format!("module Mutant{i} "), + 1, + ) + .replacen(original, mutant, 1); + std::fs::write( + directory.join("src").join(format!("Mutant{i}.nash")), + source, + ) + .unwrap(); + } + let programs = compile(&directory).await.unwrap(); + for (i, (_, _, _, obligation, witness)) in mutations.iter().enumerate() { + let proof = programs + .iter() + .find(|p| p.module == format!("Mutant{i}") && p.name == *obligation) + .unwrap(); + let arena = Arena::new(); + let mut program = flat::decode::(&arena, &proof.flat).unwrap(); + for (index, domain) in proof.domains.iter().enumerate() { + let input = match domain { + Domain::Int => Term::integer_from(&arena, witness + index as i128), + Domain::Data => Term::data_integer_from(&arena, index as i128), + other => panic!("unexpected mutant domain {other:?}"), + }; + program = program.apply(&arena, input); + } + assert!( + program + .eval_version_budget( + &arena, + nash_plutus::machine::PlutusVersion::V3, + ExBudget::max() + ) + .term + .is_err(), + "mutation {i} escaped {obligation}" + ); + } +} + +#[tokio::test] +#[ignore = "requires NASH_PROOF_LEAN_PROJECT, the pinned Lean dependencies and Z3"] +async fn live_builtin_semantics() { + let matching = std::env::var("NASH_PROOF_MATCH").ok(); + let verified = verified_obligations(); + let programs = compile(&root()) + .await + .unwrap() + .into_iter() + .filter(|p| { + matching.as_ref().map_or_else( + || verified.contains(&(p.module.clone(), p.name.clone())), + |pattern| format!("{}.{{{}}}", p.module, p.name).contains(pattern), + ) + }) + .collect::>(); + assert!(!programs.is_empty(), "no obligations matched {matching:?}"); + let config = nash_proof::Config { + lean_project: std::env::var_os("NASH_PROOF_LEAN_PROJECT") + .expect("Lean project") + .into(), + fuel: 300, + postcondition_fuel: 300, + solver_timeout: 10, + wall_timeout: std::time::Duration::from_secs(60), + }; + let output = std::env::temp_dir().join(format!( + "nash-live-builtin-semantics-{}", + std::process::id() + )); + let files = nash_proof::export( + &programs, + &output, + config.fuel, + config.postcondition_fuel, + config.solver_timeout, + ) + .unwrap(); + let mut failures = Vec::new(); + for (proof, source) in programs.iter().zip(files) { + let outcome = nash_proof::run(proof, &source, &config).unwrap(); + eprintln!("{}.{{{}}}: {:?}", proof.module, proof.name, outcome.status); + if !outcome.passed() { + failures.push(format!( + "{} / {}: {:?}: {}", + proof.module, proof.name, outcome.status, outcome.diagnostics + )); + } + } + assert!(failures.is_empty(), "{}", failures.join("\n")); +} diff --git a/crates/nash-plutus/src/machine/runtime.rs b/crates/nash-plutus/src/machine/runtime.rs index 551e680f..82ac4a91 100644 --- a/crates/nash-plutus/src/machine/runtime.rs +++ b/crates/nash-plutus/src/machine/runtime.rs @@ -626,10 +626,10 @@ impl<'a, B: BuiltinCostModel, V: Eval<'a>> Machine<'a, B, V> { self.spend_budget(budget)?; - let index: i128 = arg2.try_into().unwrap(); - - if 0 <= index && (index as usize) < arg1.len() { - let result: Integer = arg1[index as usize].into(); + if let Ok(index) = usize::try_from(arg2) + && let Some(byte) = arg1.get(index) + { + let result: Integer = (*byte).into(); let new = self.arena.alloc_integer(result); let value = Value::integer(self.arena, new); @@ -2756,10 +2756,7 @@ impl<'a, B: BuiltinCostModel, V: Eval<'a>> Machine<'a, B, V> { let bytes = runtime.args[0].unwrap_byte_string()?; let shift = runtime.args[1].unwrap_integer()?; - let arg1: i64 = u64::try_from(shift.abs()) - .unwrap() - .try_into() - .unwrap_or(i64::MAX); + let arg1 = i64::try_from(shift.abs()).unwrap_or(i64::MAX); let budget = self .costs @@ -2787,7 +2784,7 @@ impl<'a, B: BuiltinCostModel, V: Eval<'a>> Machine<'a, B, V> { if is_shift_left { if bit_shift == 0 { // If we can shift entire bytes, that's much simpler - let copy_len = length - bit_shift; + let copy_len = length - byte_shift; // For example, consider the following byte array [1,0,1,0,1] being shifted 8 bits (1 byte) // Result: [0,1,0,1,0] result[..copy_len].copy_from_slice(&bytes[byte_shift..]); @@ -2849,10 +2846,7 @@ impl<'a, B: BuiltinCostModel, V: Eval<'a>> Machine<'a, B, V> { let bytes = runtime.args[0].unwrap_byte_string()?; let shift = runtime.args[1].unwrap_integer()?; - let arg1: i64 = u64::try_from(shift.abs()) - .unwrap() - .try_into() - .unwrap_or(i64::MAX); + let arg1 = i64::try_from(shift.abs()).unwrap_or(i64::MAX); let budget = self .costs @@ -3131,10 +3125,9 @@ impl<'a, B: BuiltinCostModel, V: Eval<'a>> Machine<'a, B, V> { .ok_or(MachineError::NoCostForBuiltin(DefaultFunction::IndexArray))?; self.spend_budget(budget)?; - let index: i128 = arg1.try_into().unwrap(); - - if 0 <= index && (index as usize) < array.len() { - let element = array[index as usize]; + if let Ok(index) = usize::try_from(arg1) + && let Some(element) = array.get(index) + { let value = Value::con(self.arena, element); Ok(value) } else { diff --git a/docs/builtin-compilation.md b/docs/builtin-compilation.md index 82b6fdb6..a20f8903 100644 --- a/docs/builtin-compilation.md +++ b/docs/builtin-compilation.md @@ -1,5 +1,9 @@ # Builtin compilation certificates +The readable [Nash semantic proof files](../proofs/README.md) specify each +builtin's algebra, representations, boundaries and failure modes. The certificates +below complement those properties by checking the independent calling convention. + The builtin regression validates all **101 entries** in Nash's synthetic `Builtin` module against a frozen UPLC compilation contract. It generates **283 source declarations**: a first-class reference and every application diff --git a/docs/proofs.md b/docs/proofs.md index 7f1d8be9..abc19562 100644 --- a/docs/proofs.md +++ b/docs/proofs.md @@ -177,6 +177,17 @@ cross this boundary yet. `fail` and `fail once` modifiers are rejected for statements does not select partial correctness: only the direct body call is recognized. Outside that position it is an ordinary strict assertion helper. +## Builtin semantic specifications + +The [builtin proof project](../proofs/README.md) contains one Nash file per +builtin and 324 readable obligations: algebraic laws, unbounded integer behavior, +conversion roundtrips, boundary cases and invalid-input rejection. Its explicit +verification snapshot records 64 SMT-verified obligations and all remaining +limitations. The standard regression executes the actual source obligations, +requires nonvacuous partial-correctness samples, and rejects deliberate opcode, +argument-order, wrap and saturation mutations. This suite also exposed and guards +the local evaluator's index and shift fixes. + ## Compiler validation A separate [builtin compilation suite](builtin-compilation.md) validates all diff --git a/proofs/README.md b/proofs/README.md new file mode 100644 index 00000000..872da67b --- /dev/null +++ b/proofs/README.md @@ -0,0 +1,225 @@ +# Semantic builtin proofs + +Each builtin has its own Nash file, its compiled source binding, and readable +`proof` blocks describing its behavior. Start with [AddInteger.nash](builtins/AddInteger.nash): +commutativity, associativity, identities, inverses, an unbounded successor law, +Data-represented integer handling, and explicit 64/128-bit boundary witnesses. + +There are **324 obligations in 101 files**. The active project compiles and exports +**318 obligations in 99 files**. The two multi-scalar files have six further +obligations in [pending/builtins](pending/builtins); Nash currently cannot encode +the BLS element type in their native list constants. They have a separate project +so this compiler limitation does not prevent exporting the active suite. + +The checked-in [verification snapshot](verification.json) records every obligation +as verified, unchecked, timed out, blocked, or refuted in an opaque model. +**64 obligations are SMT-verified** at 300 CEK steps with the pinned Lean 4.24 +dependencies. [verified.tsv](verified.tsv) lists that reproducible subset. +Successful SMT verification has the backend's existing `smt_verified` trust level; +it is not a reconstructed Lean kernel proof. + +`Proof.int` and `Proof.integer` quantify over arbitrary mathematical integers, +without a bit-width bound. The successor law would fail for wrapping or saturating +addition. Large closed witnesses are constructed from bytes when they exceed the +parser's integer-literal range. Value quantities have a distinct signed 128-bit +contract and must reject overflow rather than inherit unbounded arithmetic laws. + +Passing execution obligations require success within the execution limit. +`fail` includes script error and exhaustion. `Proof.returns` checks successful +results only; native regressions also require a successful sample for every such +obligation, including valid compressed BLS points. Native examples supplement the +symbolic checks and do not turn unchecked obligations into universal proofs. + +## Run + +```sh +# Compile/export every active obligation without Lean. +nash proof proofs --emit-only --fuel 300 --postcondition-fuel 300 + +# Run all addition laws, including the currently unverified associativity law. +nash proof proofs --lean-project /path/to/built/CardanoLedgerApiBlaster \ + --fuel 300 --postcondition-fuel 300 --match AddInteger --exact + +# Execute the actual source obligations and deliberate compiler mutations. +cargo test -p nash-driver --test builtin_proofs + +# Recheck the recorded verified subset with Lean and Z3. +NASH_PROOF_LEAN_PROJECT=/path/to/built/CardanoLedgerApiBlaster \ + cargo test -p nash-driver --test builtin_proofs live_builtin_semantics -- --ignored --nocapture + +# Attempt any pending active obligation; unsuccessful results fail the test. +NASH_PROOF_MATCH='AddInteger.{associative}' \ +NASH_PROOF_LEAN_PROJECT=/path/to/built/CardanoLedgerApiBlaster \ + cargo test -p nash-driver --test builtin_proofs live_builtin_semantics -- --ignored --nocapture +``` + +The standard regression checks the one-file-per-builtin inventory, compiles and +exports the source proof blocks, executes representative inputs, checks successful +partial-correctness samples, and detects seven deliberate mutations: subtraction +or multiplication substituted for addition, a dropped argument, wrapping, +saturation, reversed byte append, and the wrong pair projection. A live Lean +check also rejected the closed addition contract compiled with subtraction. + +## Failure modes and obligations + +The contracts follow the [Plutus Core specification](https://plutus.cardano.intersectmbo.org/resources/plutus-core-spec.pdf) +and the pinned model. Each linked file contains the actual Nash statements. + +| Possible failure | Corresponding obligations | +|---|---| +| Wrong integer primitive, lost argument, wrap, saturation, signedness | [addition](builtins/AddInteger.nash), [subtraction](builtins/SubtractInteger.nash), [multiplication](builtins/MultiplyInteger.nash), comparison laws and distinguishing results | +| Floor division confused with truncation; zero divisor accepted | [divide](builtins/DivideInteger.nash), [quotient](builtins/QuotientInteger.nash), [mod](builtins/ModInteger.nash), [remainder](builtins/RemainderInteger.nash): reconstruction, signs, rounding, zero-divisor rejection | +| Equality always True, wrong Data variant or comparison ordering | [integer equality](builtins/EqualsInteger.nash), [byte equality](builtins/EqualsByteString.nash), [Data equality](builtins/EqualsData.nash), ordered comparisons | +| Appended inputs reversed; bytes counted as characters | [append](builtins/AppendByteString.nash), [length](builtins/LengthOfByteString.nash): order witnesses and additive lengths | +| Slice offset/count interchanged; negative values mishandled | [slice](builtins/SliceByteString.nash): clamping, empty slices, start/end boundaries | +| Index truncates to a host word or panics above 128 bits | [byte index](builtins/IndexByteString.nash), [array index](builtins/IndexArray.nash), [read bit](builtins/ReadBit.nash), [write bits](builtins/WriteBits.nash): negative, one-past-end, 64-bit and 129-bit indices | +| Byte value out of range; output size exceeds 8192; integer does not fit | [cons](builtins/ConsByteString.nash), [replicate](builtins/ReplicateByte.nash), [integer encoding](builtins/IntegerToByteString.nash): rejection and accepted boundary cases | +| Endianness or padding incorrect; big integers truncated | [integer encoding](builtins/IntegerToByteString.nash), [integer decoding](builtins/ByteStringToInteger.nash): known encodings, roundtrips, arbitrary widths | +| Invalid UTF8 accepted or Unicode/embedded zero corrupted | [encode](builtins/EncodeUtf8.nash), [decode](builtins/DecodeUtf8.nash): roundtrips, non-ASCII witnesses, overlong/truncated/surrogate/out-of-range rejection | +| Pair projection swapped; empty-list access succeeds; branch selection wrong | pair/list/choice files: projections, tails, empty access rejection, all five Data branches, strict conditional arguments | +| Wrong Data decoder accepts another variant or loses payload | Data constructor/decoder files: roundtrips, integer/byte/constructor shape mismatches | +| Whole-byte shifts copy wrong lengths; enormous shifts/rotations panic | [shift](builtins/ShiftByteString.nash), [rotate](builtins/RotateByteString.nash): direction, zero fill, width/inverse laws, 129-bit counts | +| Wrong hash primitive or digest width | six hash files: digest width and empty-message known answers where supplied; cryptographic model contracts remain pending | +| Malformed signature confused with well-formed verification False | signature files: invalid lengths, [RFC8032](https://www.rfc-editor.org/rfc/rfc8032) valid Ed25519 vector and changed-message False | +| Invalid compressed point/DST accepted; group operation or MSM incorrect | BLS files: valid-point algebra, compressed roundtrips, wrong widths, oversized DST, mismatched MSM lengths and scalar range | +| Wrong value update, zero removal, quantity overflow or negative containment | [insert](builtins/InsertCoin.nash), [union](builtins/UnionValue.nash), [scale](builtins/ScaleValue.nash), [containment](builtins/ValueContains.nash) | +| Malformed asset map accepted | [unValueData](builtins/UnValueData.nash): wrong Data variant, empty inner map, zero quantities, duplicate and unsorted names | + +## Findings and open model work + +The new obligations reproduced and now guard these local evaluator fixes: +`indexByteString` and `indexArray` reject unrepresentable indices instead of +truncating or panicking; shift/rotate counts no longer panic above u64; whole-byte +left shifts copy the correct number of bytes. The array rejection examples use +a monomorphic `array int` binding: an unused generalized literal template has no +runtime specialization in Nash, so it cannot exercise a rejection obligation. + +Associativity and several symbolic arithmetic/ordering obligations hit the +preparation wall limit. The ten array/value builtins lack a pinned Lean model. +BLS validation reaches unsupported `Fin`, and some symbolic bytestring operations +reach unsupported `BitVec` in the SMT translator. These outcomes remain pending. + +Cryptographic primitives and modular exponentiation are opaque in the pinned +model and lack supporting semantic lemmas. SMT can assign them behavior that +violates the intended rejection contract; the 12 such model counterexamples in +the snapshot are **not concrete Nash counterexamples**. Upstream work should add +contracts for shape validation, digest lengths, group laws and modular-exponentiation +error conditions, alongside the missing array/value model and translator support. +Native vector checks establish the listed examples independently of those SMT +abstractions; they do not verify cryptographic algorithms. + +The Rust Data representation stores constructor tags as u64 while Lean permits +arbitrary integer tags. Native smoke inputs for constructor roundtrips use the +common representable range; the Nash obligations retain their full symbolic +domains. The native `dropList` cost calculation also still converts the absolute +count through u64; counts above that range need an overflow-safe costing fix and +a corresponding large-count regression. Fixing these host gaps and proving all +compiler/type/optimizer specializations remain separate work. The existing independent +[calling-convention certificates](../docs/builtin-compilation.md) check opcodes, +forces and application prefixes across all 101 builtins. + +## Index + +| Builtin source file | Obligations | SMT-verified | +|---|---:|---:| +| [AddInteger](builtins/AddInteger.nash) | 11 | 10 | +| [AndByteString](builtins/AndByteString.nash) | 3 | 0 | +| [AppendByteString](builtins/AppendByteString.nash) | 4 | 0 | +| [AppendString](builtins/AppendString.nash) | 3 | 0 | +| [BData](builtins/BData.nash) | 1 | 0 | +| [Blake2b224](builtins/Blake2b224.nash) | 2 | 0 | +| [Blake2b256](builtins/Blake2b256.nash) | 2 | 0 | +| [Bls12381FinalVerify](builtins/Bls12381FinalVerify.nash) | 1 | 0 | +| [Bls12381G1Add](builtins/Bls12381G1Add.nash) | 1 | 0 | +| [Bls12381G1Compress](builtins/Bls12381G1Compress.nash) | 1 | 0 | +| [Bls12381G1Equal](builtins/Bls12381G1Equal.nash) | 1 | 0 | +| [Bls12381G1HashToGroup](builtins/Bls12381G1HashToGroup.nash) | 2 | 0 | +| [Bls12381G1Neg](builtins/Bls12381G1Neg.nash) | 1 | 0 | +| [Bls12381G1ScalarMul](builtins/Bls12381G1ScalarMul.nash) | 1 | 0 | +| [Bls12381G1Uncompress](builtins/Bls12381G1Uncompress.nash) | 4 | 0 | +| [Bls12381G2Add](builtins/Bls12381G2Add.nash) | 1 | 0 | +| [Bls12381G2Compress](builtins/Bls12381G2Compress.nash) | 1 | 0 | +| [Bls12381G2Equal](builtins/Bls12381G2Equal.nash) | 1 | 0 | +| [Bls12381G2HashToGroup](builtins/Bls12381G2HashToGroup.nash) | 2 | 0 | +| [Bls12381G2Neg](builtins/Bls12381G2Neg.nash) | 1 | 0 | +| [Bls12381G2ScalarMul](builtins/Bls12381G2ScalarMul.nash) | 1 | 0 | +| [Bls12381G2Uncompress](builtins/Bls12381G2Uncompress.nash) | 4 | 0 | +| [Bls12381MillerLoop](builtins/Bls12381MillerLoop.nash) | 1 | 0 | +| [Bls12381MulMlResult](builtins/Bls12381MulMlResult.nash) | 1 | 0 | +| [ByteStringToInteger](builtins/ByteStringToInteger.nash) | 4 | 0 | +| [ChooseData](builtins/ChooseData.nash) | 5 | 0 | +| [ChooseList](builtins/ChooseList.nash) | 3 | 0 | +| [ChooseUnit](builtins/ChooseUnit.nash) | 2 | 0 | +| [ComplementByteString](builtins/ComplementByteString.nash) | 3 | 0 | +| [ConsByteString](builtins/ConsByteString.nash) | 6 | 2 | +| [ConstrData](builtins/ConstrData.nash) | 1 | 0 | +| [CountSetBits](builtins/CountSetBits.nash) | 2 | 0 | +| [DecodeUtf8](builtins/DecodeUtf8.nash) | 7 | 1 | +| [DivideInteger](builtins/DivideInteger.nash) | 5 | 4 | +| [DropList](builtins/DropList.nash) | 5 | 0 | +| [EncodeUtf8](builtins/EncodeUtf8.nash) | 4 | 0 | +| [EqualsByteString](builtins/EqualsByteString.nash) | 4 | 0 | +| [EqualsData](builtins/EqualsData.nash) | 4 | 0 | +| [EqualsInteger](builtins/EqualsInteger.nash) | 5 | 2 | +| [EqualsString](builtins/EqualsString.nash) | 4 | 0 | +| [ExpModInteger](builtins/ExpModInteger.nash) | 6 | 0 | +| [FindFirstSetBit](builtins/FindFirstSetBit.nash) | 2 | 0 | +| [FstPair](builtins/FstPair.nash) | 1 | 0 | +| [HeadList](builtins/HeadList.nash) | 2 | 1 | +| [IData](builtins/IData.nash) | 1 | 0 | +| [IfThenElse](builtins/IfThenElse.nash) | 4 | 0 | +| [IndexArray](builtins/IndexArray.nash) | 5 | 0 | +| [IndexByteString](builtins/IndexByteString.nash) | 6 | 2 | +| [InsertCoin](builtins/InsertCoin.nash) | 7 | 0 | +| [IntegerToByteString](builtins/IntegerToByteString.nash) | 8 | 4 | +| [Keccak256](builtins/Keccak256.nash) | 1 | 0 | +| [LengthOfArray](builtins/LengthOfArray.nash) | 2 | 0 | +| [LengthOfByteString](builtins/LengthOfByteString.nash) | 4 | 0 | +| [LessThanByteString](builtins/LessThanByteString.nash) | 3 | 0 | +| [LessThanEqualsByteString](builtins/LessThanEqualsByteString.nash) | 2 | 0 | +| [LessThanEqualsInteger](builtins/LessThanEqualsInteger.nash) | 4 | 2 | +| [LessThanInteger](builtins/LessThanInteger.nash) | 5 | 3 | +| [ListData](builtins/ListData.nash) | 1 | 0 | +| [ListToArray](builtins/ListToArray.nash) | 1 | 0 | +| [LookupCoin](builtins/LookupCoin.nash) | 3 | 0 | +| [MapData](builtins/MapData.nash) | 1 | 0 | +| [MkCons](builtins/MkCons.nash) | 3 | 0 | +| [MkNilData](builtins/MkNilData.nash) | 2 | 0 | +| [MkNilPairData](builtins/MkNilPairData.nash) | 2 | 0 | +| [MkPairData](builtins/MkPairData.nash) | 1 | 0 | +| [ModInteger](builtins/ModInteger.nash) | 6 | 4 | +| [MultiplyInteger](builtins/MultiplyInteger.nash) | 8 | 5 | +| [NullList](builtins/NullList.nash) | 2 | 0 | +| [OrByteString](builtins/OrByteString.nash) | 3 | 0 | +| [QuotientInteger](builtins/QuotientInteger.nash) | 5 | 4 | +| [ReadBit](builtins/ReadBit.nash) | 4 | 3 | +| [RemainderInteger](builtins/RemainderInteger.nash) | 6 | 4 | +| [ReplicateByte](builtins/ReplicateByte.nash) | 7 | 4 | +| [Ripemd160](builtins/Ripemd160.nash) | 2 | 0 | +| [RotateByteString](builtins/RotateByteString.nash) | 4 | 0 | +| [ScaleValue](builtins/ScaleValue.nash) | 5 | 0 | +| [SerialiseData](builtins/SerialiseData.nash) | 1 | 0 | +| [Sha2256](builtins/Sha2256.nash) | 2 | 0 | +| [Sha3256](builtins/Sha3256.nash) | 2 | 0 | +| [ShiftByteString](builtins/ShiftByteString.nash) | 5 | 0 | +| [SliceByteString](builtins/SliceByteString.nash) | 6 | 0 | +| [SndPair](builtins/SndPair.nash) | 1 | 0 | +| [SubtractInteger](builtins/SubtractInteger.nash) | 6 | 5 | +| [TailList](builtins/TailList.nash) | 3 | 1 | +| [Trace](builtins/Trace.nash) | 2 | 0 | +| [UnBData](builtins/UnBData.nash) | 3 | 0 | +| [UnConstrData](builtins/UnConstrData.nash) | 3 | 0 | +| [UnIData](builtins/UnIData.nash) | 3 | 0 | +| [UnListData](builtins/UnListData.nash) | 3 | 0 | +| [UnMapData](builtins/UnMapData.nash) | 3 | 0 | +| [UnValueData](builtins/UnValueData.nash) | 6 | 0 | +| [UnionValue](builtins/UnionValue.nash) | 3 | 0 | +| [ValueContains](builtins/ValueContains.nash) | 4 | 0 | +| [ValueData](builtins/ValueData.nash) | 1 | 0 | +| [VerifyEcdsaSecp256k1Signature](builtins/VerifyEcdsaSecp256k1Signature.nash) | 2 | 0 | +| [VerifyEd25519Signature](builtins/VerifyEd25519Signature.nash) | 4 | 0 | +| [VerifySchnorrSecp256k1Signature](builtins/VerifySchnorrSecp256k1Signature.nash) | 2 | 0 | +| [WriteBits](builtins/WriteBits.nash) | 6 | 3 | +| [XorByteString](builtins/XorByteString.nash) | 3 | 0 | +| [Bls12381G1MultiScalarMul](pending/builtins/Bls12381G1MultiScalarMul.nash) | 3 | 0 | +| [Bls12381G2MultiScalarMul](pending/builtins/Bls12381G2MultiScalarMul.nash) | 3 | 0 | diff --git a/proofs/builtins/AddInteger.nash b/proofs/builtins/AddInteger.nash new file mode 100644 index 00000000..2dca4367 --- /dev/null +++ b/proofs/builtins/AddInteger.nash @@ -0,0 +1,103 @@ +module AddInteger exposing (..) + + +import Builtin + + +{-| UPLC addition uses arbitrary-precision integers. Both native int values and +Data-represented Int values must preserve that behavior through Nash compilation. +The symbolic domains below contain every mathematical integer, with no bit bound. +-} +add : int -> int -> int +add = Builtin.addInteger + + +boxedAdd : Int -> Int -> Int +boxedAdd x y = Builtin.iData (add (Builtin.unIData x) (Builtin.unIData y)) + + +proof + import Proof + + prop "commutative" = + let + x via Proof.int + y via Proof.int + in + do + assert (add x y == add y x) + + prop "associative" = + let + x via Proof.int + y via Proof.int + z via Proof.int + in + do + assert (add (add x y) z == add x (add y z)) + + prop "left identity" = + let + x via Proof.int + in + do + assert (add 0 x == x) + + prop "right identity" = + let + x via Proof.int + in + do + assert (add x 0 == x) + + prop "additive inverse" = + let + x via Proof.int + in + do + assert (add x (Builtin.subtractInteger 0 x) == 0) + + prop "successor never wraps at any integer width" = + let + x via Proof.int + in + do + assert (add x 1 > x) + + prop "boxed successor never wraps at any integer width" = + let + x via Proof.integer + in + do + assert + ( + Builtin.unIData (boxedAdd x (Builtin.iData 1)) > + Builtin.unIData x + ) + + test "crosses signed 64-bit boundary" = + do + assert (add 9223372036854775807 1 == 9223372036854775808) + + test "crosses unsigned 128-bit boundary" = + do + let + before = + Builtin.byteStringToInteger + True + #"ffffffffffffffffffffffffffffffff" + + after = + Builtin.byteStringToInteger + True + #"0100000000000000000000000000000000" + in + assert (add before 1 == after) + + test "crosses negative signed 64-bit boundary" = + do + assert (add (-9223372036854775808) (-1) == -9223372036854775809) + + test "detects wrong opcode argument order or signedness" = + do + assert (add 17 5 == 22) diff --git a/proofs/builtins/AndByteString.nash b/proofs/builtins/AndByteString.nash new file mode 100644 index 00000000..63717c95 --- /dev/null +++ b/proofs/builtins/AndByteString.nash @@ -0,0 +1,35 @@ +module AndByteString exposing (..) + + +import Builtin + + +operation p0 p1 p2 = Builtin.andByteString p0 p1 p2 + + +proof + import Proof + + prop "commutative for both padding modes" = + let + pad via Proof.bool + x via Proof.bytes + y via Proof.bytes + in + do + assert (operation pad x y == operation pad y x) + + prop "self law" = + let + pad via Proof.bool + x via Proof.bytes + in + do + assert (operation pad x x == x) + + prop "truncating mode with empty input" = + let + x via Proof.bytes + in + do + assert (operation False x #"" == #"") diff --git a/proofs/builtins/AppendByteString.nash b/proofs/builtins/AppendByteString.nash new file mode 100644 index 00000000..f243f0e0 --- /dev/null +++ b/proofs/builtins/AppendByteString.nash @@ -0,0 +1,46 @@ +module AppendByteString exposing (..) + + +import Builtin + + +operation p0 p1 = Builtin.appendByteString p0 p1 + + +proof + import Proof + + prop "left and right identity" = + let + x via Proof.bytes + in + do + assert (operation #"" x == x && operation x #"" == x) + + prop "associative" = + let + x via Proof.bytes + y via Proof.bytes + z via Proof.bytes + in + do + assert + (operation (operation x y) z == operation x (operation y z)) + + prop "length is additive" = + let + x via Proof.bytes + y via Proof.bytes + in + do + assert + ( + Builtin.lengthOfByteString (operation x y) == + Builtin.addInteger + (Builtin.lengthOfByteString x) + (Builtin.lengthOfByteString y) + ) + + test "preserves byte order" = + do + assert (operation #"00ff" #"0180" == #"00ff0180") diff --git a/proofs/builtins/AppendString.nash b/proofs/builtins/AppendString.nash new file mode 100644 index 00000000..97c9161e --- /dev/null +++ b/proofs/builtins/AppendString.nash @@ -0,0 +1,32 @@ +module AppendString exposing (..) + + +import Builtin + + +operation p0 p1 = Builtin.appendString p0 p1 + + +proof + import Proof + + prop "left and right identity" = + let + x via Proof.string + in + do + assert (operation "" x == x && operation x "" == x) + + prop "associative" = + let + x via Proof.string + y via Proof.string + z via Proof.string + in + do + assert + (operation (operation x y) z == operation x (operation y z)) + + test "preserves Unicode characters" = + do + assert (operation "é" "λ" == "éλ") diff --git a/proofs/builtins/BData.nash b/proofs/builtins/BData.nash new file mode 100644 index 00000000..43a08d5f --- /dev/null +++ b/proofs/builtins/BData.nash @@ -0,0 +1,18 @@ +module BData exposing (..) + + +import Builtin + + +operation p0 = Builtin.bData p0 + + +proof + import Proof + + prop "bytes roundtrip" = + let + x via Proof.bytes + in + do + assert (Builtin.unBData (operation x) == x) diff --git a/proofs/builtins/Blake2b224.nash b/proofs/builtins/Blake2b224.nash new file mode 100644 index 00000000..9874e843 --- /dev/null +++ b/proofs/builtins/Blake2b224.nash @@ -0,0 +1,27 @@ +module Blake2b224 exposing (..) + + +import Builtin + + +{-| Digest length is the semantic obligation here. This does not claim collision resistance or correctness of the underlying cryptographic implementation. The pinned Lean model exposes an opaque hash without length lemmas; this obligation is pending there. -} +operation p0 = Builtin.blake2b_224 p0 + + +proof + import Proof + + prop "digest has the specified byte length" = + let + message via Proof.bytes + in + do + assert (Builtin.lengthOfByteString (operation message) == 28) + + test "empty message known answer detects the wrong hash primitive" = + do + assert + ( + operation #"" == + #"836cc68931c2e4e3e838602eca1902591d216837bafddfe6f0c8cb07" + ) diff --git a/proofs/builtins/Blake2b256.nash b/proofs/builtins/Blake2b256.nash new file mode 100644 index 00000000..96e5f905 --- /dev/null +++ b/proofs/builtins/Blake2b256.nash @@ -0,0 +1,27 @@ +module Blake2b256 exposing (..) + + +import Builtin + + +{-| Digest length is the semantic obligation here. This does not claim collision resistance or correctness of the underlying cryptographic implementation. The pinned Lean model exposes an opaque hash without length lemmas; this obligation is pending there. -} +operation p0 = Builtin.blake2b_256 p0 + + +proof + import Proof + + prop "digest has the specified byte length" = + let + message via Proof.bytes + in + do + assert (Builtin.lengthOfByteString (operation message) == 32) + + test "empty message known answer detects the wrong hash primitive" = + do + assert + ( + operation #"" == + #"0e5751c026e543b2e8ab2eb06099daa1d1e5df47778f7787faab45cdf12fe3a8" + ) diff --git a/proofs/builtins/Bls12381FinalVerify.nash b/proofs/builtins/Bls12381FinalVerify.nash new file mode 100644 index 00000000..8a22668b --- /dev/null +++ b/proofs/builtins/Bls12381FinalVerify.nash @@ -0,0 +1,32 @@ +module Bls12381FinalVerify exposing (..) + + +import Builtin + + +{-| Pairing consistency is pending opaque cryptographic model contracts. -} +operation p0 p1 = Builtin.bls12_381_finalVerify p0 p1 + + +g1 : bytes -> bls_g1 +g1 encoded = Builtin.bls12_381_g1_uncompress encoded + + +g2 : bytes -> bls_g2 +g2 encoded = Builtin.bls12_381_g2_uncompress encoded + + +ml : bytes -> bytes -> bls_mlr +ml x y = Builtin.bls12_381_millerLoop (g1 x) (g2 y) + + +proof + import Proof + + prop "reflexive on valid Miller loop results" = + let + x via Proof.bytes + y via Proof.bytes + in + do + Proof.returns (operation (ml x y) (ml x y)) (\holds -> holds) diff --git a/proofs/builtins/Bls12381G1Add.nash b/proofs/builtins/Bls12381G1Add.nash new file mode 100644 index 00000000..be316a35 --- /dev/null +++ b/proofs/builtins/Bls12381G1Add.nash @@ -0,0 +1,30 @@ +module Bls12381G1Add exposing (..) + + +import Builtin + + +{-| These group laws are partial correctness over successfully decoded points. They do not assert that arbitrary bytes decode to a valid point. The pinned Lean model has opaque group operations without algebraic lemmas; these obligations are pending there. -} +operation p0 p1 = Builtin.bls12_381_g1_add p0 p1 + + +point : bytes -> bls_g1 +point encoded = Builtin.bls12_381_g1_uncompress encoded + + +proof + import Proof + + prop "commutative on valid decoded points" = + let + x via Proof.bytes + y via Proof.bytes + in + do + Proof.returns + ( + Builtin.bls12_381_g1_equal + (operation (point x) (point y)) + (operation (point y) (point x)) + ) + (\holds -> holds) diff --git a/proofs/builtins/Bls12381G1Compress.nash b/proofs/builtins/Bls12381G1Compress.nash new file mode 100644 index 00000000..9ed8ebe2 --- /dev/null +++ b/proofs/builtins/Bls12381G1Compress.nash @@ -0,0 +1,25 @@ +module Bls12381G1Compress exposing (..) + + +import Builtin + + +{-| These group laws are partial correctness over successfully decoded points. They do not assert that arbitrary bytes decode to a valid point. The pinned Lean model has opaque group operations without algebraic lemmas; these obligations are pending there. -} +operation p0 = Builtin.bls12_381_g1_compress p0 + + +point : bytes -> bls_g1 +point encoded = Builtin.bls12_381_g1_uncompress encoded + + +proof + import Proof + + prop "fixed compressed width on valid points" = + let + x via Proof.bytes + in + do + Proof.returns + (Builtin.lengthOfByteString (operation (point x)) == 48) + (\holds -> holds) diff --git a/proofs/builtins/Bls12381G1Equal.nash b/proofs/builtins/Bls12381G1Equal.nash new file mode 100644 index 00000000..b73653a8 --- /dev/null +++ b/proofs/builtins/Bls12381G1Equal.nash @@ -0,0 +1,23 @@ +module Bls12381G1Equal exposing (..) + + +import Builtin + + +{-| These group laws are partial correctness over successfully decoded points. They do not assert that arbitrary bytes decode to a valid point. The pinned Lean model has opaque group operations without algebraic lemmas; these obligations are pending there. -} +operation p0 p1 = Builtin.bls12_381_g1_equal p0 p1 + + +point : bytes -> bls_g1 +point encoded = Builtin.bls12_381_g1_uncompress encoded + + +proof + import Proof + + prop "reflexive on valid points" = + let + x via Proof.bytes + in + do + Proof.returns (operation (point x) (point x)) (\holds -> holds) diff --git a/proofs/builtins/Bls12381G1HashToGroup.nash b/proofs/builtins/Bls12381G1HashToGroup.nash new file mode 100644 index 00000000..c21d79d5 --- /dev/null +++ b/proofs/builtins/Bls12381G1HashToGroup.nash @@ -0,0 +1,34 @@ +module Bls12381G1HashToGroup exposing (..) + + +import Builtin + + +{-| Uses a short fixed domain separation tag. Pending cryptographic contracts in the pinned Lean model. -} +operation p0 p1 = Builtin.bls12_381_g1_hashToGroup p0 p1 + + +proof + import Proof + + prop "hash to group yields a compressed point of fixed width" = + let + message via Proof.bytes + in + do + assert + ( + Builtin.lengthOfByteString + ( + Builtin.bls12_381_g1_compress + (operation message #"4e617368") + ) == + 48 + ) + + test "oversized domain separator rejects" fail = + do + let + _ = operation #"" (Builtin.replicateByte 256 0) + in + () diff --git a/proofs/builtins/Bls12381G1Neg.nash b/proofs/builtins/Bls12381G1Neg.nash new file mode 100644 index 00000000..adad4897 --- /dev/null +++ b/proofs/builtins/Bls12381G1Neg.nash @@ -0,0 +1,29 @@ +module Bls12381G1Neg exposing (..) + + +import Builtin + + +{-| These group laws are partial correctness over successfully decoded points. They do not assert that arbitrary bytes decode to a valid point. The pinned Lean model has opaque group operations without algebraic lemmas; these obligations are pending there. -} +operation p0 = Builtin.bls12_381_g1_neg p0 + + +point : bytes -> bls_g1 +point encoded = Builtin.bls12_381_g1_uncompress encoded + + +proof + import Proof + + prop "negation is an involution on valid points" = + let + x via Proof.bytes + in + do + Proof.returns + ( + Builtin.bls12_381_g1_equal + (operation (operation (point x))) + (point x) + ) + (\holds -> holds) diff --git a/proofs/builtins/Bls12381G1ScalarMul.nash b/proofs/builtins/Bls12381G1ScalarMul.nash new file mode 100644 index 00000000..f8e6b56a --- /dev/null +++ b/proofs/builtins/Bls12381G1ScalarMul.nash @@ -0,0 +1,29 @@ +module Bls12381G1ScalarMul exposing (..) + + +import Builtin + + +{-| These group laws are partial correctness over successfully decoded points. They do not assert that arbitrary bytes decode to a valid point. The pinned Lean model has opaque group operations without algebraic lemmas; these obligations are pending there. -} +operation p0 p1 = Builtin.bls12_381_g1_scalarMul p0 p1 + + +point : bytes -> bls_g1 +point encoded = Builtin.bls12_381_g1_uncompress encoded + + +proof + import Proof + + prop "scalar one is the identity on valid points" = + let + x via Proof.bytes + in + do + Proof.returns + ( + Builtin.bls12_381_g1_equal + (operation 1 (point x)) + (point x) + ) + (\holds -> holds) diff --git a/proofs/builtins/Bls12381G1Uncompress.nash b/proofs/builtins/Bls12381G1Uncompress.nash new file mode 100644 index 00000000..d8a9bc6a --- /dev/null +++ b/proofs/builtins/Bls12381G1Uncompress.nash @@ -0,0 +1,47 @@ +module Bls12381G1Uncompress exposing (..) + + +import Builtin + + +{-| These group laws are partial correctness over successfully decoded points. They do not assert that arbitrary bytes decode to a valid point. The pinned Lean model has opaque group operations without algebraic lemmas; these obligations are pending there. -} +operation p0 = Builtin.bls12_381_g1_uncompress p0 + + +proof + import Proof + + prop "canonical compressed roundtrip on valid encodings" = + let + x via Proof.bytes + in + do + Proof.returns + (Builtin.bls12_381_g1_compress (operation x) == x) + (\holds -> holds) + + test "empty encoding rejects" fail = + do + let + _ = operation #"" + in + () + + test "wrong compressed width rejects" fail = + do + let + _ = operation #"00" + in + () + + test "canonical infinity is accepted" = + do + assert + ( + Builtin.bls12_381_g1_compress + ( + operation + #"c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + ) == + #"c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + ) diff --git a/proofs/builtins/Bls12381G2Add.nash b/proofs/builtins/Bls12381G2Add.nash new file mode 100644 index 00000000..7fb74adf --- /dev/null +++ b/proofs/builtins/Bls12381G2Add.nash @@ -0,0 +1,30 @@ +module Bls12381G2Add exposing (..) + + +import Builtin + + +{-| These group laws are partial correctness over successfully decoded points. They do not assert that arbitrary bytes decode to a valid point. The pinned Lean model has opaque group operations without algebraic lemmas; these obligations are pending there. -} +operation p0 p1 = Builtin.bls12_381_g2_add p0 p1 + + +point : bytes -> bls_g2 +point encoded = Builtin.bls12_381_g2_uncompress encoded + + +proof + import Proof + + prop "commutative on valid decoded points" = + let + x via Proof.bytes + y via Proof.bytes + in + do + Proof.returns + ( + Builtin.bls12_381_g2_equal + (operation (point x) (point y)) + (operation (point y) (point x)) + ) + (\holds -> holds) diff --git a/proofs/builtins/Bls12381G2Compress.nash b/proofs/builtins/Bls12381G2Compress.nash new file mode 100644 index 00000000..61d3faa1 --- /dev/null +++ b/proofs/builtins/Bls12381G2Compress.nash @@ -0,0 +1,25 @@ +module Bls12381G2Compress exposing (..) + + +import Builtin + + +{-| These group laws are partial correctness over successfully decoded points. They do not assert that arbitrary bytes decode to a valid point. The pinned Lean model has opaque group operations without algebraic lemmas; these obligations are pending there. -} +operation p0 = Builtin.bls12_381_g2_compress p0 + + +point : bytes -> bls_g2 +point encoded = Builtin.bls12_381_g2_uncompress encoded + + +proof + import Proof + + prop "fixed compressed width on valid points" = + let + x via Proof.bytes + in + do + Proof.returns + (Builtin.lengthOfByteString (operation (point x)) == 96) + (\holds -> holds) diff --git a/proofs/builtins/Bls12381G2Equal.nash b/proofs/builtins/Bls12381G2Equal.nash new file mode 100644 index 00000000..59e089d2 --- /dev/null +++ b/proofs/builtins/Bls12381G2Equal.nash @@ -0,0 +1,23 @@ +module Bls12381G2Equal exposing (..) + + +import Builtin + + +{-| These group laws are partial correctness over successfully decoded points. They do not assert that arbitrary bytes decode to a valid point. The pinned Lean model has opaque group operations without algebraic lemmas; these obligations are pending there. -} +operation p0 p1 = Builtin.bls12_381_g2_equal p0 p1 + + +point : bytes -> bls_g2 +point encoded = Builtin.bls12_381_g2_uncompress encoded + + +proof + import Proof + + prop "reflexive on valid points" = + let + x via Proof.bytes + in + do + Proof.returns (operation (point x) (point x)) (\holds -> holds) diff --git a/proofs/builtins/Bls12381G2HashToGroup.nash b/proofs/builtins/Bls12381G2HashToGroup.nash new file mode 100644 index 00000000..bb9c4889 --- /dev/null +++ b/proofs/builtins/Bls12381G2HashToGroup.nash @@ -0,0 +1,34 @@ +module Bls12381G2HashToGroup exposing (..) + + +import Builtin + + +{-| Uses a short fixed domain separation tag. Pending cryptographic contracts in the pinned Lean model. -} +operation p0 p1 = Builtin.bls12_381_g2_hashToGroup p0 p1 + + +proof + import Proof + + prop "hash to group yields a compressed point of fixed width" = + let + message via Proof.bytes + in + do + assert + ( + Builtin.lengthOfByteString + ( + Builtin.bls12_381_g2_compress + (operation message #"4e617368") + ) == + 96 + ) + + test "oversized domain separator rejects" fail = + do + let + _ = operation #"" (Builtin.replicateByte 256 0) + in + () diff --git a/proofs/builtins/Bls12381G2Neg.nash b/proofs/builtins/Bls12381G2Neg.nash new file mode 100644 index 00000000..e7eb63d4 --- /dev/null +++ b/proofs/builtins/Bls12381G2Neg.nash @@ -0,0 +1,29 @@ +module Bls12381G2Neg exposing (..) + + +import Builtin + + +{-| These group laws are partial correctness over successfully decoded points. They do not assert that arbitrary bytes decode to a valid point. The pinned Lean model has opaque group operations without algebraic lemmas; these obligations are pending there. -} +operation p0 = Builtin.bls12_381_g2_neg p0 + + +point : bytes -> bls_g2 +point encoded = Builtin.bls12_381_g2_uncompress encoded + + +proof + import Proof + + prop "negation is an involution on valid points" = + let + x via Proof.bytes + in + do + Proof.returns + ( + Builtin.bls12_381_g2_equal + (operation (operation (point x))) + (point x) + ) + (\holds -> holds) diff --git a/proofs/builtins/Bls12381G2ScalarMul.nash b/proofs/builtins/Bls12381G2ScalarMul.nash new file mode 100644 index 00000000..216ce134 --- /dev/null +++ b/proofs/builtins/Bls12381G2ScalarMul.nash @@ -0,0 +1,29 @@ +module Bls12381G2ScalarMul exposing (..) + + +import Builtin + + +{-| These group laws are partial correctness over successfully decoded points. They do not assert that arbitrary bytes decode to a valid point. The pinned Lean model has opaque group operations without algebraic lemmas; these obligations are pending there. -} +operation p0 p1 = Builtin.bls12_381_g2_scalarMul p0 p1 + + +point : bytes -> bls_g2 +point encoded = Builtin.bls12_381_g2_uncompress encoded + + +proof + import Proof + + prop "scalar one is the identity on valid points" = + let + x via Proof.bytes + in + do + Proof.returns + ( + Builtin.bls12_381_g2_equal + (operation 1 (point x)) + (point x) + ) + (\holds -> holds) diff --git a/proofs/builtins/Bls12381G2Uncompress.nash b/proofs/builtins/Bls12381G2Uncompress.nash new file mode 100644 index 00000000..19f98ea2 --- /dev/null +++ b/proofs/builtins/Bls12381G2Uncompress.nash @@ -0,0 +1,47 @@ +module Bls12381G2Uncompress exposing (..) + + +import Builtin + + +{-| These group laws are partial correctness over successfully decoded points. They do not assert that arbitrary bytes decode to a valid point. The pinned Lean model has opaque group operations without algebraic lemmas; these obligations are pending there. -} +operation p0 = Builtin.bls12_381_g2_uncompress p0 + + +proof + import Proof + + prop "canonical compressed roundtrip on valid encodings" = + let + x via Proof.bytes + in + do + Proof.returns + (Builtin.bls12_381_g2_compress (operation x) == x) + (\holds -> holds) + + test "empty encoding rejects" fail = + do + let + _ = operation #"" + in + () + + test "wrong compressed width rejects" fail = + do + let + _ = operation #"00" + in + () + + test "canonical infinity is accepted" = + do + assert + ( + Builtin.bls12_381_g2_compress + ( + operation + #"c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + ) == + #"c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + ) diff --git a/proofs/builtins/Bls12381MillerLoop.nash b/proofs/builtins/Bls12381MillerLoop.nash new file mode 100644 index 00000000..8416a69f --- /dev/null +++ b/proofs/builtins/Bls12381MillerLoop.nash @@ -0,0 +1,34 @@ +module Bls12381MillerLoop exposing (..) + + +import Builtin + + +{-| Basic pairing consistency, pending opaque cryptographic model contracts. -} +operation p0 p1 = Builtin.bls12_381_millerLoop p0 p1 + + +g1 : bytes -> bls_g1 +g1 encoded = Builtin.bls12_381_g1_uncompress encoded + + +g2 : bytes -> bls_g2 +g2 encoded = Builtin.bls12_381_g2_uncompress encoded + + +proof + import Proof + + prop "pairing the same points gives equivalent results" = + let + x via Proof.bytes + y via Proof.bytes + in + do + Proof.returns + ( + Builtin.bls12_381_finalVerify + (operation (g1 x) (g2 y)) + (operation (g1 x) (g2 y)) + ) + (\holds -> holds) diff --git a/proofs/builtins/Bls12381MulMlResult.nash b/proofs/builtins/Bls12381MulMlResult.nash new file mode 100644 index 00000000..c27c28a5 --- /dev/null +++ b/proofs/builtins/Bls12381MulMlResult.nash @@ -0,0 +1,40 @@ +module Bls12381MulMlResult exposing (..) + + +import Builtin + + +{-| Pairing algebra is pending opaque cryptographic model contracts. -} +operation p0 p1 = Builtin.bls12_381_mulMlResult p0 p1 + + +g1 : bytes -> bls_g1 +g1 encoded = Builtin.bls12_381_g1_uncompress encoded + + +g2 : bytes -> bls_g2 +g2 encoded = Builtin.bls12_381_g2_uncompress encoded + + +ml : bytes -> bytes -> bls_mlr +ml x y = Builtin.bls12_381_millerLoop (g1 x) (g2 y) + + +proof + import Proof + + prop "commutative under final verification" = + let + leftG1 via Proof.bytes + leftG2 via Proof.bytes + rightG1 via Proof.bytes + rightG2 via Proof.bytes + in + do + Proof.returns + ( + Builtin.bls12_381_finalVerify + (operation (ml leftG1 leftG2) (ml rightG1 rightG2)) + (operation (ml rightG1 rightG2) (ml leftG1 leftG2)) + ) + (\holds -> holds) diff --git a/proofs/builtins/ByteStringToInteger.nash b/proofs/builtins/ByteStringToInteger.nash new file mode 100644 index 00000000..7c63f865 --- /dev/null +++ b/proofs/builtins/ByteStringToInteger.nash @@ -0,0 +1,47 @@ +module ByteStringToInteger exposing (..) + + +import Builtin + + +operation p0 p1 = Builtin.byteStringToInteger p0 p1 + + +proof + import Proof + + prop "empty bytes mean zero" = + let + bigEndian via Proof.bool + in + do + assert (operation bigEndian #"" == 0) + + test "endianness" = + do + assert + ( + operation True #"0102" == + 258 && + operation False #"0102" == + 513 + ) + + prop "arbitrary width conversion stays nonnegative" = + let + bigEndian via Proof.bool + x via Proof.bytes + in + do + assert (operation bigEndian x >= 0) + + prop "leading zero does not alter big endian value" = + let + x via Proof.bytes + in + do + assert + ( + operation True (Builtin.consByteString 0 x) == + operation True x + ) diff --git a/proofs/builtins/ChooseData.nash b/proofs/builtins/ChooseData.nash new file mode 100644 index 00000000..b7222acc --- /dev/null +++ b/proofs/builtins/ChooseData.nash @@ -0,0 +1,65 @@ +module ChooseData exposing (..) + + +import Builtin + + +operation p0 p1 p2 p3 p4 p5 = Builtin.chooseData p0 p1 p2 p3 p4 p5 + + +emptyMap : Map Data Data +emptyMap = Builtin.mapData [] + + +emptyList : List Data +emptyList = Builtin.listData [] + + +proof + import Proof + + test "selects the constructor branch" = + do + assert (operation (Builtin.constrData 0 []) 10 20 30 40 50 == 10) + + test "selects the map branch" = + do + assert (operation (Primitive.coerce emptyMap) 10 20 30 40 50 == 20) + + test "selects the list branch" = + do + assert (operation (Primitive.coerce emptyList) 10 20 30 40 50 == 30) + + prop "selects the integer branch" = + let + x via Proof.int + in + do + assert + ( + operation + (Primitive.coerce (Builtin.iData x)) + 10 + 20 + 30 + 40 + 50 == + 40 + ) + + prop "selects the bytes branch" = + let + x via Proof.bytes + in + do + assert + ( + operation + (Primitive.coerce (Builtin.bData x)) + 10 + 20 + 30 + 40 + 50 == + 50 + ) diff --git a/proofs/builtins/ChooseList.nash b/proofs/builtins/ChooseList.nash new file mode 100644 index 00000000..4e04e741 --- /dev/null +++ b/proofs/builtins/ChooseList.nash @@ -0,0 +1,39 @@ +module ChooseList exposing (..) + + +import Builtin + + +operation : list Data -> int -> int -> int +operation p0 p1 p2 = Builtin.chooseList p0 p1 p2 + + +proof + import Proof + + prop "empty list chooses the empty branch" = + let + x via Proof.int + y via Proof.int + in + do + assert (operation [] x y == x) + + prop "nonempty list chooses the nonempty branch" = + let + item via Proof.data + x via Proof.int + y via Proof.int + in + do + assert (operation [ item ] x y == y) + + test "detects wrong opcode argument order or signedness" = + do + assert + ( + operation [] 17 5 == + 17 && + operation [ Builtin.constrData 0 [] ] 17 5 == + 5 + ) diff --git a/proofs/builtins/ChooseUnit.nash b/proofs/builtins/ChooseUnit.nash new file mode 100644 index 00000000..4eba303c --- /dev/null +++ b/proofs/builtins/ChooseUnit.nash @@ -0,0 +1,22 @@ +module ChooseUnit exposing (..) + + +import Builtin + + +operation p0 p1 = Builtin.chooseUnit p0 p1 + + +proof + import Proof + + prop "returns the second argument" = + let + x via Proof.data + in + do + assert (operation () x == x) + + test "detects wrong opcode argument order or signedness" = + do + assert (operation () 17 == 17) diff --git a/proofs/builtins/ComplementByteString.nash b/proofs/builtins/ComplementByteString.nash new file mode 100644 index 00000000..638ceb41 --- /dev/null +++ b/proofs/builtins/ComplementByteString.nash @@ -0,0 +1,33 @@ +module ComplementByteString exposing (..) + + +import Builtin + + +operation p0 = Builtin.complementByteString p0 + + +proof + import Proof + + prop "involution" = + let + x via Proof.bytes + in + do + assert (operation (operation x) == x) + + prop "preserves length" = + let + x via Proof.bytes + in + do + assert + ( + Builtin.lengthOfByteString (operation x) == + Builtin.lengthOfByteString x + ) + + test "complements every bit" = + do + assert (operation #"00ff0f" == #"ff00f0") diff --git a/proofs/builtins/ConsByteString.nash b/proofs/builtins/ConsByteString.nash new file mode 100644 index 00000000..87f0f382 --- /dev/null +++ b/proofs/builtins/ConsByteString.nash @@ -0,0 +1,97 @@ +module ConsByteString exposing (..) + + +import Builtin + + +{-| Plutus V3 uses strict byte validation; V1/V2 reduce the byte modulo 256. -} +operation p0 p1 = Builtin.consByteString p0 p1 + + +proof + import Proof + + prop "prepends zero and preserves length" = + let + x via Proof.bytes + in + do + assert + ( + Builtin.indexByteString (operation 0 x) 0 == + 0 && + Builtin.lengthOfByteString (operation 0 x) == + Builtin.addInteger (Builtin.lengthOfByteString x) 1 + ) + + prop "valid byte survives at the head" = + let + n via Proof.int + x via Proof.bytes + in + do + assert + ( + if n >= 0 && n <= 255 then + Builtin.indexByteString (operation n x) 0 == n + else + True + ) + + prop "negative byte rejects in Plutus V3" fail = + let + x via Proof.bytes + in + do + let + _ = operation (-1) x + in + () + + prop "byte above 255 rejects in Plutus V3" fail = + let + x via Proof.bytes + in + do + let + _ = operation 256 x + in + () + + prop "all negative byte values reject" fail = + let + n via Proof.int + x via Proof.bytes + in + do + let + _ = + operation + ( + if n < 0 then + n + else + -1 - n + ) + x + in + () + + prop "all byte values above 255 reject" fail = + let + n via Proof.int + x via Proof.bytes + in + do + let + _ = + operation + ( + if n > 255 then + n + else + 256 + ) + x + in + () diff --git a/proofs/builtins/ConstrData.nash b/proofs/builtins/ConstrData.nash new file mode 100644 index 00000000..771500be --- /dev/null +++ b/proofs/builtins/ConstrData.nash @@ -0,0 +1,33 @@ +module ConstrData exposing (..) + + +import Builtin + + +operation p0 p1 = Builtin.constrData p0 p1 + + +proof + import Proof + + prop "constructor tag and fields survive roundtrip" = + let + tag via Proof.int + x via Proof.data + in + do + assert + ( + Builtin.fstPair + (Builtin.unConstrData (operation tag [ x ])) == + tag && + Builtin.headList + ( + Builtin.sndPair + ( + Builtin.unConstrData + (operation tag [ x ]) + ) + ) == + x + ) diff --git a/proofs/builtins/CountSetBits.nash b/proofs/builtins/CountSetBits.nash new file mode 100644 index 00000000..4aca83c7 --- /dev/null +++ b/proofs/builtins/CountSetBits.nash @@ -0,0 +1,38 @@ +module CountSetBits exposing (..) + + +import Builtin + + +operation p0 = Builtin.countSetBits p0 + + +proof + import Proof + + prop "count is bounded by the bit width" = + let + x via Proof.bytes + in + do + assert + ( + operation x >= + 0 && + operation x <= + Builtin.multiplyInteger + 8 + (Builtin.lengthOfByteString x) + ) + + test "known counts" = + do + assert + ( + operation #"00" == + 0 && + operation #"ff" == + 8 && + operation #"0103" == + 3 + ) diff --git a/proofs/builtins/DecodeUtf8.nash b/proofs/builtins/DecodeUtf8.nash new file mode 100644 index 00000000..fba9ee61 --- /dev/null +++ b/proofs/builtins/DecodeUtf8.nash @@ -0,0 +1,57 @@ +module DecodeUtf8 exposing (..) + + +import Builtin + + +operation p0 = Builtin.decodeUtf8 p0 + + +proof + import Proof + + prop "inverse on encoded strings" = + let + text via Proof.string + in + do + assert (operation (Builtin.encodeUtf8 text) == text) + + test "non ASCII decoding" = + do + assert (operation #"c3a9cebb" == "éλ") + + test "invalid UTF8 rejects" fail = + do + let + _ = operation #"ff" + in + () + + test "truncated UTF8 rejects" fail = + do + let + _ = operation #"c3" + in + () + + test "overlong UTF8 is rejected" fail = + do + let + _ = operation #"c080" + in + () + + test "UTF8 surrogate codepoints are rejected" fail = + do + let + _ = operation #"eda080" + in + () + + test "codepoint above Unicode maximum is rejected" fail = + do + let + _ = operation #"f4908080" + in + () diff --git a/proofs/builtins/DivideInteger.nash b/proofs/builtins/DivideInteger.nash new file mode 100644 index 00000000..3b7e816a --- /dev/null +++ b/proofs/builtins/DivideInteger.nash @@ -0,0 +1,54 @@ +module DivideInteger exposing (..) + + +import Builtin + + +operation p0 p1 = Builtin.divideInteger p0 p1 + + +proof + import Proof + + prop "division by one" = + let + x via Proof.int + in + do + assert (operation x 1 == x) + + prop "quotient and remainder reconstruct the dividend" = + let + x via Proof.int + y via Proof.int + in + do + assert + ( + if y == 0 then + True + else + Builtin.addInteger + (Builtin.multiplyInteger (operation x y) y) + (Builtin.modInteger x y) == + x + ) + + test "negative rounding convention" = + do + assert (operation (-7) 3 == -3) + + prop "zero divisor rejects every dividend" fail = + let + x via Proof.int + in + do + let + _ = operation x 0 + in + () + + test "detects wrong opcode argument order or signedness" = + do + assert + (operation (-9223372036854775808) (-1) == 9223372036854775808) diff --git a/proofs/builtins/DropList.nash b/proofs/builtins/DropList.nash new file mode 100644 index 00000000..926eaaf0 --- /dev/null +++ b/proofs/builtins/DropList.nash @@ -0,0 +1,48 @@ +module DropList exposing (..) + + +import Builtin + + +operation p0 p1 = Builtin.dropList p0 p1 + + +proof + import Proof + + prop "dropping zero preserves the head" = + let + x via Proof.data + in + do + assert (Builtin.headList (operation 0 [ x ]) == x) + + prop "dropping one preserves the next element" = + let + x via Proof.data + y via Proof.data + in + do + assert (Builtin.headList (operation 1 [ x, y ]) == y) + + prop "dropping the full list gives empty" = + let + x via Proof.data + y via Proof.data + in + do + assert (Builtin.nullList (operation 2 [ x, y ])) + + prop "negative count preserves the list" = + let + x via Proof.data + in + do + assert (Builtin.headList (operation (-1) [ x ]) == x) + + prop "count beyond length gives empty" = + let + x via Proof.data + in + do + assert (Builtin.nullList (operation 1000000 [ x ])) diff --git a/proofs/builtins/EncodeUtf8.nash b/proofs/builtins/EncodeUtf8.nash new file mode 100644 index 00000000..b7b72523 --- /dev/null +++ b/proofs/builtins/EncodeUtf8.nash @@ -0,0 +1,38 @@ +module EncodeUtf8 exposing (..) + + +import Builtin + + +operation p0 = Builtin.encodeUtf8 p0 + + +proof + import Proof + + prop "decode after encoding" = + let + text via Proof.string + in + do + assert (Builtin.decodeUtf8 (operation text) == text) + + prop "append is preserved" = + let + x via Proof.string + y via Proof.string + in + do + assert + ( + operation (Builtin.appendString x y) == + Builtin.appendByteString (operation x) (operation y) + ) + + test "non ASCII encoding" = + do + assert (operation "éλ" == #"c3a9cebb") + + test "embedded zero is preserved" = + do + assert (operation "a\u{0000}b" == #"610062") diff --git a/proofs/builtins/EqualsByteString.nash b/proofs/builtins/EqualsByteString.nash new file mode 100644 index 00000000..7e74704c --- /dev/null +++ b/proofs/builtins/EqualsByteString.nash @@ -0,0 +1,46 @@ +module EqualsByteString exposing (..) + + +import Builtin + + +operation p0 p1 = Builtin.equalsByteString p0 p1 + + +proof + import Proof + + prop "reflexive" = + let + x via Proof.bytes + in + do + assert (operation x x) + + prop "symmetric" = + let + x via Proof.bytes + y via Proof.bytes + in + do + assert (operation x y == operation y x) + + prop "transitive" = + let + x via Proof.bytes + y via Proof.bytes + z via Proof.bytes + in + do + assert + ( + if operation x y && operation y z then + operation x z + else + True + ) + + test "detects wrong opcode argument order or signedness" = + do + assert + (not (operation #"00" #"0000") && not (operation #"00" #"01")) diff --git a/proofs/builtins/EqualsData.nash b/proofs/builtins/EqualsData.nash new file mode 100644 index 00000000..ad42c4fd --- /dev/null +++ b/proofs/builtins/EqualsData.nash @@ -0,0 +1,53 @@ +module EqualsData exposing (..) + + +import Builtin + + +operation p0 p1 = Builtin.equalsData p0 p1 + + +proof + import Proof + + prop "reflexive" = + let + x via Proof.data + in + do + assert (operation x x) + + prop "symmetric" = + let + x via Proof.data + y via Proof.data + in + do + assert (operation x y == operation y x) + + prop "transitive" = + let + x via Proof.data + y via Proof.data + z via Proof.data + in + do + assert + ( + if operation x y && operation y z then + operation x z + else + True + ) + + test "detects wrong opcode argument order or signedness" = + do + assert + ( + not + ( + operation + (Primitive.coerce (Builtin.iData 0)) + (Primitive.coerce (Builtin.bData #"00")) + ) + ) diff --git a/proofs/builtins/EqualsInteger.nash b/proofs/builtins/EqualsInteger.nash new file mode 100644 index 00000000..c355cb19 --- /dev/null +++ b/proofs/builtins/EqualsInteger.nash @@ -0,0 +1,51 @@ +module EqualsInteger exposing (..) + + +import Builtin + + +operation p0 p1 = Builtin.equalsInteger p0 p1 + + +proof + import Proof + + prop "reflexive" = + let + x via Proof.int + in + do + assert (operation x x) + + prop "symmetric" = + let + x via Proof.int + y via Proof.int + in + do + assert (operation x y == operation y x) + + prop "equivalent to zero difference" = + let + x via Proof.int + y via Proof.int + in + do + assert + ( + operation x y == + Builtin.equalsInteger + (Builtin.subtractInteger x y) + 0 + ) + + prop "successive integers are distinct at every width" = + let + x via Proof.int + in + do + assert (not (operation x (Builtin.addInteger x 1))) + + test "detects wrong opcode argument order or signedness" = + do + assert (not (operation (-1) 1)) diff --git a/proofs/builtins/EqualsString.nash b/proofs/builtins/EqualsString.nash new file mode 100644 index 00000000..92472856 --- /dev/null +++ b/proofs/builtins/EqualsString.nash @@ -0,0 +1,45 @@ +module EqualsString exposing (..) + + +import Builtin + + +operation p0 p1 = Builtin.equalsString p0 p1 + + +proof + import Proof + + prop "reflexive" = + let + x via Proof.string + in + do + assert (operation x x) + + prop "symmetric" = + let + x via Proof.string + y via Proof.string + in + do + assert (operation x y == operation y x) + + prop "transitive" = + let + x via Proof.string + y via Proof.string + z via Proof.string + in + do + assert + ( + if operation x y && operation y z then + operation x z + else + True + ) + + test "detects wrong opcode argument order or signedness" = + do + assert (not (operation "é" "e") && not (operation "a" "ab")) diff --git a/proofs/builtins/ExpModInteger.nash b/proofs/builtins/ExpModInteger.nash new file mode 100644 index 00000000..36d01537 --- /dev/null +++ b/proofs/builtins/ExpModInteger.nash @@ -0,0 +1,47 @@ +module ExpModInteger exposing (..) + + +import Builtin + + +operation p0 p1 p2 = Builtin.expModInteger p0 p1 p2 + + +proof + import Proof + + prop "exponent zero gives the modular identity" = + let + x via Proof.int + in + do + assert (operation x 0 7 == 1) + + test "known modular power" = + do + assert (operation 2 10 7 == 2) + + test "zero modulus rejects" fail = + do + let + _ = operation 2 10 0 + in + () + + test "negative modulus rejects" fail = + do + let + _ = operation 2 3 (-7) + in + () + + test "missing modular inverse rejects" fail = + do + let + _ = operation 2 (-1) 4 + in + () + + test "negative exponent uses modular inverse" = + do + assert (operation 2 (-1) 7 == 4) diff --git a/proofs/builtins/FindFirstSetBit.nash b/proofs/builtins/FindFirstSetBit.nash new file mode 100644 index 00000000..d5fe0b34 --- /dev/null +++ b/proofs/builtins/FindFirstSetBit.nash @@ -0,0 +1,30 @@ +module FindFirstSetBit exposing (..) + + +import Builtin + + +operation p0 = Builtin.findFirstSetBit p0 + + +proof + import Proof + + prop "minus one exactly for all zero bits" = + let + x via Proof.bytes + in + do + assert ((operation x == -1) == (Builtin.countSetBits x == 0)) + + test "least significant indexing" = + do + assert + ( + operation #"80" == + 7 && + operation #"01" == + 0 && + operation #"00" == + -1 + ) diff --git a/proofs/builtins/FstPair.nash b/proofs/builtins/FstPair.nash new file mode 100644 index 00000000..ca279a5f --- /dev/null +++ b/proofs/builtins/FstPair.nash @@ -0,0 +1,19 @@ +module FstPair exposing (..) + + +import Builtin + + +operation p0 = Builtin.fstPair p0 + + +proof + import Proof + + prop "projects the correct component" = + let + x via Proof.data + y via Proof.data + in + do + assert (operation (Builtin.mkPairData x y) == x) diff --git a/proofs/builtins/HeadList.nash b/proofs/builtins/HeadList.nash new file mode 100644 index 00000000..e66197fd --- /dev/null +++ b/proofs/builtins/HeadList.nash @@ -0,0 +1,27 @@ +module HeadList exposing (..) + + +import Builtin + + +operation : list Data -> Data +operation p0 = Builtin.headList p0 + + +proof + import Proof + + prop "head of a nonempty list" = + let + x via Proof.data + y via Proof.data + in + do + assert (operation [ x, y ] == x) + + test "empty list rejects" fail = + do + let + _ = operation [] + in + () diff --git a/proofs/builtins/IData.nash b/proofs/builtins/IData.nash new file mode 100644 index 00000000..3329d0a7 --- /dev/null +++ b/proofs/builtins/IData.nash @@ -0,0 +1,18 @@ +module IData exposing (..) + + +import Builtin + + +operation p0 = Builtin.iData p0 + + +proof + import Proof + + prop "integer roundtrip has no width bound" = + let + x via Proof.int + in + do + assert (Builtin.unIData (operation x) == x) diff --git a/proofs/builtins/IfThenElse.nash b/proofs/builtins/IfThenElse.nash new file mode 100644 index 00000000..bf86d835 --- /dev/null +++ b/proofs/builtins/IfThenElse.nash @@ -0,0 +1,62 @@ +module IfThenElse exposing (..) + + +import Builtin + + +operation p0 p1 p2 = Builtin.ifThenElse p0 p1 p2 + + +badArgument : int +badArgument = fail + + +proof + import Proof + + prop "selects the branch indicated by the condition" = + let + condition via Proof.bool + x via Proof.int + y via Proof.int + in + do + assert + ( + operation condition x y == + ( + if condition then + x + else + y + ) + ) + + prop "selects arbitrary Data values" = + let + condition via Proof.bool + x via Proof.data + y via Proof.data + in + do + assert + ( + operation condition x y == + ( + if condition then + x + else + y + ) + ) + + test "unselected arguments are still evaluated" fail = + do + let + _ = operation True 1 badArgument + in + () + + test "detects wrong opcode argument order or signedness" = + do + assert (operation True 17 5 == 17 && operation False 17 5 == 5) diff --git a/proofs/builtins/IndexArray.nash b/proofs/builtins/IndexArray.nash new file mode 100644 index 00000000..3e6a003f --- /dev/null +++ b/proofs/builtins/IndexArray.nash @@ -0,0 +1,66 @@ +module IndexArray exposing (..) + + +import Builtin + + +{-| The pinned PlutusCoreBlaster revision has no array/value model. These are +executable source obligations; Lean verification is pending model support. -} +operation p0 p1 = Builtin.indexArray p0 p1 + + +one : array int +one = Builtin.listToArray [ 1 ] + + +proof + import Proof + + prop "selects each position" = + let + x via Proof.data + y via Proof.data + in + do + assert + ( + operation (Builtin.listToArray [ x, y ]) 0 == + x && + operation (Builtin.listToArray [ x, y ]) 1 == + y + ) + + test "negative index rejects" fail = + do + let + _ = operation one (-1) + in + () + + test "index equal to length rejects" fail = + do + let + _ = operation one 1 + in + () + + test "index must not truncate to a machine word" fail = + do + let + _ = operation one 18446744073709551616 + in + () + + test "index above 128 bits rejects without host overflow" fail = + do + let + _ = + operation + one + ( + Builtin.byteStringToInteger + True + #"0100000000000000000000000000000000" + ) + in + () diff --git a/proofs/builtins/IndexByteString.nash b/proofs/builtins/IndexByteString.nash new file mode 100644 index 00000000..25799ce3 --- /dev/null +++ b/proofs/builtins/IndexByteString.nash @@ -0,0 +1,73 @@ +module IndexByteString exposing (..) + + +import Builtin + + +operation p0 p1 = Builtin.indexByteString p0 p1 + + +proof + import Proof + + prop "valid indices return a byte" = + let + x via Proof.bytes + index via Proof.int + in + do + assert + ( + if index >= 0 && index < Builtin.lengthOfByteString x then + operation x index >= 0 && operation x index <= 255 + else + True + ) + + prop "cons supplies index zero" = + let + x via Proof.bytes + in + do + assert (operation (Builtin.consByteString 255 x) 0 == 255) + + prop "negative index rejects" fail = + let + x via Proof.bytes + in + do + let + _ = operation x (-1) + in + () + + prop "index equal to length rejects" fail = + let + x via Proof.bytes + in + do + let + _ = operation x (Builtin.lengthOfByteString x) + in + () + + test "index must not truncate to a machine word" fail = + do + let + _ = operation #"ff" 18446744073709551616 + in + () + + test "index above 128 bits rejects without host overflow" fail = + do + let + _ = + operation + #"ff" + ( + Builtin.byteStringToInteger + True + #"0100000000000000000000000000000000" + ) + in + () diff --git a/proofs/builtins/InsertCoin.nash b/proofs/builtins/InsertCoin.nash new file mode 100644 index 00000000..da0302aa --- /dev/null +++ b/proofs/builtins/InsertCoin.nash @@ -0,0 +1,106 @@ +module InsertCoin exposing (..) + + +import Builtin +import Cardano.Value + + +{-| The pinned PlutusCoreBlaster revision has no array/value model. These are +executable source obligations; Lean verification is pending model support. -} +operation p0 p1 p2 p3 = Builtin.insertCoin p0 p1 p2 p3 + + +empty : value +empty = Cardano.Value.empty + + +coin : int -> value +coin amount = Builtin.insertCoin #"" #"" amount empty + + +proof + import Proof + + prop "insert then lookup" = + let + amount via Proof.int + in + do + assert + ( + if amount >= 0 && amount <= 1000000 then + Builtin.lookupCoin + #"" + #"" + (operation #"" #"" amount empty) == + amount + else + True + ) + + test "zero quantity removes the coin" = + do + assert + (Builtin.lookupCoin #"" #"" (operation #"" #"" 0 (coin 5)) == 0) + + test "nonzero quantity rejects currency ID above 32 bytes" fail = + do + let + _ = + operation + #"000000000000000000000000000000000000000000000000000000000000000000" + #"" + 1 + empty + in + () + + test "nonzero quantity rejects token name above 32 bytes" fail = + do + let + _ = + operation + #"" + #"000000000000000000000000000000000000000000000000000000000000000000" + 1 + empty + in + () + + test "positive quantity above signed 128 bits rejects" fail = + do + let + _ = + operation + #"" + #"" + ( + Builtin.byteStringToInteger + True + #"80000000000000000000000000000000" + ) + empty + in + () + + test "zero quantity accepts long identifiers" = + do + assert + ( + Builtin.lookupCoin + #"" + #"" + ( + operation + #"000000000000000000000000000000000000000000000000000000000000000000" + #"000000000000000000000000000000000000000000000000000000000000000000" + 0 + (coin 5) + ) == + 5 + ) + + test "reinsertion replaces rather than adds" = + do + assert + (Builtin.lookupCoin #"" #"" (operation #"" #"" 7 (coin 5)) == 7) diff --git a/proofs/builtins/IntegerToByteString.nash b/proofs/builtins/IntegerToByteString.nash new file mode 100644 index 00000000..b671e43e --- /dev/null +++ b/proofs/builtins/IntegerToByteString.nash @@ -0,0 +1,73 @@ +module IntegerToByteString exposing (..) + + +import Builtin + + +operation p0 p1 p2 = Builtin.integerToByteString p0 p1 p2 + + +proof + import Proof + + test "zero with minimal width is empty" = + do + assert (operation True 0 0 == #"" && operation False 0 0 == #"") + + test "endianness and leading padding" = + do + assert + ( + operation True 3 258 == + #"000102" && + operation False 3 258 == + #"020100" + ) + + prop "unsigned byte roundtrip" = + let + n via Proof.int + in + do + assert + ( + if n >= 0 && n < 256 then + Builtin.byteStringToInteger + True + (operation True 1 n) == + n + else + True + ) + + test "negative integer rejects" fail = + do + let + _ = operation True 0 (-1) + in + () + + test "insufficient width rejects" fail = + do + let + _ = operation True 1 256 + in + () + + test "negative width rejects" fail = + do + let + _ = operation True (-1) 1 + in + () + + test "width above 8192 rejects" fail = + do + let + _ = operation True 8193 1 + in + () + + test "largest supported width is accepted" = + do + assert (Builtin.lengthOfByteString (operation True 8192 1) == 8192) diff --git a/proofs/builtins/Keccak256.nash b/proofs/builtins/Keccak256.nash new file mode 100644 index 00000000..75b2d756 --- /dev/null +++ b/proofs/builtins/Keccak256.nash @@ -0,0 +1,19 @@ +module Keccak256 exposing (..) + + +import Builtin + + +{-| Digest length is the semantic obligation here. This does not claim collision resistance or correctness of the underlying cryptographic implementation. The pinned Lean model exposes an opaque hash without length lemmas; this obligation is pending there. -} +operation p0 = Builtin.keccak_256 p0 + + +proof + import Proof + + prop "digest has the specified byte length" = + let + message via Proof.bytes + in + do + assert (Builtin.lengthOfByteString (operation message) == 32) diff --git a/proofs/builtins/LengthOfArray.nash b/proofs/builtins/LengthOfArray.nash new file mode 100644 index 00000000..068301a5 --- /dev/null +++ b/proofs/builtins/LengthOfArray.nash @@ -0,0 +1,26 @@ +module LengthOfArray exposing (..) + + +import Builtin + + +{-| The pinned PlutusCoreBlaster revision has no array/value model. These are +executable source obligations; Lean verification is pending model support. -} +operation : array Data -> int +operation p0 = Builtin.lengthOfArray p0 + + +proof + import Proof + + test "empty array length" = + do + assert (operation (Builtin.listToArray []) == 0) + + prop "length after list conversion" = + let + x via Proof.data + y via Proof.data + in + do + assert (operation (Builtin.listToArray [ x, y ]) == 2) diff --git a/proofs/builtins/LengthOfByteString.nash b/proofs/builtins/LengthOfByteString.nash new file mode 100644 index 00000000..c378a9d5 --- /dev/null +++ b/proofs/builtins/LengthOfByteString.nash @@ -0,0 +1,38 @@ +module LengthOfByteString exposing (..) + + +import Builtin + + +operation p0 = Builtin.lengthOfByteString p0 + + +proof + import Proof + + prop "nonnegative" = + let + x via Proof.bytes + in + do + assert (operation x >= 0) + + test "empty length is zero" = + do + assert (operation #"" == 0) + + prop "append adds lengths" = + let + x via Proof.bytes + y via Proof.bytes + in + do + assert + ( + operation (Builtin.appendByteString x y) == + Builtin.addInteger (operation x) (operation y) + ) + + test "bytes are counted rather than UTF8 codepoints" = + do + assert (operation #"00c3a9ff" == 4) diff --git a/proofs/builtins/LessThanByteString.nash b/proofs/builtins/LessThanByteString.nash new file mode 100644 index 00000000..f901770f --- /dev/null +++ b/proofs/builtins/LessThanByteString.nash @@ -0,0 +1,30 @@ +module LessThanByteString exposing (..) + + +import Builtin + + +operation p0 p1 = Builtin.lessThanByteString p0 p1 + + +proof + import Proof + + prop "irreflexive" = + let + x via Proof.bytes + in + do + assert (not (operation x x)) + + prop "asymmetric" = + let + x via Proof.bytes + y via Proof.bytes + in + do + assert (not (operation x y && operation y x)) + + test "unsigned lexicographic ordering" = + do + assert (operation #"00ff" #"0100" && not (operation #"ff" #"00")) diff --git a/proofs/builtins/LessThanEqualsByteString.nash b/proofs/builtins/LessThanEqualsByteString.nash new file mode 100644 index 00000000..3c6fbc00 --- /dev/null +++ b/proofs/builtins/LessThanEqualsByteString.nash @@ -0,0 +1,33 @@ +module LessThanEqualsByteString exposing (..) + + +import Builtin + + +operation p0 p1 = Builtin.lessThanEqualsByteString p0 p1 + + +proof + import Proof + + prop "reflexive" = + let + x via Proof.bytes + in + do + assert (operation x x) + + prop "agrees with strict order or equality" = + let + x via Proof.bytes + y via Proof.bytes + in + do + assert + ( + operation x y == + ( + Builtin.lessThanByteString x y || + Builtin.equalsByteString x y + ) + ) diff --git a/proofs/builtins/LessThanEqualsInteger.nash b/proofs/builtins/LessThanEqualsInteger.nash new file mode 100644 index 00000000..ddacbeac --- /dev/null +++ b/proofs/builtins/LessThanEqualsInteger.nash @@ -0,0 +1,51 @@ +module LessThanEqualsInteger exposing (..) + + +import Builtin + + +operation p0 p1 = Builtin.lessThanEqualsInteger p0 p1 + + +proof + import Proof + + prop "reflexive" = + let + x via Proof.int + in + do + assert (operation x x) + + prop "antisymmetric" = + let + x via Proof.int + y via Proof.int + in + do + assert + ( + if operation x y && operation y x then + Builtin.equalsInteger x y + else + True + ) + + prop "agrees with strict order or equality" = + let + x via Proof.int + y via Proof.int + in + do + assert + ( + operation x y == + ( + Builtin.lessThanInteger x y || + Builtin.equalsInteger x y + ) + ) + + test "detects wrong opcode argument order or signedness" = + do + assert (operation (-1) 0 && not (operation 0 (-1))) diff --git a/proofs/builtins/LessThanInteger.nash b/proofs/builtins/LessThanInteger.nash new file mode 100644 index 00000000..6c12aef7 --- /dev/null +++ b/proofs/builtins/LessThanInteger.nash @@ -0,0 +1,52 @@ +module LessThanInteger exposing (..) + + +import Builtin + + +operation p0 p1 = Builtin.lessThanInteger p0 p1 + + +proof + import Proof + + prop "irreflexive" = + let + x via Proof.int + in + do + assert (not (operation x x)) + + prop "strict successor order at every width" = + let + x via Proof.int + in + do + assert (operation x (Builtin.addInteger x 1)) + + prop "asymmetric" = + let + x via Proof.int + y via Proof.int + in + do + assert (not (operation x y && operation y x)) + + prop "transitive" = + let + x via Proof.int + y via Proof.int + z via Proof.int + in + do + assert + ( + if operation x y && operation y z then + operation x z + else + True + ) + + test "detects wrong opcode argument order or signedness" = + do + assert (operation (-1) 0 && not (operation 0 (-1))) diff --git a/proofs/builtins/ListData.nash b/proofs/builtins/ListData.nash new file mode 100644 index 00000000..873f7e39 --- /dev/null +++ b/proofs/builtins/ListData.nash @@ -0,0 +1,33 @@ +module ListData exposing (..) + + +import Builtin + + +operation p0 = Builtin.listData p0 + + +proof + import Proof + + prop "list elements survive roundtrip" = + let + x via Proof.data + y via Proof.data + in + do + assert + ( + Builtin.headList + (Builtin.unListData (operation [ x, y ])) == + x && + Builtin.headList + ( + Builtin.tailList + ( + Builtin.unListData + (operation [ x, y ]) + ) + ) == + y + ) diff --git a/proofs/builtins/ListToArray.nash b/proofs/builtins/ListToArray.nash new file mode 100644 index 00000000..7ec90be8 --- /dev/null +++ b/proofs/builtins/ListToArray.nash @@ -0,0 +1,27 @@ +module ListToArray exposing (..) + + +import Builtin + + +{-| The pinned PlutusCoreBlaster revision has no array/value model. These are +executable source obligations; Lean verification is pending model support. -} +operation p0 = Builtin.listToArray p0 + + +proof + import Proof + + prop "preserves order" = + let + x via Proof.data + y via Proof.data + in + do + assert + ( + Builtin.indexArray (operation [ x, y ]) 0 == + x && + Builtin.indexArray (operation [ x, y ]) 1 == + y + ) diff --git a/proofs/builtins/LookupCoin.nash b/proofs/builtins/LookupCoin.nash new file mode 100644 index 00000000..a55e10e8 --- /dev/null +++ b/proofs/builtins/LookupCoin.nash @@ -0,0 +1,39 @@ +module LookupCoin exposing (..) + + +import Builtin +import Cardano.Value + + +{-| The pinned PlutusCoreBlaster revision has no array/value model. These are +executable source obligations; Lean verification is pending model support. -} +operation p0 p1 p2 = Builtin.lookupCoin p0 p1 p2 + + +empty : value +empty = Cardano.Value.empty + + +coin : int -> value +coin amount = Builtin.insertCoin #"" #"" amount empty + + +proof + test "absent asset has zero quantity" = + do + assert (operation #"" #"" empty == 0) + + test "returns the inserted quantity" = + do + assert (operation #"" #"" (coin 5) == 5) + + test "long nonexistent identifier gives zero" = + do + assert + ( + operation + #"000000000000000000000000000000000000000000000000000000000000000000" + #"" + (coin 5) == + 0 + ) diff --git a/proofs/builtins/MapData.nash b/proofs/builtins/MapData.nash new file mode 100644 index 00000000..f882a027 --- /dev/null +++ b/proofs/builtins/MapData.nash @@ -0,0 +1,46 @@ +module MapData exposing (..) + + +import Builtin + + +operation p0 = Builtin.mapData p0 + + +proof + import Proof + + prop "map entry survives roundtrip" = + let + x via Proof.data + y via Proof.data + in + do + assert + ( + Builtin.fstPair + ( + Builtin.headList + ( + Builtin.unMapData + ( + operation + [ Builtin.mkPairData x y ] + ) + ) + ) == + x && + Builtin.sndPair + ( + Builtin.headList + ( + Builtin.unMapData + ( + operation + [ Builtin.mkPairData x y + ] + ) + ) + ) == + y + ) diff --git a/proofs/builtins/MkCons.nash b/proofs/builtins/MkCons.nash new file mode 100644 index 00000000..1bb137d8 --- /dev/null +++ b/proofs/builtins/MkCons.nash @@ -0,0 +1,43 @@ +module MkCons exposing (..) + + +import Builtin + + +operation p0 p1 = Builtin.mkCons p0 p1 + + +proof + import Proof + + prop "head of cons is its element" = + let + x via Proof.data + y via Proof.data + in + do + assert (Builtin.headList (operation x [ y ]) == x) + + prop "tail of cons is its original list" = + let + x via Proof.data + y via Proof.data + in + do + assert + ( + Builtin.headList (Builtin.tailList (operation x [ y ])) == + y && + Builtin.nullList + ( + Builtin.tailList + (Builtin.tailList (operation x [ y ])) + ) + ) + + prop "cons is never empty" = + let + x via Proof.data + in + do + assert (not (Builtin.nullList (operation x []))) diff --git a/proofs/builtins/MkNilData.nash b/proofs/builtins/MkNilData.nash new file mode 100644 index 00000000..74d74250 --- /dev/null +++ b/proofs/builtins/MkNilData.nash @@ -0,0 +1,20 @@ +module MkNilData exposing (..) + + +import Builtin + + +operation p0 = Builtin.mkNilData p0 + + +proof + test "produces the empty list" = + do + assert (Builtin.nullList (operation ())) + + test "empty result has no head" fail = + do + let + _ = Builtin.headList (operation ()) + in + () diff --git a/proofs/builtins/MkNilPairData.nash b/proofs/builtins/MkNilPairData.nash new file mode 100644 index 00000000..44d3d9c1 --- /dev/null +++ b/proofs/builtins/MkNilPairData.nash @@ -0,0 +1,20 @@ +module MkNilPairData exposing (..) + + +import Builtin + + +operation p0 = Builtin.mkNilPairData p0 + + +proof + test "produces the empty list" = + do + assert (Builtin.nullList (operation ())) + + test "empty result has no head" fail = + do + let + _ = Builtin.headList (operation ()) + in + () diff --git a/proofs/builtins/MkPairData.nash b/proofs/builtins/MkPairData.nash new file mode 100644 index 00000000..ebe964c9 --- /dev/null +++ b/proofs/builtins/MkPairData.nash @@ -0,0 +1,25 @@ +module MkPairData exposing (..) + + +import Builtin + + +operation p0 p1 = Builtin.mkPairData p0 p1 + + +proof + import Proof + + prop "both projections recover their arguments" = + let + x via Proof.data + y via Proof.data + in + do + assert + ( + Builtin.fstPair (operation x y) == + x && + Builtin.sndPair (operation x y) == + y + ) diff --git a/proofs/builtins/ModInteger.nash b/proofs/builtins/ModInteger.nash new file mode 100644 index 00000000..32ee3d86 --- /dev/null +++ b/proofs/builtins/ModInteger.nash @@ -0,0 +1,71 @@ +module ModInteger exposing (..) + + +import Builtin + + +operation p0 p1 = Builtin.modInteger p0 p1 + + +proof + import Proof + + prop "remainder modulo one is zero" = + let + x via Proof.int + in + do + assert (operation x 1 == 0) + + prop "division reconstruction" = + let + x via Proof.int + y via Proof.int + in + do + assert + ( + if y == 0 then + True + else + Builtin.addInteger + ( + Builtin.multiplyInteger + (Builtin.divideInteger x y) + y + ) + (operation x y) == + x + ) + + prop "positive modulus bounds the remainder" = + let + x via Proof.int + y via Proof.int + in + do + assert + ( + if y > 0 then + operation x y >= 0 && operation x y < y + else + True + ) + + test "negative rounding convention" = + do + assert (operation (-7) 3 == 2) + + prop "zero divisor rejects every dividend" fail = + let + x via Proof.int + in + do + let + _ = operation x 0 + in + () + + test "detects wrong opcode argument order or signedness" = + do + assert (operation 7 (-3) == -2) diff --git a/proofs/builtins/MultiplyInteger.nash b/proofs/builtins/MultiplyInteger.nash new file mode 100644 index 00000000..623f1257 --- /dev/null +++ b/proofs/builtins/MultiplyInteger.nash @@ -0,0 +1,71 @@ +module MultiplyInteger exposing (..) + + +import Builtin + + +operation p0 p1 = Builtin.multiplyInteger p0 p1 + + +proof + import Proof + + prop "commutative" = + let + x via Proof.int + y via Proof.int + in + do + assert (operation x y == operation y x) + + prop "associative" = + let + x via Proof.int + y via Proof.int + z via Proof.int + in + do + assert + (operation (operation x y) z == operation x (operation y z)) + + prop "distributive over addition" = + let + x via Proof.int + y via Proof.int + z via Proof.int + in + do + assert + ( + operation x (Builtin.addInteger y z) == + Builtin.addInteger (operation x y) (operation x z) + ) + + prop "multiplicative identity" = + let + x via Proof.int + in + do + assert (operation x 1 == x && operation 1 x == x) + + prop "zero annihilates" = + let + x via Proof.int + in + do + assert (operation x 0 == 0) + + prop "square is nonnegative without machine overflow" = + let + x via Proof.int + in + do + assert (operation x x >= 0) + + test "crosses signed 64-bit boundary" = + do + assert (operation 4294967296 4294967296 == 18446744073709551616) + + test "detects wrong opcode argument order or signedness" = + do + assert (operation (-4294967296) 4294967296 == -18446744073709551616) diff --git a/proofs/builtins/NullList.nash b/proofs/builtins/NullList.nash new file mode 100644 index 00000000..4742326a --- /dev/null +++ b/proofs/builtins/NullList.nash @@ -0,0 +1,23 @@ +module NullList exposing (..) + + +import Builtin + + +operation : list Data -> bool +operation p0 = Builtin.nullList p0 + + +proof + import Proof + + test "empty list is null" = + do + assert (operation []) + + prop "singleton list is not null" = + let + x via Proof.data + in + do + assert (not (operation [ x ])) diff --git a/proofs/builtins/OrByteString.nash b/proofs/builtins/OrByteString.nash new file mode 100644 index 00000000..d0b309ad --- /dev/null +++ b/proofs/builtins/OrByteString.nash @@ -0,0 +1,35 @@ +module OrByteString exposing (..) + + +import Builtin + + +operation p0 p1 p2 = Builtin.orByteString p0 p1 p2 + + +proof + import Proof + + prop "commutative for both padding modes" = + let + pad via Proof.bool + x via Proof.bytes + y via Proof.bytes + in + do + assert (operation pad x y == operation pad y x) + + prop "self law" = + let + pad via Proof.bool + x via Proof.bytes + in + do + assert (operation pad x x == x) + + prop "truncating mode with empty input" = + let + x via Proof.bytes + in + do + assert (operation False x #"" == #"") diff --git a/proofs/builtins/QuotientInteger.nash b/proofs/builtins/QuotientInteger.nash new file mode 100644 index 00000000..35bc9bd8 --- /dev/null +++ b/proofs/builtins/QuotientInteger.nash @@ -0,0 +1,54 @@ +module QuotientInteger exposing (..) + + +import Builtin + + +operation p0 p1 = Builtin.quotientInteger p0 p1 + + +proof + import Proof + + prop "division by one" = + let + x via Proof.int + in + do + assert (operation x 1 == x) + + prop "quotient and remainder reconstruct the dividend" = + let + x via Proof.int + y via Proof.int + in + do + assert + ( + if y == 0 then + True + else + Builtin.addInteger + (Builtin.multiplyInteger (operation x y) y) + (Builtin.remainderInteger x y) == + x + ) + + test "negative rounding convention" = + do + assert (operation (-7) 3 == -2) + + prop "zero divisor rejects every dividend" fail = + let + x via Proof.int + in + do + let + _ = operation x 0 + in + () + + test "detects wrong opcode argument order or signedness" = + do + assert + (operation (-9223372036854775808) (-1) == 9223372036854775808) diff --git a/proofs/builtins/ReadBit.nash b/proofs/builtins/ReadBit.nash new file mode 100644 index 00000000..c394f79b --- /dev/null +++ b/proofs/builtins/ReadBit.nash @@ -0,0 +1,56 @@ +module ReadBit exposing (..) + + +import Builtin + + +operation p0 p1 = Builtin.readBit p0 p1 + + +proof + import Proof + + test "least significant bit indexing" = + do + assert (operation #"01" 0 && not (operation #"01" 7)) + + prop "negative bit index rejects" fail = + let + x via Proof.bytes + in + do + let + _ = operation x (-1) + in + () + + prop "index equal to bit count rejects" fail = + let + x via Proof.bytes + in + do + let + _ = + operation + x + ( + Builtin.multiplyInteger + 8 + (Builtin.lengthOfByteString x) + ) + in + () + + test "index above 128 bits rejects without host overflow" fail = + do + let + _ = + operation + #"ff" + ( + Builtin.byteStringToInteger + True + #"0100000000000000000000000000000000" + ) + in + () diff --git a/proofs/builtins/RemainderInteger.nash b/proofs/builtins/RemainderInteger.nash new file mode 100644 index 00000000..2196af89 --- /dev/null +++ b/proofs/builtins/RemainderInteger.nash @@ -0,0 +1,73 @@ +module RemainderInteger exposing (..) + + +import Builtin + + +operation p0 p1 = Builtin.remainderInteger p0 p1 + + +proof + import Proof + + prop "remainder modulo one is zero" = + let + x via Proof.int + in + do + assert (operation x 1 == 0) + + prop "division reconstruction" = + let + x via Proof.int + y via Proof.int + in + do + assert + ( + if y == 0 then + True + else + Builtin.addInteger + ( + Builtin.multiplyInteger + (Builtin.quotientInteger x y) + y + ) + (operation x y) == + x + ) + + prop "remainder has the dividend sign" = + let + x via Proof.int + y via Proof.int + in + do + assert + ( + if y == 0 then + True + else if x >= 0 then + operation x y >= 0 + else + operation x y <= 0 + ) + + test "negative rounding convention" = + do + assert (operation (-7) 3 == -1) + + prop "zero divisor rejects every dividend" fail = + let + x via Proof.int + in + do + let + _ = operation x 0 + in + () + + test "detects wrong opcode argument order or signedness" = + do + assert (operation 7 (-3) == 1) diff --git a/proofs/builtins/ReplicateByte.nash b/proofs/builtins/ReplicateByte.nash new file mode 100644 index 00000000..344750b7 --- /dev/null +++ b/proofs/builtins/ReplicateByte.nash @@ -0,0 +1,49 @@ +module ReplicateByte exposing (..) + + +import Builtin + + +operation p0 p1 = Builtin.replicateByte p0 p1 + + +proof + test "zero count produces empty bytes" = + do + assert (operation 0 255 == #"") + + test "repeats exactly the requested byte" = + do + assert (operation 3 128 == #"808080") + + test "invalid byte rejects" fail = + do + let + _ = operation 1 256 + in + () + + test "negative count rejects" fail = + do + let + _ = operation (-1) 0 + in + () + + test "negative byte rejects" fail = + do + let + _ = operation 1 (-1) + in + () + + test "count above 8192 rejects" fail = + do + let + _ = operation 8193 0 + in + () + + test "largest supported count is accepted" = + do + assert (Builtin.lengthOfByteString (operation 8192 0) == 8192) diff --git a/proofs/builtins/Ripemd160.nash b/proofs/builtins/Ripemd160.nash new file mode 100644 index 00000000..9e1ac834 --- /dev/null +++ b/proofs/builtins/Ripemd160.nash @@ -0,0 +1,24 @@ +module Ripemd160 exposing (..) + + +import Builtin + + +{-| Digest length is the semantic obligation here. This does not claim collision resistance or correctness of the underlying cryptographic implementation. The pinned Lean model exposes an opaque hash without length lemmas; this obligation is pending there. -} +operation p0 = Builtin.ripemd_160 p0 + + +proof + import Proof + + prop "digest has the specified byte length" = + let + message via Proof.bytes + in + do + assert (Builtin.lengthOfByteString (operation message) == 20) + + test "empty message known answer detects the wrong hash primitive" = + do + assert + (operation #"" == #"9c1185a5c5e9fc54612808977ee8f548b2258d31") diff --git a/proofs/builtins/RotateByteString.nash b/proofs/builtins/RotateByteString.nash new file mode 100644 index 00000000..ab4c3a34 --- /dev/null +++ b/proofs/builtins/RotateByteString.nash @@ -0,0 +1,61 @@ +module RotateByteString exposing (..) + + +import Builtin + + +operation p0 p1 = Builtin.rotateByteString p0 p1 + + +proof + import Proof + + prop "zero rotation is identity" = + let + x via Proof.bytes + in + do + assert (operation x 0 == x) + + prop "rotation by width is identity" = + let + x via Proof.bytes + in + do + assert + ( + operation + x + ( + Builtin.multiplyInteger + 8 + (Builtin.lengthOfByteString x) + ) == + x + ) + + prop "inverse rotation" = + let + x via Proof.bytes + n via Proof.int + in + do + assert + ( + operation (operation x n) (Builtin.subtractInteger 0 n) == + x + ) + + test "large rotation is reduced by bit width" = + do + assert + ( + operation + #"01" + ( + Builtin.byteStringToInteger + True + #"0100000000000000000000000000000000" + ) == + #"01" + ) diff --git a/proofs/builtins/ScaleValue.nash b/proofs/builtins/ScaleValue.nash new file mode 100644 index 00000000..77eb9505 --- /dev/null +++ b/proofs/builtins/ScaleValue.nash @@ -0,0 +1,53 @@ +module ScaleValue exposing (..) + + +import Builtin +import Cardano.Value + + +{-| The pinned PlutusCoreBlaster revision has no array/value model. These are +executable source obligations; Lean verification is pending model support. -} +operation p0 p1 = Builtin.scaleValue p0 p1 + + +empty : value +empty = Cardano.Value.empty + + +coin : int -> value +coin amount = Builtin.insertCoin #"" #"" amount empty + + +proof + test "zero annihilates quantities" = + do + assert (Builtin.lookupCoin #"" #"" (operation 0 (coin 5)) == 0) + + test "multiplicative identity" = + do + assert (Builtin.lookupCoin #"" #"" (operation 1 (coin 5)) == 5) + + test "scales quantities" = + do + assert (Builtin.lookupCoin #"" #"" (operation 3 (coin 5)) == 15) + + test "quantity overflow rejects" fail = + do + let + _ = operation 2 (coin 170141183460469231731687303715884105727) + in + () + + test "out of bounds multiplier rejects" fail = + do + let + _ = + operation + ( + Builtin.byteStringToInteger + True + #"80000000000000000000000000000000" + ) + empty + in + () diff --git a/proofs/builtins/SerialiseData.nash b/proofs/builtins/SerialiseData.nash new file mode 100644 index 00000000..632d9ec7 --- /dev/null +++ b/proofs/builtins/SerialiseData.nash @@ -0,0 +1,20 @@ +module SerialiseData exposing (..) + + +import Builtin + + +{-| These are explicit CBOR examples, not a completeness theorem for serialization. -} +operation p0 = Builtin.serialiseData p0 + + +proof + test "canonical CBOR integer encoding" = + do + assert + ( + operation (Primitive.coerce (Builtin.iData 0)) == + #"00" && + operation (Primitive.coerce (Builtin.iData (-1))) == + #"20" + ) diff --git a/proofs/builtins/Sha2256.nash b/proofs/builtins/Sha2256.nash new file mode 100644 index 00000000..6d5b4328 --- /dev/null +++ b/proofs/builtins/Sha2256.nash @@ -0,0 +1,27 @@ +module Sha2256 exposing (..) + + +import Builtin + + +{-| Digest length is the semantic obligation here. This does not claim collision resistance or correctness of the underlying cryptographic implementation. The pinned Lean model exposes an opaque hash without length lemmas; this obligation is pending there. -} +operation p0 = Builtin.sha2_256 p0 + + +proof + import Proof + + prop "digest has the specified byte length" = + let + message via Proof.bytes + in + do + assert (Builtin.lengthOfByteString (operation message) == 32) + + test "empty message known answer detects the wrong hash primitive" = + do + assert + ( + operation #"" == + #"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + ) diff --git a/proofs/builtins/Sha3256.nash b/proofs/builtins/Sha3256.nash new file mode 100644 index 00000000..3f2c12a6 --- /dev/null +++ b/proofs/builtins/Sha3256.nash @@ -0,0 +1,27 @@ +module Sha3256 exposing (..) + + +import Builtin + + +{-| Digest length is the semantic obligation here. This does not claim collision resistance or correctness of the underlying cryptographic implementation. The pinned Lean model exposes an opaque hash without length lemmas; this obligation is pending there. -} +operation p0 = Builtin.sha3_256 p0 + + +proof + import Proof + + prop "digest has the specified byte length" = + let + message via Proof.bytes + in + do + assert (Builtin.lengthOfByteString (operation message) == 32) + + test "empty message known answer detects the wrong hash primitive" = + do + assert + ( + operation #"" == + #"a7ffc6f8bf1ed76651c14756a061d662f580ff4de43b49fa82d80a4b80f8434a" + ) diff --git a/proofs/builtins/ShiftByteString.nash b/proofs/builtins/ShiftByteString.nash new file mode 100644 index 00000000..e93f85a7 --- /dev/null +++ b/proofs/builtins/ShiftByteString.nash @@ -0,0 +1,69 @@ +module ShiftByteString exposing (..) + + +import Builtin + + +operation p0 p1 = Builtin.shiftByteString p0 p1 + + +proof + import Proof + + prop "zero shift is identity" = + let + x via Proof.bytes + in + do + assert (operation x 0 == x) + + test "direction and zero fill" = + do + assert (operation #"01" 1 == #"02" && operation #"80" (-1) == #"40") + + prop "shifting out every bit produces zero" = + let + x via Proof.bytes + in + do + assert + ( + Builtin.countSetBits + ( + operation + x + ( + Builtin.multiplyInteger + 8 + (Builtin.lengthOfByteString x) + ) + ) == + 0 + ) + + test "large shift does not truncate to a host word" = + do + assert + ( + Builtin.countSetBits + ( + operation + #"ff" + ( + Builtin.byteStringToInteger + True + #"0100000000000000000000000000000000" + ) + ) == + 0 + ) + + test "whole byte shifts preserve the remaining bytes" = + do + assert + ( + operation #"001122" 8 == + #"112200" && + operation #"001122" (-8) == + #"000011" + ) diff --git a/proofs/builtins/SliceByteString.nash b/proofs/builtins/SliceByteString.nash new file mode 100644 index 00000000..9b0648e2 --- /dev/null +++ b/proofs/builtins/SliceByteString.nash @@ -0,0 +1,48 @@ +module SliceByteString exposing (..) + + +import Builtin + + +operation p0 p1 p2 = Builtin.sliceByteString p0 p1 p2 + + +proof + import Proof + + prop "zero count produces empty bytes" = + let + x via Proof.bytes + offset via Proof.int + in + do + assert (operation offset 0 x == #"") + + prop "negative count produces empty bytes" = + let + x via Proof.bytes + in + do + assert (operation 0 (-1) x == #"") + + prop "whole slice preserves the bytes" = + let + x via Proof.bytes + in + do + assert (operation 0 (Builtin.lengthOfByteString x) x == x) + + test "negative offset is clamped independently of count" = + do + assert (operation (-2) 2 #"010203" == #"0102") + + prop "offset at the end gives empty bytes" = + let + x via Proof.bytes + in + do + assert (operation (Builtin.lengthOfByteString x) 1 x == #"") + + test "detects wrong opcode argument order or signedness" = + do + assert (operation 1 2 #"00010203" == #"0102") diff --git a/proofs/builtins/SndPair.nash b/proofs/builtins/SndPair.nash new file mode 100644 index 00000000..253b0c31 --- /dev/null +++ b/proofs/builtins/SndPair.nash @@ -0,0 +1,19 @@ +module SndPair exposing (..) + + +import Builtin + + +operation p0 = Builtin.sndPair p0 + + +proof + import Proof + + prop "projects the correct component" = + let + x via Proof.data + y via Proof.data + in + do + assert (operation (Builtin.mkPairData x y) == y) diff --git a/proofs/builtins/SubtractInteger.nash b/proofs/builtins/SubtractInteger.nash new file mode 100644 index 00000000..b05ffc5c --- /dev/null +++ b/proofs/builtins/SubtractInteger.nash @@ -0,0 +1,48 @@ +module SubtractInteger exposing (..) + + +import Builtin + + +operation p0 p1 = Builtin.subtractInteger p0 p1 + + +proof + import Proof + + prop "right identity" = + let + x via Proof.int + in + do + assert (operation x 0 == x) + + prop "self subtraction is zero" = + let + x via Proof.int + in + do + assert (operation x x == 0) + + prop "inverse of addition" = + let + x via Proof.int + y via Proof.int + in + do + assert (Builtin.addInteger (operation x y) y == x) + + prop "predecessor never wraps at any integer width" = + let + x via Proof.int + in + do + assert (operation x 1 < x) + + test "argument order" = + do + assert (operation 7 2 == 5 && operation 2 7 == -5) + + test "detects wrong opcode argument order or signedness" = + do + assert (operation (-9223372036854775808) 1 == -9223372036854775809) diff --git a/proofs/builtins/TailList.nash b/proofs/builtins/TailList.nash new file mode 100644 index 00000000..2423f74b --- /dev/null +++ b/proofs/builtins/TailList.nash @@ -0,0 +1,39 @@ +module TailList exposing (..) + + +import Builtin + + +operation : list Data -> list Data +operation p0 = Builtin.tailList p0 + + +proof + import Proof + + prop "tail preserves subsequent elements" = + let + x via Proof.data + y via Proof.data + in + do + assert + ( + Builtin.headList (operation [ x, y ]) == + y && + Builtin.nullList (operation (operation [ x, y ])) + ) + + prop "singleton tail is empty" = + let + x via Proof.data + in + do + assert (Builtin.nullList (operation [ x ])) + + test "empty list rejects" fail = + do + let + _ = operation [] + in + () diff --git a/proofs/builtins/Trace.nash b/proofs/builtins/Trace.nash new file mode 100644 index 00000000..17600b26 --- /dev/null +++ b/proofs/builtins/Trace.nash @@ -0,0 +1,24 @@ +module Trace exposing (..) + + +import Builtin + + +{-| The returned value is specified here. Log contents are not observed by the proof backend. -} +operation p0 p1 = Builtin.trace p0 p1 + + +proof + import Proof + + prop "preserves the value" = + let + message via Proof.string + x via Proof.data + in + do + assert (operation message x == x) + + test "detects wrong opcode argument order or signedness" = + do + assert (operation "log" 17 == 17) diff --git a/proofs/builtins/UnBData.nash b/proofs/builtins/UnBData.nash new file mode 100644 index 00000000..fae488da --- /dev/null +++ b/proofs/builtins/UnBData.nash @@ -0,0 +1,35 @@ +module UnBData exposing (..) + + +import Builtin + + +operation p0 = Builtin.unBData p0 + + +proof + import Proof + + prop "unboxes every bytestring" = + let + x via Proof.bytes + in + do + assert (operation (Builtin.bData x) == x) + + prop "integer Data is not bytes Data" fail = + let + x via Proof.int + in + do + let + _ = operation (Primitive.coerce (Builtin.iData x)) + in + () + + test "constructor Data cannot be used as bytes" fail = + do + let + _ = operation (Primitive.coerce (Builtin.constrData 0 [])) + in + () diff --git a/proofs/builtins/UnConstrData.nash b/proofs/builtins/UnConstrData.nash new file mode 100644 index 00000000..ee3f756f --- /dev/null +++ b/proofs/builtins/UnConstrData.nash @@ -0,0 +1,53 @@ +module UnConstrData exposing (..) + + +import Builtin + + +operation p0 = Builtin.unConstrData p0 + + +proof + import Proof + + prop "recovers tag and first field" = + let + tag via Proof.int + x via Proof.data + in + do + assert + ( + Builtin.fstPair + (operation (Builtin.constrData tag [ x ])) == + tag && + Builtin.headList + ( + Builtin.sndPair + ( + operation + (Builtin.constrData tag [ x ]) + ) + ) == + x + ) + + prop "integer Data is not constructor Data" fail = + let + x via Proof.int + in + do + let + _ = operation (Primitive.coerce (Builtin.iData x)) + in + () + + prop "bytes Data cannot be used as a constructor" fail = + let + x via Proof.bytes + in + do + let + _ = operation (Primitive.coerce (Builtin.bData x)) + in + () diff --git a/proofs/builtins/UnIData.nash b/proofs/builtins/UnIData.nash new file mode 100644 index 00000000..aa8c2614 --- /dev/null +++ b/proofs/builtins/UnIData.nash @@ -0,0 +1,35 @@ +module UnIData exposing (..) + + +import Builtin + + +operation p0 = Builtin.unIData p0 + + +proof + import Proof + + prop "unboxes every integer" = + let + x via Proof.int + in + do + assert (operation (Builtin.iData x) == x) + + prop "bytes Data is not integer Data" fail = + let + x via Proof.bytes + in + do + let + _ = operation (Primitive.coerce (Builtin.bData x)) + in + () + + test "constructor Data cannot be used as an integer" fail = + do + let + _ = operation (Primitive.coerce (Builtin.constrData 0 [])) + in + () diff --git a/proofs/builtins/UnListData.nash b/proofs/builtins/UnListData.nash new file mode 100644 index 00000000..0b474548 --- /dev/null +++ b/proofs/builtins/UnListData.nash @@ -0,0 +1,47 @@ +module UnListData exposing (..) + + +import Builtin + + +operation p0 = Builtin.unListData p0 + + +wrongShape : int -> List Data +wrongShape x = Primitive.coerce (Builtin.iData x) + + +wrongBytes : bytes -> List Data +wrongBytes x = Primitive.coerce (Builtin.bData x) + + +proof + import Proof + + prop "recovers the first element" = + let + x via Proof.data + in + do + assert + (Builtin.headList (operation (Builtin.listData [ x ])) == x) + + prop "integer Data is not list Data" fail = + let + x via Proof.int + in + do + let + _ = operation (wrongShape x) + in + () + + prop "bytes Data cannot be used as a list" fail = + let + x via Proof.bytes + in + do + let + _ = operation (wrongBytes x) + in + () diff --git a/proofs/builtins/UnMapData.nash b/proofs/builtins/UnMapData.nash new file mode 100644 index 00000000..25f5ced2 --- /dev/null +++ b/proofs/builtins/UnMapData.nash @@ -0,0 +1,61 @@ +module UnMapData exposing (..) + + +import Builtin + + +operation p0 = Builtin.unMapData p0 + + +wrongShape : int -> Map Data Data +wrongShape x = Primitive.coerce (Builtin.iData x) + + +wrongBytes : bytes -> Map Data Data +wrongBytes x = Primitive.coerce (Builtin.bData x) + + +proof + import Proof + + prop "recovers a map entry" = + let + x via Proof.data + y via Proof.data + in + do + assert + ( + Builtin.sndPair + ( + Builtin.headList + ( + operation + ( + Builtin.mapData + [ Builtin.mkPairData x y ] + ) + ) + ) == + y + ) + + prop "integer Data is not map Data" fail = + let + x via Proof.int + in + do + let + _ = operation (wrongShape x) + in + () + + prop "bytes Data cannot be used as a map" fail = + let + x via Proof.bytes + in + do + let + _ = operation (wrongBytes x) + in + () diff --git a/proofs/builtins/UnValueData.nash b/proofs/builtins/UnValueData.nash new file mode 100644 index 00000000..ef29f0ee --- /dev/null +++ b/proofs/builtins/UnValueData.nash @@ -0,0 +1,139 @@ +module UnValueData exposing (..) + + +import Builtin +import Cardano.Value + + +{-| The pinned PlutusCoreBlaster revision has no array/value model. These are +executable source obligations; Lean verification is pending model support. -} +operation p0 = Builtin.unValueData p0 + + +empty : value +empty = Cardano.Value.empty + + +coin : int -> value +coin amount = Builtin.insertCoin #"" #"" amount empty + + +emptyAssetMap : Map Bytes Int +emptyAssetMap = Builtin.mapData [] + + +emptyInner : Data +emptyInner = + Primitive.coerce + ( + Builtin.mapData + [ Builtin.mkPairData (Builtin.bData #"") emptyAssetMap ] + ) + + +zeroQuantity : Data +zeroQuantity = + Primitive.coerce + ( + Builtin.mapData + [ Builtin.mkPairData + (Builtin.bData #"") + ( + Builtin.mapData + [ Builtin.mkPairData + (Builtin.bData #"") + (Builtin.iData 0) + ] + ) + ] + ) + + +duplicateToken : Data +duplicateToken = + Primitive.coerce + ( + Builtin.mapData + [ Builtin.mkPairData + (Builtin.bData #"") + ( + Builtin.mapData + [ Builtin.mkPairData + (Builtin.bData #"") + (Builtin.iData 1) + , Builtin.mkPairData + (Builtin.bData #"") + (Builtin.iData 2) + ] + ) + ] + ) + + +unsortedTokens : Data +unsortedTokens = + Primitive.coerce + ( + Builtin.mapData + [ Builtin.mkPairData + (Builtin.bData #"") + ( + Builtin.mapData + [ Builtin.mkPairData + (Builtin.bData #"01") + (Builtin.iData 1) + , Builtin.mkPairData + (Builtin.bData #"00") + (Builtin.iData 2) + ] + ) + ] + ) + + +proof + test "decoding and encoding recover quantities" = + do + assert + ( + Builtin.lookupCoin + #"" + #"" + (operation (Builtin.valueData (coin 5))) == + 5 + ) + + test "integer Data is not a value" fail = + do + let + _ = operation (Primitive.coerce (Builtin.iData 5)) + in + () + + test "empty inner asset map rejects" fail = + do + let + _ = operation emptyInner + in + () + + test "zero encoded quantity rejects" fail = + do + let + _ = operation zeroQuantity + in + () + + test "duplicate asset names reject" fail = + do + let + _ = operation duplicateToken + in + () + + test "out of order asset names reject" fail = + do + let + _ = operation unsortedTokens + in + () diff --git a/proofs/builtins/UnionValue.nash b/proofs/builtins/UnionValue.nash new file mode 100644 index 00000000..6dc6383c --- /dev/null +++ b/proofs/builtins/UnionValue.nash @@ -0,0 +1,39 @@ +module UnionValue exposing (..) + + +import Builtin +import Cardano.Value + + +{-| The pinned PlutusCoreBlaster revision has no array/value model. These are +executable source obligations; Lean verification is pending model support. -} +operation p0 p1 = Builtin.unionValue p0 p1 + + +empty : value +empty = Cardano.Value.empty + + +coin : int -> value +coin amount = Builtin.insertCoin #"" #"" amount empty + + +proof + test "empty value is the identity" = + do + assert (Builtin.lookupCoin #"" #"" (operation empty (coin 5)) == 5) + + test "adds the same asset quantities" = + do + assert + (Builtin.lookupCoin #"" #"" (operation (coin 5) (coin 7)) == 12) + + test "quantity overflow rejects" fail = + do + let + _ = + operation + (coin 170141183460469231731687303715884105727) + (coin 1) + in + () diff --git a/proofs/builtins/ValueContains.nash b/proofs/builtins/ValueContains.nash new file mode 100644 index 00000000..2420df9e --- /dev/null +++ b/proofs/builtins/ValueContains.nash @@ -0,0 +1,46 @@ +module ValueContains exposing (..) + + +import Builtin +import Cardano.Value + + +{-| The pinned PlutusCoreBlaster revision has no array/value model. These are +executable source obligations; Lean verification is pending model support. -} +operation p0 p1 = Builtin.valueContains p0 p1 + + +empty : value +empty = Cardano.Value.empty + + +coin : int -> value +coin amount = Builtin.insertCoin #"" #"" amount empty + + +proof + test "nonnegative value contains itself" = + do + assert (operation (coin 5) (coin 5)) + + test "quantity containment" = + do + assert + ( + operation (coin 5) (coin 3) && + not (operation (coin 3) (coin 5)) + ) + + test "negative first quantity rejects" fail = + do + let + _ = operation (coin (-1)) (coin 1) + in + () + + test "negative second quantity rejects" fail = + do + let + _ = operation (coin 1) (coin (-1)) + in + () diff --git a/proofs/builtins/ValueData.nash b/proofs/builtins/ValueData.nash new file mode 100644 index 00000000..5df3f857 --- /dev/null +++ b/proofs/builtins/ValueData.nash @@ -0,0 +1,31 @@ +module ValueData exposing (..) + + +import Builtin +import Cardano.Value + + +{-| The pinned PlutusCoreBlaster revision has no array/value model. These are +executable source obligations; Lean verification is pending model support. -} +operation p0 = Builtin.valueData p0 + + +empty : value +empty = Cardano.Value.empty + + +coin : int -> value +coin amount = Builtin.insertCoin #"" #"" amount empty + + +proof + test "encoding and decoding recover quantities" = + do + assert + ( + Builtin.lookupCoin + #"" + #"" + (Builtin.unValueData (operation (coin 5))) == + 5 + ) diff --git a/proofs/builtins/VerifyEcdsaSecp256k1Signature.nash b/proofs/builtins/VerifyEcdsaSecp256k1Signature.nash new file mode 100644 index 00000000..bde498ff --- /dev/null +++ b/proofs/builtins/VerifyEcdsaSecp256k1Signature.nash @@ -0,0 +1,33 @@ +module VerifyEcdsaSecp256k1Signature exposing (..) + + +import Builtin + + +{-| Malformed-key rejection is specified explicitly. Signature soundness and completeness require cryptographic contracts absent from the pinned model. -} +operation p0 p1 p2 = Builtin.verifyEcdsaSecp256k1Signature p0 p1 p2 + + +proof + import Proof + + prop "empty public key rejects" fail = + let + message via Proof.bytes + in + do + let + _ = operation #"" message #"" + in + () + + test "valid key with truncated signature rejects" fail = + do + let + _ = + operation + #"0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" + #"0000000000000000000000000000000000000000000000000000000000000000" + #"00" + in + () diff --git a/proofs/builtins/VerifyEd25519Signature.nash b/proofs/builtins/VerifyEd25519Signature.nash new file mode 100644 index 00000000..0db615ba --- /dev/null +++ b/proofs/builtins/VerifyEd25519Signature.nash @@ -0,0 +1,56 @@ +module VerifyEd25519Signature exposing (..) + + +import Builtin + + +{-| Malformed-key rejection is specified explicitly. Signature soundness and completeness require cryptographic contracts absent from the pinned model. -} +operation p0 p1 p2 = Builtin.verifyEd25519Signature p0 p1 p2 + + +proof + import Proof + + prop "empty public key rejects" fail = + let + message via Proof.bytes + in + do + let + _ = operation #"" message #"" + in + () + + test "valid key with truncated signature rejects" fail = + do + let + _ = + operation + #"d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a" + #"" + #"00" + in + () + + test "RFC8032 empty message signature verifies" = + do + assert + ( + operation + #"d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a" + #"" + #"e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b" + ) + + test "changed message returns False instead of rejecting" = + do + assert + ( + not + ( + operation + #"d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a" + #"00" + #"e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b" + ) + ) diff --git a/proofs/builtins/VerifySchnorrSecp256k1Signature.nash b/proofs/builtins/VerifySchnorrSecp256k1Signature.nash new file mode 100644 index 00000000..c63b806c --- /dev/null +++ b/proofs/builtins/VerifySchnorrSecp256k1Signature.nash @@ -0,0 +1,33 @@ +module VerifySchnorrSecp256k1Signature exposing (..) + + +import Builtin + + +{-| Malformed-key rejection is specified explicitly. Signature soundness and completeness require cryptographic contracts absent from the pinned model. -} +operation p0 p1 p2 = Builtin.verifySchnorrSecp256k1Signature p0 p1 p2 + + +proof + import Proof + + prop "empty public key rejects" fail = + let + message via Proof.bytes + in + do + let + _ = operation #"" message #"" + in + () + + test "valid key with truncated signature rejects" fail = + do + let + _ = + operation + #"79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" + #"" + #"00" + in + () diff --git a/proofs/builtins/WriteBits.nash b/proofs/builtins/WriteBits.nash new file mode 100644 index 00000000..ed58761c --- /dev/null +++ b/proofs/builtins/WriteBits.nash @@ -0,0 +1,61 @@ +module WriteBits exposing (..) + + +import Builtin + + +operation p0 p1 p2 = Builtin.writeBits p0 p1 p2 + + +proof + import Proof + + prop "write then read" = + let + bit via Proof.bool + in + do + assert (Builtin.readBit (operation #"00" [ 0 ] bit) 0 == bit) + + prop "empty index list is identity" = + let + x via Proof.bytes + bit via Proof.bool + in + do + assert (operation x [] bit == x) + + test "invalid index rejects" fail = + do + let + _ = operation #"00" [ 8 ] True + in + () + + test "index above 128 bits rejects without host overflow" fail = + do + let + _ = + operation + #"00" + [ Builtin.byteStringToInteger + True + #"0100000000000000000000000000000000" + ] + True + in + () + + test "negative index rejects" fail = + do + let + _ = operation #"00" [ -1 ] True + in + () + + test "all indices must be valid" fail = + do + let + _ = operation #"00" [ 0, 8 ] True + in + () diff --git a/proofs/builtins/XorByteString.nash b/proofs/builtins/XorByteString.nash new file mode 100644 index 00000000..37014c13 --- /dev/null +++ b/proofs/builtins/XorByteString.nash @@ -0,0 +1,35 @@ +module XorByteString exposing (..) + + +import Builtin + + +operation p0 p1 p2 = Builtin.xorByteString p0 p1 p2 + + +proof + import Proof + + prop "commutative for both padding modes" = + let + pad via Proof.bool + x via Proof.bytes + y via Proof.bytes + in + do + assert (operation pad x y == operation pad y x) + + prop "self law" = + let + pad via Proof.bool + x via Proof.bytes + in + do + assert (Builtin.countSetBits (operation pad x x) == 0) + + prop "truncating mode with empty input" = + let + x via Proof.bytes + in + do + assert (operation False x #"" == #"") diff --git a/proofs/nash.jsonc b/proofs/nash.jsonc new file mode 100644 index 00000000..60d26262 --- /dev/null +++ b/proofs/nash.jsonc @@ -0,0 +1 @@ +{"type":"application","sourceDirectories":["builtins"],"plutusVersion":"v3"} diff --git a/proofs/pending/builtins/Bls12381G1MultiScalarMul.nash b/proofs/pending/builtins/Bls12381G1MultiScalarMul.nash new file mode 100644 index 00000000..f8ef26f7 --- /dev/null +++ b/proofs/pending/builtins/Bls12381G1MultiScalarMul.nash @@ -0,0 +1,63 @@ +module Bls12381G1MultiScalarMul exposing (..) + + +import Builtin + + +{-| These group laws are partial correctness over successfully decoded points. They do not assert that arbitrary bytes decode to a valid point. The pinned Lean model has opaque group operations without algebraic lemmas; these obligations are pending there. -} +operation p0 p1 = Builtin.bls12_381_g1_multiScalarMul p0 p1 + + +point : bytes -> bls_g1 +point encoded = Builtin.bls12_381_g1_uncompress encoded + + +proof + import Proof + + prop "singleton agrees with scalar multiplication" = + let + x via Proof.bytes + n via Proof.int + in + do + Proof.returns + ( + Builtin.bls12_381_g1_equal + (operation [ n ] [ point x ]) + (Builtin.bls12_381_g1_scalarMul n (point x)) + ) + (\holds -> holds) + + test "different scalar and point counts reject" fail = + do + let + _ = + operation + [ 1, 2 ] + [ point + #"c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + ] + in + () + + test "scalar above the signed 4096 bit range rejects" fail = + do + let + scalar = + Builtin.byteStringToInteger + True + ( + Builtin.consByteString + 128 + (Builtin.replicateByte 511 0) + ) + + _ = + operation + [ scalar ] + [ point + #"c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + ] + in + () diff --git a/proofs/pending/builtins/Bls12381G2MultiScalarMul.nash b/proofs/pending/builtins/Bls12381G2MultiScalarMul.nash new file mode 100644 index 00000000..a0879694 --- /dev/null +++ b/proofs/pending/builtins/Bls12381G2MultiScalarMul.nash @@ -0,0 +1,63 @@ +module Bls12381G2MultiScalarMul exposing (..) + + +import Builtin + + +{-| These group laws are partial correctness over successfully decoded points. They do not assert that arbitrary bytes decode to a valid point. The pinned Lean model has opaque group operations without algebraic lemmas; these obligations are pending there. -} +operation p0 p1 = Builtin.bls12_381_g2_multiScalarMul p0 p1 + + +point : bytes -> bls_g2 +point encoded = Builtin.bls12_381_g2_uncompress encoded + + +proof + import Proof + + prop "singleton agrees with scalar multiplication" = + let + x via Proof.bytes + n via Proof.int + in + do + Proof.returns + ( + Builtin.bls12_381_g2_equal + (operation [ n ] [ point x ]) + (Builtin.bls12_381_g2_scalarMul n (point x)) + ) + (\holds -> holds) + + test "different scalar and point counts reject" fail = + do + let + _ = + operation + [ 1, 2 ] + [ point + #"c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + ] + in + () + + test "scalar above the signed 4096 bit range rejects" fail = + do + let + scalar = + Builtin.byteStringToInteger + True + ( + Builtin.consByteString + 128 + (Builtin.replicateByte 511 0) + ) + + _ = + operation + [ scalar ] + [ point + #"c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + ] + in + () diff --git a/proofs/pending/nash.jsonc b/proofs/pending/nash.jsonc new file mode 100644 index 00000000..60d26262 --- /dev/null +++ b/proofs/pending/nash.jsonc @@ -0,0 +1 @@ +{"type":"application","sourceDirectories":["builtins"],"plutusVersion":"v3"} diff --git a/proofs/verification.json b/proofs/verification.json new file mode 100644 index 00000000..67ed5f44 --- /dev/null +++ b/proofs/verification.json @@ -0,0 +1,2342 @@ +{ + "fuel": 300, + "postcondition_fuel": 300, + "solver_timeout_seconds": 10, + "wall_timeout_seconds": 25, + "trust": "smt_verified", + "backend": { + "lean": "4.24.0", + "Blaster": "bafdd4f7976037cd7bd8e443df04af096dc5b96e", + "PlutusCore": "41fe7eadf460dc66bef22b85656bc36408638bdc", + "CardanoLedgerApi": "3f9f7c6b6ecf0012e1397bab0a814daa36e70ba2" + }, + "obligations": [ + { + "file": "builtins/AddInteger.nash", + "module": "AddInteger", + "name": "commutative", + "source_sha256": "3a8e3aa12a6c780234160c664ae3c4365d707fbb3f945ebd9bf7e1019acc6055", + "status": "verified" + }, + { + "file": "builtins/AddInteger.nash", + "module": "AddInteger", + "name": "associative", + "source_sha256": "3a8e3aa12a6c780234160c664ae3c4365d707fbb3f945ebd9bf7e1019acc6055", + "status": "timeout", + "reason": "Did not finish within the wall limit; no proof or rejection witness claimed." + }, + { + "file": "builtins/AddInteger.nash", + "module": "AddInteger", + "name": "left identity", + "source_sha256": "3a8e3aa12a6c780234160c664ae3c4365d707fbb3f945ebd9bf7e1019acc6055", + "status": "verified" + }, + { + "file": "builtins/AddInteger.nash", + "module": "AddInteger", + "name": "right identity", + "source_sha256": "3a8e3aa12a6c780234160c664ae3c4365d707fbb3f945ebd9bf7e1019acc6055", + "status": "verified" + }, + { + "file": "builtins/AddInteger.nash", + "module": "AddInteger", + "name": "additive inverse", + "source_sha256": "3a8e3aa12a6c780234160c664ae3c4365d707fbb3f945ebd9bf7e1019acc6055", + "status": "verified" + }, + { + "file": "builtins/AddInteger.nash", + "module": "AddInteger", + "name": "successor never wraps at any integer width", + "source_sha256": "3a8e3aa12a6c780234160c664ae3c4365d707fbb3f945ebd9bf7e1019acc6055", + "status": "verified" + }, + { + "file": "builtins/AddInteger.nash", + "module": "AddInteger", + "name": "boxed successor never wraps at any integer width", + "source_sha256": "3a8e3aa12a6c780234160c664ae3c4365d707fbb3f945ebd9bf7e1019acc6055", + "status": "verified" + }, + { + "file": "builtins/AddInteger.nash", + "module": "AddInteger", + "name": "crosses signed 64-bit boundary", + "source_sha256": "3a8e3aa12a6c780234160c664ae3c4365d707fbb3f945ebd9bf7e1019acc6055", + "status": "verified" + }, + { + "file": "builtins/AddInteger.nash", + "module": "AddInteger", + "name": "crosses unsigned 128-bit boundary", + "source_sha256": "3a8e3aa12a6c780234160c664ae3c4365d707fbb3f945ebd9bf7e1019acc6055", + "status": "verified" + }, + { + "file": "builtins/AddInteger.nash", + "module": "AddInteger", + "name": "crosses negative signed 64-bit boundary", + "source_sha256": "3a8e3aa12a6c780234160c664ae3c4365d707fbb3f945ebd9bf7e1019acc6055", + "status": "verified" + }, + { + "file": "builtins/AddInteger.nash", + "module": "AddInteger", + "name": "detects wrong opcode argument order or signedness", + "source_sha256": "3a8e3aa12a6c780234160c664ae3c4365d707fbb3f945ebd9bf7e1019acc6055", + "status": "verified" + }, + { + "file": "builtins/AndByteString.nash", + "module": "AndByteString", + "name": "commutative for both padding modes", + "source_sha256": "efd4079cba62107e8aef8811d504c9706243469ed49cc2d9ed9435a30284ccac", + "status": "not_checked" + }, + { + "file": "builtins/AndByteString.nash", + "module": "AndByteString", + "name": "self law", + "source_sha256": "efd4079cba62107e8aef8811d504c9706243469ed49cc2d9ed9435a30284ccac", + "status": "not_checked" + }, + { + "file": "builtins/AndByteString.nash", + "module": "AndByteString", + "name": "truncating mode with empty input", + "source_sha256": "efd4079cba62107e8aef8811d504c9706243469ed49cc2d9ed9435a30284ccac", + "status": "not_checked" + }, + { + "file": "builtins/AppendByteString.nash", + "module": "AppendByteString", + "name": "left and right identity", + "source_sha256": "dcbd160b48c1ca7c81c01c936646b0e6ed41c74e2bc874bc3b3e47dd22c2b90c", + "status": "not_checked" + }, + { + "file": "builtins/AppendByteString.nash", + "module": "AppendByteString", + "name": "associative", + "source_sha256": "dcbd160b48c1ca7c81c01c936646b0e6ed41c74e2bc874bc3b3e47dd22c2b90c", + "status": "not_checked" + }, + { + "file": "builtins/AppendByteString.nash", + "module": "AppendByteString", + "name": "length is additive", + "source_sha256": "dcbd160b48c1ca7c81c01c936646b0e6ed41c74e2bc874bc3b3e47dd22c2b90c", + "status": "not_checked" + }, + { + "file": "builtins/AppendByteString.nash", + "module": "AppendByteString", + "name": "preserves byte order", + "source_sha256": "dcbd160b48c1ca7c81c01c936646b0e6ed41c74e2bc874bc3b3e47dd22c2b90c", + "status": "not_checked" + }, + { + "file": "builtins/AppendString.nash", + "module": "AppendString", + "name": "left and right identity", + "source_sha256": "add6ff09c26954fa30855ad4f902097165b61abf971125243748b7b40ca0769e", + "status": "not_checked" + }, + { + "file": "builtins/AppendString.nash", + "module": "AppendString", + "name": "associative", + "source_sha256": "add6ff09c26954fa30855ad4f902097165b61abf971125243748b7b40ca0769e", + "status": "not_checked" + }, + { + "file": "builtins/AppendString.nash", + "module": "AppendString", + "name": "preserves Unicode characters", + "source_sha256": "add6ff09c26954fa30855ad4f902097165b61abf971125243748b7b40ca0769e", + "status": "not_checked" + }, + { + "file": "builtins/BData.nash", + "module": "BData", + "name": "bytes roundtrip", + "source_sha256": "5a2845a995de08dd4f527c3cdeafbb18cf5433b64af407dfe55c81ec096cb386", + "status": "not_checked" + }, + { + "file": "builtins/Blake2b224.nash", + "module": "Blake2b224", + "name": "digest has the specified byte length", + "source_sha256": "ef21cb62ea26311156278cb4e84f5f6ecff493e9dac19cf95dcfff72924ce331", + "status": "not_checked" + }, + { + "file": "builtins/Blake2b224.nash", + "module": "Blake2b224", + "name": "empty message known answer detects the wrong hash primitive", + "source_sha256": "ef21cb62ea26311156278cb4e84f5f6ecff493e9dac19cf95dcfff72924ce331", + "status": "not_checked" + }, + { + "file": "builtins/Blake2b256.nash", + "module": "Blake2b256", + "name": "digest has the specified byte length", + "source_sha256": "ef2ccf54332c8873fcc8a4d34025b3c7e383c0c26dfbe3457c6d5b12f22ee194", + "status": "not_checked" + }, + { + "file": "builtins/Blake2b256.nash", + "module": "Blake2b256", + "name": "empty message known answer detects the wrong hash primitive", + "source_sha256": "ef2ccf54332c8873fcc8a4d34025b3c7e383c0c26dfbe3457c6d5b12f22ee194", + "status": "not_checked" + }, + { + "file": "builtins/Bls12381FinalVerify.nash", + "module": "Bls12381FinalVerify", + "name": "reflexive on valid Miller loop results", + "source_sha256": "f107a395e42fa64c1a8052bfc1457ef9ea042a9423c6ae4a0b8add8bbee4fd64", + "status": "not_checked" + }, + { + "file": "builtins/Bls12381G1Add.nash", + "module": "Bls12381G1Add", + "name": "commutative on valid decoded points", + "source_sha256": "dc1eaa2bc2e486734c79b72c9ad017711d429a0ad4e122735d320cc91815d329", + "status": "not_checked" + }, + { + "file": "builtins/Bls12381G1Compress.nash", + "module": "Bls12381G1Compress", + "name": "fixed compressed width on valid points", + "source_sha256": "e819f19f9d112c3343f635ffeea1571bf97eebf777ba22463867513964e3e94a", + "status": "not_checked" + }, + { + "file": "builtins/Bls12381G1Equal.nash", + "module": "Bls12381G1Equal", + "name": "reflexive on valid points", + "source_sha256": "e5cfe4e38f147f8cac205bdbf0f9c45371cdf15622740280d21d9aa5bee1750b", + "status": "not_checked" + }, + { + "file": "builtins/Bls12381G1HashToGroup.nash", + "module": "Bls12381G1HashToGroup", + "name": "hash to group yields a compressed point of fixed width", + "source_sha256": "38a59566e74c78fca4de560b56ec4e86db6352256fec9178b4843f984582fc62", + "status": "not_checked" + }, + { + "file": "builtins/Bls12381G1HashToGroup.nash", + "module": "Bls12381G1HashToGroup", + "name": "oversized domain separator rejects", + "source_sha256": "38a59566e74c78fca4de560b56ec4e86db6352256fec9178b4843f984582fc62", + "status": "backend_error", + "reason": "The SMT translator does not support an encountered Fin/BitVec datatype." + }, + { + "file": "builtins/Bls12381G1Neg.nash", + "module": "Bls12381G1Neg", + "name": "negation is an involution on valid points", + "source_sha256": "12c2b4e0583cee36c3c316cda4482fb742a7d1782adb2ecef5cd2270a6454598", + "status": "not_checked" + }, + { + "file": "builtins/Bls12381G1ScalarMul.nash", + "module": "Bls12381G1ScalarMul", + "name": "scalar one is the identity on valid points", + "source_sha256": "84fe446b0e5cf29823e19d62e8c4ffaf1c0340bf09a7422f4c0a96f05e05d4d6", + "status": "not_checked" + }, + { + "file": "builtins/Bls12381G1Uncompress.nash", + "module": "Bls12381G1Uncompress", + "name": "canonical compressed roundtrip on valid encodings", + "source_sha256": "f7ccc0ef2477b7cc6f2d4048d1be6dabc30dbec96b24362e3b483d90aeb161ee", + "status": "not_checked" + }, + { + "file": "builtins/Bls12381G1Uncompress.nash", + "module": "Bls12381G1Uncompress", + "name": "empty encoding rejects", + "source_sha256": "f7ccc0ef2477b7cc6f2d4048d1be6dabc30dbec96b24362e3b483d90aeb161ee", + "status": "backend_error", + "reason": "The SMT translator does not support an encountered Fin/BitVec datatype." + }, + { + "file": "builtins/Bls12381G1Uncompress.nash", + "module": "Bls12381G1Uncompress", + "name": "wrong compressed width rejects", + "source_sha256": "f7ccc0ef2477b7cc6f2d4048d1be6dabc30dbec96b24362e3b483d90aeb161ee", + "status": "backend_error", + "reason": "The SMT translator does not support an encountered Fin/BitVec datatype." + }, + { + "file": "builtins/Bls12381G1Uncompress.nash", + "module": "Bls12381G1Uncompress", + "name": "canonical infinity is accepted", + "source_sha256": "f7ccc0ef2477b7cc6f2d4048d1be6dabc30dbec96b24362e3b483d90aeb161ee", + "status": "not_checked" + }, + { + "file": "builtins/Bls12381G2Add.nash", + "module": "Bls12381G2Add", + "name": "commutative on valid decoded points", + "source_sha256": "15798ba1820b5ba559e845b348673484f7b4d2c122b5e0f56a4d364b191acb6b", + "status": "not_checked" + }, + { + "file": "builtins/Bls12381G2Compress.nash", + "module": "Bls12381G2Compress", + "name": "fixed compressed width on valid points", + "source_sha256": "8eccd44502c9bc82c31efcffa247a9157d5a2c1162cdfe2569db72ee8b470a67", + "status": "not_checked" + }, + { + "file": "builtins/Bls12381G2Equal.nash", + "module": "Bls12381G2Equal", + "name": "reflexive on valid points", + "source_sha256": "d068c460205c020a029ba083e7b3226ad6a6cb405e27b556969e21a601470df7", + "status": "not_checked" + }, + { + "file": "builtins/Bls12381G2HashToGroup.nash", + "module": "Bls12381G2HashToGroup", + "name": "hash to group yields a compressed point of fixed width", + "source_sha256": "d8397e7e897801964b2738c79c0bf1304619894038015753d055d2fc0b48a3ce", + "status": "not_checked" + }, + { + "file": "builtins/Bls12381G2HashToGroup.nash", + "module": "Bls12381G2HashToGroup", + "name": "oversized domain separator rejects", + "source_sha256": "d8397e7e897801964b2738c79c0bf1304619894038015753d055d2fc0b48a3ce", + "status": "backend_error", + "reason": "The SMT translator does not support an encountered Fin/BitVec datatype." + }, + { + "file": "builtins/Bls12381G2Neg.nash", + "module": "Bls12381G2Neg", + "name": "negation is an involution on valid points", + "source_sha256": "9dfef88a94225db2b4460da3bc97f1def176238813f4fcf5fdf562ae9a34532a", + "status": "not_checked" + }, + { + "file": "builtins/Bls12381G2ScalarMul.nash", + "module": "Bls12381G2ScalarMul", + "name": "scalar one is the identity on valid points", + "source_sha256": "052ca2e508cb66bc31d904cb20133ecc0dd71e4c445966cef7dc5eddfe46221e", + "status": "not_checked" + }, + { + "file": "builtins/Bls12381G2Uncompress.nash", + "module": "Bls12381G2Uncompress", + "name": "canonical compressed roundtrip on valid encodings", + "source_sha256": "fd9927b451e08242761632df367dfaa067ed588f4b45ff7b5edb28cf4ea0905a", + "status": "not_checked" + }, + { + "file": "builtins/Bls12381G2Uncompress.nash", + "module": "Bls12381G2Uncompress", + "name": "empty encoding rejects", + "source_sha256": "fd9927b451e08242761632df367dfaa067ed588f4b45ff7b5edb28cf4ea0905a", + "status": "backend_error", + "reason": "The SMT translator does not support an encountered Fin/BitVec datatype." + }, + { + "file": "builtins/Bls12381G2Uncompress.nash", + "module": "Bls12381G2Uncompress", + "name": "wrong compressed width rejects", + "source_sha256": "fd9927b451e08242761632df367dfaa067ed588f4b45ff7b5edb28cf4ea0905a", + "status": "backend_error", + "reason": "The SMT translator does not support an encountered Fin/BitVec datatype." + }, + { + "file": "builtins/Bls12381G2Uncompress.nash", + "module": "Bls12381G2Uncompress", + "name": "canonical infinity is accepted", + "source_sha256": "fd9927b451e08242761632df367dfaa067ed588f4b45ff7b5edb28cf4ea0905a", + "status": "not_checked" + }, + { + "file": "builtins/Bls12381MillerLoop.nash", + "module": "Bls12381MillerLoop", + "name": "pairing the same points gives equivalent results", + "source_sha256": "4d3da5021b0bf48103eb9a0ab4c4ace51ef81a7865f37a9693b9c0140c840cec", + "status": "not_checked" + }, + { + "file": "builtins/Bls12381MulMlResult.nash", + "module": "Bls12381MulMlResult", + "name": "commutative under final verification", + "source_sha256": "fcf1e621713613f2884b873c102f896850ebccefc7700f2c24d78908f228bb0c", + "status": "not_checked" + }, + { + "file": "builtins/ByteStringToInteger.nash", + "module": "ByteStringToInteger", + "name": "empty bytes mean zero", + "source_sha256": "c40f41663cf37c0899c2d59aed9b1492e35b392ffb5983970dbedab421afe79f", + "status": "not_checked" + }, + { + "file": "builtins/ByteStringToInteger.nash", + "module": "ByteStringToInteger", + "name": "endianness", + "source_sha256": "c40f41663cf37c0899c2d59aed9b1492e35b392ffb5983970dbedab421afe79f", + "status": "not_checked" + }, + { + "file": "builtins/ByteStringToInteger.nash", + "module": "ByteStringToInteger", + "name": "arbitrary width conversion stays nonnegative", + "source_sha256": "c40f41663cf37c0899c2d59aed9b1492e35b392ffb5983970dbedab421afe79f", + "status": "not_checked" + }, + { + "file": "builtins/ByteStringToInteger.nash", + "module": "ByteStringToInteger", + "name": "leading zero does not alter big endian value", + "source_sha256": "c40f41663cf37c0899c2d59aed9b1492e35b392ffb5983970dbedab421afe79f", + "status": "not_checked" + }, + { + "file": "builtins/ChooseData.nash", + "module": "ChooseData", + "name": "selects the constructor branch", + "source_sha256": "3720d8f49a22b3127811717964b98c31a5d46e614d62cc8407aeb2138e2c02f4", + "status": "not_checked" + }, + { + "file": "builtins/ChooseData.nash", + "module": "ChooseData", + "name": "selects the map branch", + "source_sha256": "3720d8f49a22b3127811717964b98c31a5d46e614d62cc8407aeb2138e2c02f4", + "status": "not_checked" + }, + { + "file": "builtins/ChooseData.nash", + "module": "ChooseData", + "name": "selects the list branch", + "source_sha256": "3720d8f49a22b3127811717964b98c31a5d46e614d62cc8407aeb2138e2c02f4", + "status": "not_checked" + }, + { + "file": "builtins/ChooseData.nash", + "module": "ChooseData", + "name": "selects the integer branch", + "source_sha256": "3720d8f49a22b3127811717964b98c31a5d46e614d62cc8407aeb2138e2c02f4", + "status": "not_checked" + }, + { + "file": "builtins/ChooseData.nash", + "module": "ChooseData", + "name": "selects the bytes branch", + "source_sha256": "3720d8f49a22b3127811717964b98c31a5d46e614d62cc8407aeb2138e2c02f4", + "status": "not_checked" + }, + { + "file": "builtins/ChooseList.nash", + "module": "ChooseList", + "name": "empty list chooses the empty branch", + "source_sha256": "0164fd136bbe25b2f0be56c84d6223d7e6e8ece44db1c1d42189b9432f2e4f3f", + "status": "not_checked" + }, + { + "file": "builtins/ChooseList.nash", + "module": "ChooseList", + "name": "nonempty list chooses the nonempty branch", + "source_sha256": "0164fd136bbe25b2f0be56c84d6223d7e6e8ece44db1c1d42189b9432f2e4f3f", + "status": "not_checked" + }, + { + "file": "builtins/ChooseList.nash", + "module": "ChooseList", + "name": "detects wrong opcode argument order or signedness", + "source_sha256": "0164fd136bbe25b2f0be56c84d6223d7e6e8ece44db1c1d42189b9432f2e4f3f", + "status": "not_checked" + }, + { + "file": "builtins/ChooseUnit.nash", + "module": "ChooseUnit", + "name": "returns the second argument", + "source_sha256": "2d3042cb4cfbacbc0680f60d11fc6aed8cf12c13fa802683871b36929e0a3eff", + "status": "not_checked" + }, + { + "file": "builtins/ChooseUnit.nash", + "module": "ChooseUnit", + "name": "detects wrong opcode argument order or signedness", + "source_sha256": "2d3042cb4cfbacbc0680f60d11fc6aed8cf12c13fa802683871b36929e0a3eff", + "status": "not_checked" + }, + { + "file": "builtins/ComplementByteString.nash", + "module": "ComplementByteString", + "name": "involution", + "source_sha256": "63a14d511e64af8c599c2bde12d1af7a8e19f42bce07667506bd895f91316ccd", + "status": "not_checked" + }, + { + "file": "builtins/ComplementByteString.nash", + "module": "ComplementByteString", + "name": "preserves length", + "source_sha256": "63a14d511e64af8c599c2bde12d1af7a8e19f42bce07667506bd895f91316ccd", + "status": "not_checked" + }, + { + "file": "builtins/ComplementByteString.nash", + "module": "ComplementByteString", + "name": "complements every bit", + "source_sha256": "63a14d511e64af8c599c2bde12d1af7a8e19f42bce07667506bd895f91316ccd", + "status": "not_checked" + }, + { + "file": "builtins/ConsByteString.nash", + "module": "ConsByteString", + "name": "prepends zero and preserves length", + "source_sha256": "aca6a7be3d9836fd97cbee1f5889ab24b70a2583ed51be9a416387656249707f", + "status": "not_checked" + }, + { + "file": "builtins/ConsByteString.nash", + "module": "ConsByteString", + "name": "valid byte survives at the head", + "source_sha256": "aca6a7be3d9836fd97cbee1f5889ab24b70a2583ed51be9a416387656249707f", + "status": "not_checked" + }, + { + "file": "builtins/ConsByteString.nash", + "module": "ConsByteString", + "name": "negative byte rejects in Plutus V3", + "source_sha256": "aca6a7be3d9836fd97cbee1f5889ab24b70a2583ed51be9a416387656249707f", + "status": "verified" + }, + { + "file": "builtins/ConsByteString.nash", + "module": "ConsByteString", + "name": "byte above 255 rejects in Plutus V3", + "source_sha256": "aca6a7be3d9836fd97cbee1f5889ab24b70a2583ed51be9a416387656249707f", + "status": "verified" + }, + { + "file": "builtins/ConsByteString.nash", + "module": "ConsByteString", + "name": "all negative byte values reject", + "source_sha256": "aca6a7be3d9836fd97cbee1f5889ab24b70a2583ed51be9a416387656249707f", + "status": "timeout", + "reason": "Did not finish within the wall limit; no proof or rejection witness claimed." + }, + { + "file": "builtins/ConsByteString.nash", + "module": "ConsByteString", + "name": "all byte values above 255 reject", + "source_sha256": "aca6a7be3d9836fd97cbee1f5889ab24b70a2583ed51be9a416387656249707f", + "status": "timeout", + "reason": "Did not finish within the wall limit; no proof or rejection witness claimed." + }, + { + "file": "builtins/ConstrData.nash", + "module": "ConstrData", + "name": "constructor tag and fields survive roundtrip", + "source_sha256": "fcba123af0e6a1765a70b81ab2a04d3110e2311f66d2ab0d1a34cae1c1885cbd", + "status": "not_checked" + }, + { + "file": "builtins/CountSetBits.nash", + "module": "CountSetBits", + "name": "count is bounded by the bit width", + "source_sha256": "8d617db7d1ccbec70297d29137de18782847c03da97c9c708d59ddaf24c2ba67", + "status": "not_checked" + }, + { + "file": "builtins/CountSetBits.nash", + "module": "CountSetBits", + "name": "known counts", + "source_sha256": "8d617db7d1ccbec70297d29137de18782847c03da97c9c708d59ddaf24c2ba67", + "status": "not_checked" + }, + { + "file": "builtins/DecodeUtf8.nash", + "module": "DecodeUtf8", + "name": "inverse on encoded strings", + "source_sha256": "991556c25a18fe32d2b8e1ac4d165b2be8460508b09c10b9170c8048593aa087", + "status": "not_checked" + }, + { + "file": "builtins/DecodeUtf8.nash", + "module": "DecodeUtf8", + "name": "non ASCII decoding", + "source_sha256": "991556c25a18fe32d2b8e1ac4d165b2be8460508b09c10b9170c8048593aa087", + "status": "not_checked" + }, + { + "file": "builtins/DecodeUtf8.nash", + "module": "DecodeUtf8", + "name": "invalid UTF8 rejects", + "source_sha256": "991556c25a18fe32d2b8e1ac4d165b2be8460508b09c10b9170c8048593aa087", + "status": "verified" + }, + { + "file": "builtins/DecodeUtf8.nash", + "module": "DecodeUtf8", + "name": "truncated UTF8 rejects", + "source_sha256": "991556c25a18fe32d2b8e1ac4d165b2be8460508b09c10b9170c8048593aa087", + "status": "timeout", + "reason": "Did not finish within the wall limit; no proof or rejection witness claimed." + }, + { + "file": "builtins/DecodeUtf8.nash", + "module": "DecodeUtf8", + "name": "overlong UTF8 is rejected", + "source_sha256": "991556c25a18fe32d2b8e1ac4d165b2be8460508b09c10b9170c8048593aa087", + "status": "timeout", + "reason": "Did not finish within the wall limit; no proof or rejection witness claimed." + }, + { + "file": "builtins/DecodeUtf8.nash", + "module": "DecodeUtf8", + "name": "UTF8 surrogate codepoints are rejected", + "source_sha256": "991556c25a18fe32d2b8e1ac4d165b2be8460508b09c10b9170c8048593aa087", + "status": "timeout", + "reason": "Did not finish within the wall limit; no proof or rejection witness claimed." + }, + { + "file": "builtins/DecodeUtf8.nash", + "module": "DecodeUtf8", + "name": "codepoint above Unicode maximum is rejected", + "source_sha256": "991556c25a18fe32d2b8e1ac4d165b2be8460508b09c10b9170c8048593aa087", + "status": "timeout", + "reason": "Did not finish within the wall limit; no proof or rejection witness claimed." + }, + { + "file": "builtins/DivideInteger.nash", + "module": "DivideInteger", + "name": "division by one", + "source_sha256": "7e1241661337239cf0a5bf1b840566d486efaca0209822688f0575a94dcca83c", + "status": "verified" + }, + { + "file": "builtins/DivideInteger.nash", + "module": "DivideInteger", + "name": "quotient and remainder reconstruct the dividend", + "source_sha256": "7e1241661337239cf0a5bf1b840566d486efaca0209822688f0575a94dcca83c", + "status": "timeout", + "reason": "Did not finish within the wall limit; no proof or rejection witness claimed." + }, + { + "file": "builtins/DivideInteger.nash", + "module": "DivideInteger", + "name": "negative rounding convention", + "source_sha256": "7e1241661337239cf0a5bf1b840566d486efaca0209822688f0575a94dcca83c", + "status": "verified" + }, + { + "file": "builtins/DivideInteger.nash", + "module": "DivideInteger", + "name": "zero divisor rejects every dividend", + "source_sha256": "7e1241661337239cf0a5bf1b840566d486efaca0209822688f0575a94dcca83c", + "status": "verified" + }, + { + "file": "builtins/DivideInteger.nash", + "module": "DivideInteger", + "name": "detects wrong opcode argument order or signedness", + "source_sha256": "7e1241661337239cf0a5bf1b840566d486efaca0209822688f0575a94dcca83c", + "status": "verified" + }, + { + "file": "builtins/DropList.nash", + "module": "DropList", + "name": "dropping zero preserves the head", + "source_sha256": "229f63138341cf7a42d5a229475bfbf78e44e945c7301f936ea1389d902c89fb", + "status": "not_checked" + }, + { + "file": "builtins/DropList.nash", + "module": "DropList", + "name": "dropping one preserves the next element", + "source_sha256": "229f63138341cf7a42d5a229475bfbf78e44e945c7301f936ea1389d902c89fb", + "status": "not_checked" + }, + { + "file": "builtins/DropList.nash", + "module": "DropList", + "name": "dropping the full list gives empty", + "source_sha256": "229f63138341cf7a42d5a229475bfbf78e44e945c7301f936ea1389d902c89fb", + "status": "not_checked" + }, + { + "file": "builtins/DropList.nash", + "module": "DropList", + "name": "negative count preserves the list", + "source_sha256": "229f63138341cf7a42d5a229475bfbf78e44e945c7301f936ea1389d902c89fb", + "status": "not_checked" + }, + { + "file": "builtins/DropList.nash", + "module": "DropList", + "name": "count beyond length gives empty", + "source_sha256": "229f63138341cf7a42d5a229475bfbf78e44e945c7301f936ea1389d902c89fb", + "status": "not_checked" + }, + { + "file": "builtins/EncodeUtf8.nash", + "module": "EncodeUtf8", + "name": "decode after encoding", + "source_sha256": "4959bcf9829ea6613f9f365f324f8118ca48a96ce4f3a06e91ccbb16714138e7", + "status": "not_checked" + }, + { + "file": "builtins/EncodeUtf8.nash", + "module": "EncodeUtf8", + "name": "append is preserved", + "source_sha256": "4959bcf9829ea6613f9f365f324f8118ca48a96ce4f3a06e91ccbb16714138e7", + "status": "not_checked" + }, + { + "file": "builtins/EncodeUtf8.nash", + "module": "EncodeUtf8", + "name": "non ASCII encoding", + "source_sha256": "4959bcf9829ea6613f9f365f324f8118ca48a96ce4f3a06e91ccbb16714138e7", + "status": "not_checked" + }, + { + "file": "builtins/EncodeUtf8.nash", + "module": "EncodeUtf8", + "name": "embedded zero is preserved", + "source_sha256": "4959bcf9829ea6613f9f365f324f8118ca48a96ce4f3a06e91ccbb16714138e7", + "status": "not_checked" + }, + { + "file": "builtins/EqualsByteString.nash", + "module": "EqualsByteString", + "name": "reflexive", + "source_sha256": "263083b016df90e5cfb513d04b19341ad6f9e6f917af7294de24554a7b34c8c3", + "status": "not_checked" + }, + { + "file": "builtins/EqualsByteString.nash", + "module": "EqualsByteString", + "name": "symmetric", + "source_sha256": "263083b016df90e5cfb513d04b19341ad6f9e6f917af7294de24554a7b34c8c3", + "status": "not_checked" + }, + { + "file": "builtins/EqualsByteString.nash", + "module": "EqualsByteString", + "name": "transitive", + "source_sha256": "263083b016df90e5cfb513d04b19341ad6f9e6f917af7294de24554a7b34c8c3", + "status": "not_checked" + }, + { + "file": "builtins/EqualsByteString.nash", + "module": "EqualsByteString", + "name": "detects wrong opcode argument order or signedness", + "source_sha256": "263083b016df90e5cfb513d04b19341ad6f9e6f917af7294de24554a7b34c8c3", + "status": "not_checked" + }, + { + "file": "builtins/EqualsData.nash", + "module": "EqualsData", + "name": "reflexive", + "source_sha256": "cb5a90908258c4d48d2b4bc04b1b0c02a34b159bdea9f7975a140b2e8ef0408f", + "status": "not_checked" + }, + { + "file": "builtins/EqualsData.nash", + "module": "EqualsData", + "name": "symmetric", + "source_sha256": "cb5a90908258c4d48d2b4bc04b1b0c02a34b159bdea9f7975a140b2e8ef0408f", + "status": "not_checked" + }, + { + "file": "builtins/EqualsData.nash", + "module": "EqualsData", + "name": "transitive", + "source_sha256": "cb5a90908258c4d48d2b4bc04b1b0c02a34b159bdea9f7975a140b2e8ef0408f", + "status": "not_checked" + }, + { + "file": "builtins/EqualsData.nash", + "module": "EqualsData", + "name": "detects wrong opcode argument order or signedness", + "source_sha256": "cb5a90908258c4d48d2b4bc04b1b0c02a34b159bdea9f7975a140b2e8ef0408f", + "status": "not_checked" + }, + { + "file": "builtins/EqualsInteger.nash", + "module": "EqualsInteger", + "name": "reflexive", + "source_sha256": "a68aecf09ad5a8173bdbf662adbeeb04b7cf59029857c1a5e2f76e850b7f3816", + "status": "verified" + }, + { + "file": "builtins/EqualsInteger.nash", + "module": "EqualsInteger", + "name": "symmetric", + "source_sha256": "a68aecf09ad5a8173bdbf662adbeeb04b7cf59029857c1a5e2f76e850b7f3816", + "status": "timeout", + "reason": "Did not finish within the wall limit; no proof or rejection witness claimed." + }, + { + "file": "builtins/EqualsInteger.nash", + "module": "EqualsInteger", + "name": "equivalent to zero difference", + "source_sha256": "a68aecf09ad5a8173bdbf662adbeeb04b7cf59029857c1a5e2f76e850b7f3816", + "status": "timeout", + "reason": "Did not finish within the wall limit; no proof or rejection witness claimed." + }, + { + "file": "builtins/EqualsInteger.nash", + "module": "EqualsInteger", + "name": "successive integers are distinct at every width", + "source_sha256": "a68aecf09ad5a8173bdbf662adbeeb04b7cf59029857c1a5e2f76e850b7f3816", + "status": "timeout", + "reason": "Did not finish within the wall limit; no proof or rejection witness claimed." + }, + { + "file": "builtins/EqualsInteger.nash", + "module": "EqualsInteger", + "name": "detects wrong opcode argument order or signedness", + "source_sha256": "a68aecf09ad5a8173bdbf662adbeeb04b7cf59029857c1a5e2f76e850b7f3816", + "status": "verified" + }, + { + "file": "builtins/EqualsString.nash", + "module": "EqualsString", + "name": "reflexive", + "source_sha256": "f5300d7fbf6468ff29a2d6d79e27275366cabe8bcefc03929098d9005f67ba91", + "status": "not_checked" + }, + { + "file": "builtins/EqualsString.nash", + "module": "EqualsString", + "name": "symmetric", + "source_sha256": "f5300d7fbf6468ff29a2d6d79e27275366cabe8bcefc03929098d9005f67ba91", + "status": "not_checked" + }, + { + "file": "builtins/EqualsString.nash", + "module": "EqualsString", + "name": "transitive", + "source_sha256": "f5300d7fbf6468ff29a2d6d79e27275366cabe8bcefc03929098d9005f67ba91", + "status": "not_checked" + }, + { + "file": "builtins/EqualsString.nash", + "module": "EqualsString", + "name": "detects wrong opcode argument order or signedness", + "source_sha256": "f5300d7fbf6468ff29a2d6d79e27275366cabe8bcefc03929098d9005f67ba91", + "status": "not_checked" + }, + { + "file": "builtins/ExpModInteger.nash", + "module": "ExpModInteger", + "name": "exponent zero gives the modular identity", + "source_sha256": "23193aad0cd956725b2a816fbea6a37caa2fecb7c1b9427e008bace7aa6df468", + "status": "not_checked" + }, + { + "file": "builtins/ExpModInteger.nash", + "module": "ExpModInteger", + "name": "known modular power", + "source_sha256": "23193aad0cd956725b2a816fbea6a37caa2fecb7c1b9427e008bace7aa6df468", + "status": "not_checked" + }, + { + "file": "builtins/ExpModInteger.nash", + "module": "ExpModInteger", + "name": "zero modulus rejects", + "source_sha256": "23193aad0cd956725b2a816fbea6a37caa2fecb7c1b9427e008bace7aa6df468", + "status": "counterexample_in_opaque_model", + "reason": "The SMT interpretation assigns unconstrained behavior to the opaque cryptographic primitive; this is not a concrete Nash counterexample." + }, + { + "file": "builtins/ExpModInteger.nash", + "module": "ExpModInteger", + "name": "negative modulus rejects", + "source_sha256": "23193aad0cd956725b2a816fbea6a37caa2fecb7c1b9427e008bace7aa6df468", + "status": "counterexample_in_opaque_model", + "reason": "The SMT interpretation assigns unconstrained behavior to the opaque cryptographic primitive; this is not a concrete Nash counterexample." + }, + { + "file": "builtins/ExpModInteger.nash", + "module": "ExpModInteger", + "name": "missing modular inverse rejects", + "source_sha256": "23193aad0cd956725b2a816fbea6a37caa2fecb7c1b9427e008bace7aa6df468", + "status": "counterexample_in_opaque_model", + "reason": "The SMT interpretation assigns unconstrained behavior to the opaque cryptographic primitive; this is not a concrete Nash counterexample." + }, + { + "file": "builtins/ExpModInteger.nash", + "module": "ExpModInteger", + "name": "negative exponent uses modular inverse", + "source_sha256": "23193aad0cd956725b2a816fbea6a37caa2fecb7c1b9427e008bace7aa6df468", + "status": "not_checked" + }, + { + "file": "builtins/FindFirstSetBit.nash", + "module": "FindFirstSetBit", + "name": "minus one exactly for all zero bits", + "source_sha256": "b914a1e7e9b08349d74bb1646f46eae186c06cab6738c3b38937f6144ad0704d", + "status": "not_checked" + }, + { + "file": "builtins/FindFirstSetBit.nash", + "module": "FindFirstSetBit", + "name": "least significant indexing", + "source_sha256": "b914a1e7e9b08349d74bb1646f46eae186c06cab6738c3b38937f6144ad0704d", + "status": "not_checked" + }, + { + "file": "builtins/FstPair.nash", + "module": "FstPair", + "name": "projects the correct component", + "source_sha256": "c5276f530af614f07ac935e3f128a69fa97a23967423b3dcfb7d8dc80a1984ce", + "status": "not_checked" + }, + { + "file": "builtins/HeadList.nash", + "module": "HeadList", + "name": "head of a nonempty list", + "source_sha256": "c67e1b3fe1aeb093895803b406b9e3ab07aa13ce3c8e817ee7800bc8cf7e5385", + "status": "not_checked" + }, + { + "file": "builtins/HeadList.nash", + "module": "HeadList", + "name": "empty list rejects", + "source_sha256": "c67e1b3fe1aeb093895803b406b9e3ab07aa13ce3c8e817ee7800bc8cf7e5385", + "status": "verified" + }, + { + "file": "builtins/IData.nash", + "module": "IData", + "name": "integer roundtrip has no width bound", + "source_sha256": "46e7ff3cd58fb4caf6ed3a64e1213e946d22c5168f8ce5fd96de09106ef650a2", + "status": "not_checked" + }, + { + "file": "builtins/IfThenElse.nash", + "module": "IfThenElse", + "name": "selects the branch indicated by the condition", + "source_sha256": "dfd1580aecf46f091410b87bc64023946ccd4a2b8f7fc88a196e13d635a1ea8b", + "status": "not_checked" + }, + { + "file": "builtins/IfThenElse.nash", + "module": "IfThenElse", + "name": "selects arbitrary Data values", + "source_sha256": "dfd1580aecf46f091410b87bc64023946ccd4a2b8f7fc88a196e13d635a1ea8b", + "status": "not_checked" + }, + { + "file": "builtins/IfThenElse.nash", + "module": "IfThenElse", + "name": "unselected arguments are still evaluated", + "source_sha256": "dfd1580aecf46f091410b87bc64023946ccd4a2b8f7fc88a196e13d635a1ea8b", + "status": "not_checked" + }, + { + "file": "builtins/IfThenElse.nash", + "module": "IfThenElse", + "name": "detects wrong opcode argument order or signedness", + "source_sha256": "dfd1580aecf46f091410b87bc64023946ccd4a2b8f7fc88a196e13d635a1ea8b", + "status": "not_checked" + }, + { + "file": "builtins/IndexArray.nash", + "module": "IndexArray", + "name": "selects each position", + "source_sha256": "f7e6bac94e0624ab2d287977b22610a445dac3004d91cb5cac5702a85b91aaac", + "status": "not_checked" + }, + { + "file": "builtins/IndexArray.nash", + "module": "IndexArray", + "name": "negative index rejects", + "source_sha256": "f7e6bac94e0624ab2d287977b22610a445dac3004d91cb5cac5702a85b91aaac", + "status": "timeout", + "reason": "Did not finish within the wall limit; no proof or rejection witness claimed." + }, + { + "file": "builtins/IndexArray.nash", + "module": "IndexArray", + "name": "index equal to length rejects", + "source_sha256": "f7e6bac94e0624ab2d287977b22610a445dac3004d91cb5cac5702a85b91aaac", + "status": "timeout", + "reason": "Did not finish within the wall limit; no proof or rejection witness claimed." + }, + { + "file": "builtins/IndexArray.nash", + "module": "IndexArray", + "name": "index must not truncate to a machine word", + "source_sha256": "f7e6bac94e0624ab2d287977b22610a445dac3004d91cb5cac5702a85b91aaac", + "status": "not_checked" + }, + { + "file": "builtins/IndexArray.nash", + "module": "IndexArray", + "name": "index above 128 bits rejects without host overflow", + "source_sha256": "f7e6bac94e0624ab2d287977b22610a445dac3004d91cb5cac5702a85b91aaac", + "status": "timeout", + "reason": "Did not finish within the wall limit; no proof or rejection witness claimed." + }, + { + "file": "builtins/IndexByteString.nash", + "module": "IndexByteString", + "name": "valid indices return a byte", + "source_sha256": "5d62bea74e6a730ab523b5f5d4920663422497b2e0dced15b619c9ab03ac263f", + "status": "not_checked" + }, + { + "file": "builtins/IndexByteString.nash", + "module": "IndexByteString", + "name": "cons supplies index zero", + "source_sha256": "5d62bea74e6a730ab523b5f5d4920663422497b2e0dced15b619c9ab03ac263f", + "status": "not_checked" + }, + { + "file": "builtins/IndexByteString.nash", + "module": "IndexByteString", + "name": "negative index rejects", + "source_sha256": "5d62bea74e6a730ab523b5f5d4920663422497b2e0dced15b619c9ab03ac263f", + "status": "verified" + }, + { + "file": "builtins/IndexByteString.nash", + "module": "IndexByteString", + "name": "index equal to length rejects", + "source_sha256": "5d62bea74e6a730ab523b5f5d4920663422497b2e0dced15b619c9ab03ac263f", + "status": "backend_error", + "reason": "The SMT translator does not support an encountered Fin/BitVec datatype." + }, + { + "file": "builtins/IndexByteString.nash", + "module": "IndexByteString", + "name": "index must not truncate to a machine word", + "source_sha256": "5d62bea74e6a730ab523b5f5d4920663422497b2e0dced15b619c9ab03ac263f", + "status": "not_checked" + }, + { + "file": "builtins/IndexByteString.nash", + "module": "IndexByteString", + "name": "index above 128 bits rejects without host overflow", + "source_sha256": "5d62bea74e6a730ab523b5f5d4920663422497b2e0dced15b619c9ab03ac263f", + "status": "verified" + }, + { + "file": "builtins/InsertCoin.nash", + "module": "InsertCoin", + "name": "insert then lookup", + "source_sha256": "f8cb439f708e30b61df149c9b27b9b363b771c7735f4ea50e23f2205103fcf8e", + "status": "not_checked" + }, + { + "file": "builtins/InsertCoin.nash", + "module": "InsertCoin", + "name": "zero quantity removes the coin", + "source_sha256": "f8cb439f708e30b61df149c9b27b9b363b771c7735f4ea50e23f2205103fcf8e", + "status": "not_checked" + }, + { + "file": "builtins/InsertCoin.nash", + "module": "InsertCoin", + "name": "nonzero quantity rejects currency ID above 32 bytes", + "source_sha256": "f8cb439f708e30b61df149c9b27b9b363b771c7735f4ea50e23f2205103fcf8e", + "status": "timeout", + "reason": "Did not finish within the wall limit; no proof or rejection witness claimed." + }, + { + "file": "builtins/InsertCoin.nash", + "module": "InsertCoin", + "name": "nonzero quantity rejects token name above 32 bytes", + "source_sha256": "f8cb439f708e30b61df149c9b27b9b363b771c7735f4ea50e23f2205103fcf8e", + "status": "timeout", + "reason": "Did not finish within the wall limit; no proof or rejection witness claimed." + }, + { + "file": "builtins/InsertCoin.nash", + "module": "InsertCoin", + "name": "positive quantity above signed 128 bits rejects", + "source_sha256": "f8cb439f708e30b61df149c9b27b9b363b771c7735f4ea50e23f2205103fcf8e", + "status": "timeout", + "reason": "Did not finish within the wall limit; no proof or rejection witness claimed." + }, + { + "file": "builtins/InsertCoin.nash", + "module": "InsertCoin", + "name": "zero quantity accepts long identifiers", + "source_sha256": "f8cb439f708e30b61df149c9b27b9b363b771c7735f4ea50e23f2205103fcf8e", + "status": "not_checked" + }, + { + "file": "builtins/InsertCoin.nash", + "module": "InsertCoin", + "name": "reinsertion replaces rather than adds", + "source_sha256": "f8cb439f708e30b61df149c9b27b9b363b771c7735f4ea50e23f2205103fcf8e", + "status": "not_checked" + }, + { + "file": "builtins/IntegerToByteString.nash", + "module": "IntegerToByteString", + "name": "zero with minimal width is empty", + "source_sha256": "84e35ce21ff4b21a11cf0fe4724669c58edd027a1e81f81624f8ca781a0e6795", + "status": "not_checked" + }, + { + "file": "builtins/IntegerToByteString.nash", + "module": "IntegerToByteString", + "name": "endianness and leading padding", + "source_sha256": "84e35ce21ff4b21a11cf0fe4724669c58edd027a1e81f81624f8ca781a0e6795", + "status": "not_checked" + }, + { + "file": "builtins/IntegerToByteString.nash", + "module": "IntegerToByteString", + "name": "unsigned byte roundtrip", + "source_sha256": "84e35ce21ff4b21a11cf0fe4724669c58edd027a1e81f81624f8ca781a0e6795", + "status": "not_checked" + }, + { + "file": "builtins/IntegerToByteString.nash", + "module": "IntegerToByteString", + "name": "negative integer rejects", + "source_sha256": "84e35ce21ff4b21a11cf0fe4724669c58edd027a1e81f81624f8ca781a0e6795", + "status": "verified" + }, + { + "file": "builtins/IntegerToByteString.nash", + "module": "IntegerToByteString", + "name": "insufficient width rejects", + "source_sha256": "84e35ce21ff4b21a11cf0fe4724669c58edd027a1e81f81624f8ca781a0e6795", + "status": "verified" + }, + { + "file": "builtins/IntegerToByteString.nash", + "module": "IntegerToByteString", + "name": "negative width rejects", + "source_sha256": "84e35ce21ff4b21a11cf0fe4724669c58edd027a1e81f81624f8ca781a0e6795", + "status": "verified" + }, + { + "file": "builtins/IntegerToByteString.nash", + "module": "IntegerToByteString", + "name": "width above 8192 rejects", + "source_sha256": "84e35ce21ff4b21a11cf0fe4724669c58edd027a1e81f81624f8ca781a0e6795", + "status": "verified" + }, + { + "file": "builtins/IntegerToByteString.nash", + "module": "IntegerToByteString", + "name": "largest supported width is accepted", + "source_sha256": "84e35ce21ff4b21a11cf0fe4724669c58edd027a1e81f81624f8ca781a0e6795", + "status": "not_checked" + }, + { + "file": "builtins/Keccak256.nash", + "module": "Keccak256", + "name": "digest has the specified byte length", + "source_sha256": "0e802e224b2873a001a0743d088a2208a718ac933618ac38c0cc90d247738fb0", + "status": "not_checked" + }, + { + "file": "builtins/LengthOfArray.nash", + "module": "LengthOfArray", + "name": "empty array length", + "source_sha256": "17aec38c6ee47865ea254427800b50a96d7aa3676eae0c00ddb24c94b12f2203", + "status": "not_checked" + }, + { + "file": "builtins/LengthOfArray.nash", + "module": "LengthOfArray", + "name": "length after list conversion", + "source_sha256": "17aec38c6ee47865ea254427800b50a96d7aa3676eae0c00ddb24c94b12f2203", + "status": "not_checked" + }, + { + "file": "builtins/LengthOfByteString.nash", + "module": "LengthOfByteString", + "name": "nonnegative", + "source_sha256": "866001394f3eeebabce6eb33254a6af3eb1d5dd189d2115a7b0f983cc8a3fa43", + "status": "not_checked" + }, + { + "file": "builtins/LengthOfByteString.nash", + "module": "LengthOfByteString", + "name": "empty length is zero", + "source_sha256": "866001394f3eeebabce6eb33254a6af3eb1d5dd189d2115a7b0f983cc8a3fa43", + "status": "not_checked" + }, + { + "file": "builtins/LengthOfByteString.nash", + "module": "LengthOfByteString", + "name": "append adds lengths", + "source_sha256": "866001394f3eeebabce6eb33254a6af3eb1d5dd189d2115a7b0f983cc8a3fa43", + "status": "not_checked" + }, + { + "file": "builtins/LengthOfByteString.nash", + "module": "LengthOfByteString", + "name": "bytes are counted rather than UTF8 codepoints", + "source_sha256": "866001394f3eeebabce6eb33254a6af3eb1d5dd189d2115a7b0f983cc8a3fa43", + "status": "not_checked" + }, + { + "file": "builtins/LessThanByteString.nash", + "module": "LessThanByteString", + "name": "irreflexive", + "source_sha256": "02fd1873ff7285f9fd3b2f4e192404ea05f06ea9c91443853208aaa51923172a", + "status": "not_checked" + }, + { + "file": "builtins/LessThanByteString.nash", + "module": "LessThanByteString", + "name": "asymmetric", + "source_sha256": "02fd1873ff7285f9fd3b2f4e192404ea05f06ea9c91443853208aaa51923172a", + "status": "not_checked" + }, + { + "file": "builtins/LessThanByteString.nash", + "module": "LessThanByteString", + "name": "unsigned lexicographic ordering", + "source_sha256": "02fd1873ff7285f9fd3b2f4e192404ea05f06ea9c91443853208aaa51923172a", + "status": "not_checked" + }, + { + "file": "builtins/LessThanEqualsByteString.nash", + "module": "LessThanEqualsByteString", + "name": "reflexive", + "source_sha256": "6b00fdee94ad2b0d4cb58dcb0fdeea5bfede44e02f94ac572cca90ea7e074387", + "status": "not_checked" + }, + { + "file": "builtins/LessThanEqualsByteString.nash", + "module": "LessThanEqualsByteString", + "name": "agrees with strict order or equality", + "source_sha256": "6b00fdee94ad2b0d4cb58dcb0fdeea5bfede44e02f94ac572cca90ea7e074387", + "status": "not_checked" + }, + { + "file": "builtins/LessThanEqualsInteger.nash", + "module": "LessThanEqualsInteger", + "name": "reflexive", + "source_sha256": "dc2e5d35ca06921fb417e189e81e2bbabf18b350dfc334a25756d19848f944ec", + "status": "verified" + }, + { + "file": "builtins/LessThanEqualsInteger.nash", + "module": "LessThanEqualsInteger", + "name": "antisymmetric", + "source_sha256": "dc2e5d35ca06921fb417e189e81e2bbabf18b350dfc334a25756d19848f944ec", + "status": "timeout", + "reason": "Did not finish within the wall limit; no proof or rejection witness claimed." + }, + { + "file": "builtins/LessThanEqualsInteger.nash", + "module": "LessThanEqualsInteger", + "name": "agrees with strict order or equality", + "source_sha256": "dc2e5d35ca06921fb417e189e81e2bbabf18b350dfc334a25756d19848f944ec", + "status": "timeout", + "reason": "Did not finish within the wall limit; no proof or rejection witness claimed." + }, + { + "file": "builtins/LessThanEqualsInteger.nash", + "module": "LessThanEqualsInteger", + "name": "detects wrong opcode argument order or signedness", + "source_sha256": "dc2e5d35ca06921fb417e189e81e2bbabf18b350dfc334a25756d19848f944ec", + "status": "verified" + }, + { + "file": "builtins/LessThanInteger.nash", + "module": "LessThanInteger", + "name": "irreflexive", + "source_sha256": "7e297299a3ac48729855b3cfe216f7e9df5e0f34fb2584b5147d2a6768bfa147", + "status": "verified" + }, + { + "file": "builtins/LessThanInteger.nash", + "module": "LessThanInteger", + "name": "strict successor order at every width", + "source_sha256": "7e297299a3ac48729855b3cfe216f7e9df5e0f34fb2584b5147d2a6768bfa147", + "status": "verified" + }, + { + "file": "builtins/LessThanInteger.nash", + "module": "LessThanInteger", + "name": "asymmetric", + "source_sha256": "7e297299a3ac48729855b3cfe216f7e9df5e0f34fb2584b5147d2a6768bfa147", + "status": "timeout", + "reason": "Did not finish within the wall limit; no proof or rejection witness claimed." + }, + { + "file": "builtins/LessThanInteger.nash", + "module": "LessThanInteger", + "name": "transitive", + "source_sha256": "7e297299a3ac48729855b3cfe216f7e9df5e0f34fb2584b5147d2a6768bfa147", + "status": "timeout", + "reason": "Did not finish within the wall limit; no proof or rejection witness claimed." + }, + { + "file": "builtins/LessThanInteger.nash", + "module": "LessThanInteger", + "name": "detects wrong opcode argument order or signedness", + "source_sha256": "7e297299a3ac48729855b3cfe216f7e9df5e0f34fb2584b5147d2a6768bfa147", + "status": "verified" + }, + { + "file": "builtins/ListData.nash", + "module": "ListData", + "name": "list elements survive roundtrip", + "source_sha256": "034a0b9cdf82e37cd7fd1d26ca72dcb5a2692319b6da7f76ff79efcabc1631ad", + "status": "not_checked" + }, + { + "file": "builtins/ListToArray.nash", + "module": "ListToArray", + "name": "preserves order", + "source_sha256": "befc87b01312836833a4784f979c14ae9e3caf12af8a0a760be49fede1731526", + "status": "not_checked" + }, + { + "file": "builtins/LookupCoin.nash", + "module": "LookupCoin", + "name": "absent asset has zero quantity", + "source_sha256": "4eedfe77733162b690777a4a61bdeebeedbc40a345dbb61daebd749d874c3457", + "status": "not_checked" + }, + { + "file": "builtins/LookupCoin.nash", + "module": "LookupCoin", + "name": "returns the inserted quantity", + "source_sha256": "4eedfe77733162b690777a4a61bdeebeedbc40a345dbb61daebd749d874c3457", + "status": "not_checked" + }, + { + "file": "builtins/LookupCoin.nash", + "module": "LookupCoin", + "name": "long nonexistent identifier gives zero", + "source_sha256": "4eedfe77733162b690777a4a61bdeebeedbc40a345dbb61daebd749d874c3457", + "status": "not_checked" + }, + { + "file": "builtins/MapData.nash", + "module": "MapData", + "name": "map entry survives roundtrip", + "source_sha256": "c6934265ccc1808db2c48a083bce631b1908834aff87e91a7d74c95ff1c59f3f", + "status": "not_checked" + }, + { + "file": "builtins/MkCons.nash", + "module": "MkCons", + "name": "head of cons is its element", + "source_sha256": "bb21e43e2315d53ab0841682f90a054dbb48d9748579e1b6ae29dc401d1ed510", + "status": "not_checked" + }, + { + "file": "builtins/MkCons.nash", + "module": "MkCons", + "name": "tail of cons is its original list", + "source_sha256": "bb21e43e2315d53ab0841682f90a054dbb48d9748579e1b6ae29dc401d1ed510", + "status": "not_checked" + }, + { + "file": "builtins/MkCons.nash", + "module": "MkCons", + "name": "cons is never empty", + "source_sha256": "bb21e43e2315d53ab0841682f90a054dbb48d9748579e1b6ae29dc401d1ed510", + "status": "not_checked" + }, + { + "file": "builtins/MkNilData.nash", + "module": "MkNilData", + "name": "produces the empty list", + "source_sha256": "d5e708f1d140b27b5c9e111e6878a6670a32090ce9ca140a10ae2ab77b59588d", + "status": "not_checked" + }, + { + "file": "builtins/MkNilData.nash", + "module": "MkNilData", + "name": "empty result has no head", + "source_sha256": "d5e708f1d140b27b5c9e111e6878a6670a32090ce9ca140a10ae2ab77b59588d", + "status": "not_checked" + }, + { + "file": "builtins/MkNilPairData.nash", + "module": "MkNilPairData", + "name": "produces the empty list", + "source_sha256": "123eb956be08903b6a031fbf4dd27f1da1583f01a085d305e48ec53c803113d5", + "status": "not_checked" + }, + { + "file": "builtins/MkNilPairData.nash", + "module": "MkNilPairData", + "name": "empty result has no head", + "source_sha256": "123eb956be08903b6a031fbf4dd27f1da1583f01a085d305e48ec53c803113d5", + "status": "not_checked" + }, + { + "file": "builtins/MkPairData.nash", + "module": "MkPairData", + "name": "both projections recover their arguments", + "source_sha256": "8e4b9a314085bb303ae25b9eccb8571e983a8af4f50433fbf77feac220578d94", + "status": "not_checked" + }, + { + "file": "builtins/ModInteger.nash", + "module": "ModInteger", + "name": "remainder modulo one is zero", + "source_sha256": "c1c66b2f1ace739a8ffa2309bc9d14592cc1e3fbc5e60f4d664dc6ea3729cba6", + "status": "verified" + }, + { + "file": "builtins/ModInteger.nash", + "module": "ModInteger", + "name": "division reconstruction", + "source_sha256": "c1c66b2f1ace739a8ffa2309bc9d14592cc1e3fbc5e60f4d664dc6ea3729cba6", + "status": "timeout", + "reason": "Did not finish within the wall limit; no proof or rejection witness claimed." + }, + { + "file": "builtins/ModInteger.nash", + "module": "ModInteger", + "name": "positive modulus bounds the remainder", + "source_sha256": "c1c66b2f1ace739a8ffa2309bc9d14592cc1e3fbc5e60f4d664dc6ea3729cba6", + "status": "timeout", + "reason": "Did not finish within the wall limit; no proof or rejection witness claimed." + }, + { + "file": "builtins/ModInteger.nash", + "module": "ModInteger", + "name": "negative rounding convention", + "source_sha256": "c1c66b2f1ace739a8ffa2309bc9d14592cc1e3fbc5e60f4d664dc6ea3729cba6", + "status": "verified" + }, + { + "file": "builtins/ModInteger.nash", + "module": "ModInteger", + "name": "zero divisor rejects every dividend", + "source_sha256": "c1c66b2f1ace739a8ffa2309bc9d14592cc1e3fbc5e60f4d664dc6ea3729cba6", + "status": "verified" + }, + { + "file": "builtins/ModInteger.nash", + "module": "ModInteger", + "name": "detects wrong opcode argument order or signedness", + "source_sha256": "c1c66b2f1ace739a8ffa2309bc9d14592cc1e3fbc5e60f4d664dc6ea3729cba6", + "status": "verified" + }, + { + "file": "builtins/MultiplyInteger.nash", + "module": "MultiplyInteger", + "name": "commutative", + "source_sha256": "d2a91324f674ef643bf8cac3b0d68907147c9734eb1d55425d4a3df9fb919d23", + "status": "verified" + }, + { + "file": "builtins/MultiplyInteger.nash", + "module": "MultiplyInteger", + "name": "associative", + "source_sha256": "d2a91324f674ef643bf8cac3b0d68907147c9734eb1d55425d4a3df9fb919d23", + "status": "timeout", + "reason": "Did not finish within the wall limit; no proof or rejection witness claimed." + }, + { + "file": "builtins/MultiplyInteger.nash", + "module": "MultiplyInteger", + "name": "distributive over addition", + "source_sha256": "d2a91324f674ef643bf8cac3b0d68907147c9734eb1d55425d4a3df9fb919d23", + "status": "timeout", + "reason": "Did not finish within the wall limit; no proof or rejection witness claimed." + }, + { + "file": "builtins/MultiplyInteger.nash", + "module": "MultiplyInteger", + "name": "multiplicative identity", + "source_sha256": "d2a91324f674ef643bf8cac3b0d68907147c9734eb1d55425d4a3df9fb919d23", + "status": "verified" + }, + { + "file": "builtins/MultiplyInteger.nash", + "module": "MultiplyInteger", + "name": "zero annihilates", + "source_sha256": "d2a91324f674ef643bf8cac3b0d68907147c9734eb1d55425d4a3df9fb919d23", + "status": "verified" + }, + { + "file": "builtins/MultiplyInteger.nash", + "module": "MultiplyInteger", + "name": "square is nonnegative without machine overflow", + "source_sha256": "d2a91324f674ef643bf8cac3b0d68907147c9734eb1d55425d4a3df9fb919d23", + "status": "timeout", + "reason": "Did not finish within the wall limit; no proof or rejection witness claimed." + }, + { + "file": "builtins/MultiplyInteger.nash", + "module": "MultiplyInteger", + "name": "crosses signed 64-bit boundary", + "source_sha256": "d2a91324f674ef643bf8cac3b0d68907147c9734eb1d55425d4a3df9fb919d23", + "status": "verified" + }, + { + "file": "builtins/MultiplyInteger.nash", + "module": "MultiplyInteger", + "name": "detects wrong opcode argument order or signedness", + "source_sha256": "d2a91324f674ef643bf8cac3b0d68907147c9734eb1d55425d4a3df9fb919d23", + "status": "verified" + }, + { + "file": "builtins/NullList.nash", + "module": "NullList", + "name": "empty list is null", + "source_sha256": "d90c8266f2c6e2316bd72a7e47243fca0dc3cebc26c056b72d8022fcc000831d", + "status": "not_checked" + }, + { + "file": "builtins/NullList.nash", + "module": "NullList", + "name": "singleton list is not null", + "source_sha256": "d90c8266f2c6e2316bd72a7e47243fca0dc3cebc26c056b72d8022fcc000831d", + "status": "not_checked" + }, + { + "file": "builtins/OrByteString.nash", + "module": "OrByteString", + "name": "commutative for both padding modes", + "source_sha256": "c76c5d0c7f8b5cd5c300d8994b1e9f4113dc8096c8f530bad26a692401371230", + "status": "not_checked" + }, + { + "file": "builtins/OrByteString.nash", + "module": "OrByteString", + "name": "self law", + "source_sha256": "c76c5d0c7f8b5cd5c300d8994b1e9f4113dc8096c8f530bad26a692401371230", + "status": "not_checked" + }, + { + "file": "builtins/OrByteString.nash", + "module": "OrByteString", + "name": "truncating mode with empty input", + "source_sha256": "c76c5d0c7f8b5cd5c300d8994b1e9f4113dc8096c8f530bad26a692401371230", + "status": "not_checked" + }, + { + "file": "builtins/QuotientInteger.nash", + "module": "QuotientInteger", + "name": "division by one", + "source_sha256": "8f92a13ef16e47fffb82ef378c046d22c3493135e03884a09840dbe875b37c93", + "status": "verified" + }, + { + "file": "builtins/QuotientInteger.nash", + "module": "QuotientInteger", + "name": "quotient and remainder reconstruct the dividend", + "source_sha256": "8f92a13ef16e47fffb82ef378c046d22c3493135e03884a09840dbe875b37c93", + "status": "timeout", + "reason": "Did not finish within the wall limit; no proof or rejection witness claimed." + }, + { + "file": "builtins/QuotientInteger.nash", + "module": "QuotientInteger", + "name": "negative rounding convention", + "source_sha256": "8f92a13ef16e47fffb82ef378c046d22c3493135e03884a09840dbe875b37c93", + "status": "verified" + }, + { + "file": "builtins/QuotientInteger.nash", + "module": "QuotientInteger", + "name": "zero divisor rejects every dividend", + "source_sha256": "8f92a13ef16e47fffb82ef378c046d22c3493135e03884a09840dbe875b37c93", + "status": "verified" + }, + { + "file": "builtins/QuotientInteger.nash", + "module": "QuotientInteger", + "name": "detects wrong opcode argument order or signedness", + "source_sha256": "8f92a13ef16e47fffb82ef378c046d22c3493135e03884a09840dbe875b37c93", + "status": "verified" + }, + { + "file": "builtins/ReadBit.nash", + "module": "ReadBit", + "name": "least significant bit indexing", + "source_sha256": "c2032885e10e60ade90b7d2ec5718565cd2dac02619c419bb43d9e77e6204f9e", + "status": "not_checked" + }, + { + "file": "builtins/ReadBit.nash", + "module": "ReadBit", + "name": "negative bit index rejects", + "source_sha256": "c2032885e10e60ade90b7d2ec5718565cd2dac02619c419bb43d9e77e6204f9e", + "status": "verified" + }, + { + "file": "builtins/ReadBit.nash", + "module": "ReadBit", + "name": "index equal to bit count rejects", + "source_sha256": "c2032885e10e60ade90b7d2ec5718565cd2dac02619c419bb43d9e77e6204f9e", + "status": "verified" + }, + { + "file": "builtins/ReadBit.nash", + "module": "ReadBit", + "name": "index above 128 bits rejects without host overflow", + "source_sha256": "c2032885e10e60ade90b7d2ec5718565cd2dac02619c419bb43d9e77e6204f9e", + "status": "verified" + }, + { + "file": "builtins/RemainderInteger.nash", + "module": "RemainderInteger", + "name": "remainder modulo one is zero", + "source_sha256": "3c480cdceb54125e8f87ed5667745fc45fb2f001c932463d85d7ec6f498d0947", + "status": "verified" + }, + { + "file": "builtins/RemainderInteger.nash", + "module": "RemainderInteger", + "name": "division reconstruction", + "source_sha256": "3c480cdceb54125e8f87ed5667745fc45fb2f001c932463d85d7ec6f498d0947", + "status": "timeout", + "reason": "Did not finish within the wall limit; no proof or rejection witness claimed." + }, + { + "file": "builtins/RemainderInteger.nash", + "module": "RemainderInteger", + "name": "remainder has the dividend sign", + "source_sha256": "3c480cdceb54125e8f87ed5667745fc45fb2f001c932463d85d7ec6f498d0947", + "status": "timeout", + "reason": "Did not finish within the wall limit; no proof or rejection witness claimed." + }, + { + "file": "builtins/RemainderInteger.nash", + "module": "RemainderInteger", + "name": "negative rounding convention", + "source_sha256": "3c480cdceb54125e8f87ed5667745fc45fb2f001c932463d85d7ec6f498d0947", + "status": "verified" + }, + { + "file": "builtins/RemainderInteger.nash", + "module": "RemainderInteger", + "name": "zero divisor rejects every dividend", + "source_sha256": "3c480cdceb54125e8f87ed5667745fc45fb2f001c932463d85d7ec6f498d0947", + "status": "verified" + }, + { + "file": "builtins/RemainderInteger.nash", + "module": "RemainderInteger", + "name": "detects wrong opcode argument order or signedness", + "source_sha256": "3c480cdceb54125e8f87ed5667745fc45fb2f001c932463d85d7ec6f498d0947", + "status": "verified" + }, + { + "file": "builtins/ReplicateByte.nash", + "module": "ReplicateByte", + "name": "zero count produces empty bytes", + "source_sha256": "bfc02d6daff02ef5066e00f8f863a941655784696939b53d32923ea5d0f851d3", + "status": "not_checked" + }, + { + "file": "builtins/ReplicateByte.nash", + "module": "ReplicateByte", + "name": "repeats exactly the requested byte", + "source_sha256": "bfc02d6daff02ef5066e00f8f863a941655784696939b53d32923ea5d0f851d3", + "status": "not_checked" + }, + { + "file": "builtins/ReplicateByte.nash", + "module": "ReplicateByte", + "name": "invalid byte rejects", + "source_sha256": "bfc02d6daff02ef5066e00f8f863a941655784696939b53d32923ea5d0f851d3", + "status": "verified" + }, + { + "file": "builtins/ReplicateByte.nash", + "module": "ReplicateByte", + "name": "negative count rejects", + "source_sha256": "bfc02d6daff02ef5066e00f8f863a941655784696939b53d32923ea5d0f851d3", + "status": "verified" + }, + { + "file": "builtins/ReplicateByte.nash", + "module": "ReplicateByte", + "name": "negative byte rejects", + "source_sha256": "bfc02d6daff02ef5066e00f8f863a941655784696939b53d32923ea5d0f851d3", + "status": "verified" + }, + { + "file": "builtins/ReplicateByte.nash", + "module": "ReplicateByte", + "name": "count above 8192 rejects", + "source_sha256": "bfc02d6daff02ef5066e00f8f863a941655784696939b53d32923ea5d0f851d3", + "status": "verified" + }, + { + "file": "builtins/ReplicateByte.nash", + "module": "ReplicateByte", + "name": "largest supported count is accepted", + "source_sha256": "bfc02d6daff02ef5066e00f8f863a941655784696939b53d32923ea5d0f851d3", + "status": "not_checked" + }, + { + "file": "builtins/Ripemd160.nash", + "module": "Ripemd160", + "name": "digest has the specified byte length", + "source_sha256": "54dcb28b5abda7f7def4d07c88719d299ea85ca84178df3a627414ef8329ea01", + "status": "not_checked" + }, + { + "file": "builtins/Ripemd160.nash", + "module": "Ripemd160", + "name": "empty message known answer detects the wrong hash primitive", + "source_sha256": "54dcb28b5abda7f7def4d07c88719d299ea85ca84178df3a627414ef8329ea01", + "status": "not_checked" + }, + { + "file": "builtins/RotateByteString.nash", + "module": "RotateByteString", + "name": "zero rotation is identity", + "source_sha256": "5c0afa3feee1dd5a7c422ea807b29729e70b738848c6f131dc3fb8f7166b2447", + "status": "not_checked" + }, + { + "file": "builtins/RotateByteString.nash", + "module": "RotateByteString", + "name": "rotation by width is identity", + "source_sha256": "5c0afa3feee1dd5a7c422ea807b29729e70b738848c6f131dc3fb8f7166b2447", + "status": "not_checked" + }, + { + "file": "builtins/RotateByteString.nash", + "module": "RotateByteString", + "name": "inverse rotation", + "source_sha256": "5c0afa3feee1dd5a7c422ea807b29729e70b738848c6f131dc3fb8f7166b2447", + "status": "not_checked" + }, + { + "file": "builtins/RotateByteString.nash", + "module": "RotateByteString", + "name": "large rotation is reduced by bit width", + "source_sha256": "5c0afa3feee1dd5a7c422ea807b29729e70b738848c6f131dc3fb8f7166b2447", + "status": "counterexample_in_opaque_model", + "reason": "The SMT interpretation assigns unconstrained behavior to the opaque cryptographic primitive; this is not a concrete Nash counterexample." + }, + { + "file": "builtins/ScaleValue.nash", + "module": "ScaleValue", + "name": "zero annihilates quantities", + "source_sha256": "48339d3ca100d527144709992e93ac284a742d59cf74ece393273c1ae1cb1589", + "status": "not_checked" + }, + { + "file": "builtins/ScaleValue.nash", + "module": "ScaleValue", + "name": "multiplicative identity", + "source_sha256": "48339d3ca100d527144709992e93ac284a742d59cf74ece393273c1ae1cb1589", + "status": "not_checked" + }, + { + "file": "builtins/ScaleValue.nash", + "module": "ScaleValue", + "name": "scales quantities", + "source_sha256": "48339d3ca100d527144709992e93ac284a742d59cf74ece393273c1ae1cb1589", + "status": "not_checked" + }, + { + "file": "builtins/ScaleValue.nash", + "module": "ScaleValue", + "name": "quantity overflow rejects", + "source_sha256": "48339d3ca100d527144709992e93ac284a742d59cf74ece393273c1ae1cb1589", + "status": "timeout", + "reason": "Did not finish within the wall limit; no proof or rejection witness claimed." + }, + { + "file": "builtins/ScaleValue.nash", + "module": "ScaleValue", + "name": "out of bounds multiplier rejects", + "source_sha256": "48339d3ca100d527144709992e93ac284a742d59cf74ece393273c1ae1cb1589", + "status": "timeout", + "reason": "Did not finish within the wall limit; no proof or rejection witness claimed." + }, + { + "file": "builtins/SerialiseData.nash", + "module": "SerialiseData", + "name": "canonical CBOR integer encoding", + "source_sha256": "64c3959b6325508a269fcbeaf09f25a33e91f95ce83fa639b6ded8d0baafa659", + "status": "not_checked" + }, + { + "file": "builtins/Sha2256.nash", + "module": "Sha2256", + "name": "digest has the specified byte length", + "source_sha256": "cdbaa425599b5bbef0707b916196a5ccaa1c068384ca93eddaed6e4dffa2a66c", + "status": "not_checked" + }, + { + "file": "builtins/Sha2256.nash", + "module": "Sha2256", + "name": "empty message known answer detects the wrong hash primitive", + "source_sha256": "cdbaa425599b5bbef0707b916196a5ccaa1c068384ca93eddaed6e4dffa2a66c", + "status": "not_checked" + }, + { + "file": "builtins/Sha3256.nash", + "module": "Sha3256", + "name": "digest has the specified byte length", + "source_sha256": "a09f08e6f2946d5aac17be4a2d9549f432aecc25964732a1e26e8bee3416d29b", + "status": "not_checked" + }, + { + "file": "builtins/Sha3256.nash", + "module": "Sha3256", + "name": "empty message known answer detects the wrong hash primitive", + "source_sha256": "a09f08e6f2946d5aac17be4a2d9549f432aecc25964732a1e26e8bee3416d29b", + "status": "not_checked" + }, + { + "file": "builtins/ShiftByteString.nash", + "module": "ShiftByteString", + "name": "zero shift is identity", + "source_sha256": "cf1c719bc17823d3c5bfcb1749e8a7f0fafbd55aca241d2c71b3fab09c72f2a5", + "status": "not_checked" + }, + { + "file": "builtins/ShiftByteString.nash", + "module": "ShiftByteString", + "name": "direction and zero fill", + "source_sha256": "cf1c719bc17823d3c5bfcb1749e8a7f0fafbd55aca241d2c71b3fab09c72f2a5", + "status": "not_checked" + }, + { + "file": "builtins/ShiftByteString.nash", + "module": "ShiftByteString", + "name": "shifting out every bit produces zero", + "source_sha256": "cf1c719bc17823d3c5bfcb1749e8a7f0fafbd55aca241d2c71b3fab09c72f2a5", + "status": "not_checked" + }, + { + "file": "builtins/ShiftByteString.nash", + "module": "ShiftByteString", + "name": "large shift does not truncate to a host word", + "source_sha256": "cf1c719bc17823d3c5bfcb1749e8a7f0fafbd55aca241d2c71b3fab09c72f2a5", + "status": "counterexample_in_opaque_model", + "reason": "The SMT interpretation assigns unconstrained behavior to the opaque cryptographic primitive; this is not a concrete Nash counterexample." + }, + { + "file": "builtins/ShiftByteString.nash", + "module": "ShiftByteString", + "name": "whole byte shifts preserve the remaining bytes", + "source_sha256": "cf1c719bc17823d3c5bfcb1749e8a7f0fafbd55aca241d2c71b3fab09c72f2a5", + "status": "counterexample_in_opaque_model", + "reason": "The SMT interpretation assigns unconstrained behavior to the opaque cryptographic primitive; this is not a concrete Nash counterexample." + }, + { + "file": "builtins/SliceByteString.nash", + "module": "SliceByteString", + "name": "zero count produces empty bytes", + "source_sha256": "c8693d7d6a8f72c68917995760caf764990a0d24f6c40f6a206959e32c718aff", + "status": "not_checked" + }, + { + "file": "builtins/SliceByteString.nash", + "module": "SliceByteString", + "name": "negative count produces empty bytes", + "source_sha256": "c8693d7d6a8f72c68917995760caf764990a0d24f6c40f6a206959e32c718aff", + "status": "not_checked" + }, + { + "file": "builtins/SliceByteString.nash", + "module": "SliceByteString", + "name": "whole slice preserves the bytes", + "source_sha256": "c8693d7d6a8f72c68917995760caf764990a0d24f6c40f6a206959e32c718aff", + "status": "not_checked" + }, + { + "file": "builtins/SliceByteString.nash", + "module": "SliceByteString", + "name": "negative offset is clamped independently of count", + "source_sha256": "c8693d7d6a8f72c68917995760caf764990a0d24f6c40f6a206959e32c718aff", + "status": "not_checked" + }, + { + "file": "builtins/SliceByteString.nash", + "module": "SliceByteString", + "name": "offset at the end gives empty bytes", + "source_sha256": "c8693d7d6a8f72c68917995760caf764990a0d24f6c40f6a206959e32c718aff", + "status": "not_checked" + }, + { + "file": "builtins/SliceByteString.nash", + "module": "SliceByteString", + "name": "detects wrong opcode argument order or signedness", + "source_sha256": "c8693d7d6a8f72c68917995760caf764990a0d24f6c40f6a206959e32c718aff", + "status": "not_checked" + }, + { + "file": "builtins/SndPair.nash", + "module": "SndPair", + "name": "projects the correct component", + "source_sha256": "4299259aabf7dbc5caf39f2944ade060632f8067c0313baec2a47f613522d723", + "status": "not_checked" + }, + { + "file": "builtins/SubtractInteger.nash", + "module": "SubtractInteger", + "name": "right identity", + "source_sha256": "beb39b6b1fb1cda54df42822c8eb963b870ac38fdee24755bea6bb80a4846664", + "status": "verified" + }, + { + "file": "builtins/SubtractInteger.nash", + "module": "SubtractInteger", + "name": "self subtraction is zero", + "source_sha256": "beb39b6b1fb1cda54df42822c8eb963b870ac38fdee24755bea6bb80a4846664", + "status": "verified" + }, + { + "file": "builtins/SubtractInteger.nash", + "module": "SubtractInteger", + "name": "inverse of addition", + "source_sha256": "beb39b6b1fb1cda54df42822c8eb963b870ac38fdee24755bea6bb80a4846664", + "status": "timeout", + "reason": "Did not finish within the wall limit; no proof or rejection witness claimed." + }, + { + "file": "builtins/SubtractInteger.nash", + "module": "SubtractInteger", + "name": "predecessor never wraps at any integer width", + "source_sha256": "beb39b6b1fb1cda54df42822c8eb963b870ac38fdee24755bea6bb80a4846664", + "status": "verified" + }, + { + "file": "builtins/SubtractInteger.nash", + "module": "SubtractInteger", + "name": "argument order", + "source_sha256": "beb39b6b1fb1cda54df42822c8eb963b870ac38fdee24755bea6bb80a4846664", + "status": "verified" + }, + { + "file": "builtins/SubtractInteger.nash", + "module": "SubtractInteger", + "name": "detects wrong opcode argument order or signedness", + "source_sha256": "beb39b6b1fb1cda54df42822c8eb963b870ac38fdee24755bea6bb80a4846664", + "status": "verified" + }, + { + "file": "builtins/TailList.nash", + "module": "TailList", + "name": "tail preserves subsequent elements", + "source_sha256": "f26fc640c4944dbb5d2900a8e151274f7c5a3cc20345305095a5f85d031fd4ac", + "status": "not_checked" + }, + { + "file": "builtins/TailList.nash", + "module": "TailList", + "name": "singleton tail is empty", + "source_sha256": "f26fc640c4944dbb5d2900a8e151274f7c5a3cc20345305095a5f85d031fd4ac", + "status": "not_checked" + }, + { + "file": "builtins/TailList.nash", + "module": "TailList", + "name": "empty list rejects", + "source_sha256": "f26fc640c4944dbb5d2900a8e151274f7c5a3cc20345305095a5f85d031fd4ac", + "status": "verified" + }, + { + "file": "builtins/Trace.nash", + "module": "Trace", + "name": "preserves the value", + "source_sha256": "23b38eb0f8518fc409262a485f41922b61a858e3aadd5589cec62f82a7e47646", + "status": "not_checked" + }, + { + "file": "builtins/Trace.nash", + "module": "Trace", + "name": "detects wrong opcode argument order or signedness", + "source_sha256": "23b38eb0f8518fc409262a485f41922b61a858e3aadd5589cec62f82a7e47646", + "status": "not_checked" + }, + { + "file": "builtins/UnBData.nash", + "module": "UnBData", + "name": "unboxes every bytestring", + "source_sha256": "7aa45a411f7453c906a2c9860933f4136f6d98e303b159c2b3e6a64108ec7448", + "status": "not_checked" + }, + { + "file": "builtins/UnBData.nash", + "module": "UnBData", + "name": "integer Data is not bytes Data", + "source_sha256": "7aa45a411f7453c906a2c9860933f4136f6d98e303b159c2b3e6a64108ec7448", + "status": "not_checked" + }, + { + "file": "builtins/UnBData.nash", + "module": "UnBData", + "name": "constructor Data cannot be used as bytes", + "source_sha256": "7aa45a411f7453c906a2c9860933f4136f6d98e303b159c2b3e6a64108ec7448", + "status": "not_checked" + }, + { + "file": "builtins/UnConstrData.nash", + "module": "UnConstrData", + "name": "recovers tag and first field", + "source_sha256": "4cd1dea95442f5064e5675af43b6ba7a90a693c473bf57f992d61ecea64f739b", + "status": "not_checked" + }, + { + "file": "builtins/UnConstrData.nash", + "module": "UnConstrData", + "name": "integer Data is not constructor Data", + "source_sha256": "4cd1dea95442f5064e5675af43b6ba7a90a693c473bf57f992d61ecea64f739b", + "status": "not_checked" + }, + { + "file": "builtins/UnConstrData.nash", + "module": "UnConstrData", + "name": "bytes Data cannot be used as a constructor", + "source_sha256": "4cd1dea95442f5064e5675af43b6ba7a90a693c473bf57f992d61ecea64f739b", + "status": "not_checked" + }, + { + "file": "builtins/UnIData.nash", + "module": "UnIData", + "name": "unboxes every integer", + "source_sha256": "864f4539a6c426a73e69af1826d37360047d346b87060f9399c720ae7824cfd5", + "status": "not_checked" + }, + { + "file": "builtins/UnIData.nash", + "module": "UnIData", + "name": "bytes Data is not integer Data", + "source_sha256": "864f4539a6c426a73e69af1826d37360047d346b87060f9399c720ae7824cfd5", + "status": "not_checked" + }, + { + "file": "builtins/UnIData.nash", + "module": "UnIData", + "name": "constructor Data cannot be used as an integer", + "source_sha256": "864f4539a6c426a73e69af1826d37360047d346b87060f9399c720ae7824cfd5", + "status": "not_checked" + }, + { + "file": "builtins/UnListData.nash", + "module": "UnListData", + "name": "recovers the first element", + "source_sha256": "a424030664ca78f042ae3aebabbcaa2396dad154dd4bb776b9299f43237006a7", + "status": "not_checked" + }, + { + "file": "builtins/UnListData.nash", + "module": "UnListData", + "name": "integer Data is not list Data", + "source_sha256": "a424030664ca78f042ae3aebabbcaa2396dad154dd4bb776b9299f43237006a7", + "status": "not_checked" + }, + { + "file": "builtins/UnListData.nash", + "module": "UnListData", + "name": "bytes Data cannot be used as a list", + "source_sha256": "a424030664ca78f042ae3aebabbcaa2396dad154dd4bb776b9299f43237006a7", + "status": "not_checked" + }, + { + "file": "builtins/UnMapData.nash", + "module": "UnMapData", + "name": "recovers a map entry", + "source_sha256": "cb21e0d32bf13ceeff37605908e8a24bc270af49490cdcccb486f9cbe6bdf44a", + "status": "not_checked" + }, + { + "file": "builtins/UnMapData.nash", + "module": "UnMapData", + "name": "integer Data is not map Data", + "source_sha256": "cb21e0d32bf13ceeff37605908e8a24bc270af49490cdcccb486f9cbe6bdf44a", + "status": "not_checked" + }, + { + "file": "builtins/UnMapData.nash", + "module": "UnMapData", + "name": "bytes Data cannot be used as a map", + "source_sha256": "cb21e0d32bf13ceeff37605908e8a24bc270af49490cdcccb486f9cbe6bdf44a", + "status": "not_checked" + }, + { + "file": "builtins/UnValueData.nash", + "module": "UnValueData", + "name": "decoding and encoding recover quantities", + "source_sha256": "6de5bc8f2ff995e4bf1ad529a2a07331fb2c13f8e7c0ee22e4d7a48d8dd07272", + "status": "not_checked" + }, + { + "file": "builtins/UnValueData.nash", + "module": "UnValueData", + "name": "integer Data is not a value", + "source_sha256": "6de5bc8f2ff995e4bf1ad529a2a07331fb2c13f8e7c0ee22e4d7a48d8dd07272", + "status": "not_checked" + }, + { + "file": "builtins/UnValueData.nash", + "module": "UnValueData", + "name": "empty inner asset map rejects", + "source_sha256": "6de5bc8f2ff995e4bf1ad529a2a07331fb2c13f8e7c0ee22e4d7a48d8dd07272", + "status": "timeout", + "reason": "Did not finish within the wall limit; no proof or rejection witness claimed." + }, + { + "file": "builtins/UnValueData.nash", + "module": "UnValueData", + "name": "zero encoded quantity rejects", + "source_sha256": "6de5bc8f2ff995e4bf1ad529a2a07331fb2c13f8e7c0ee22e4d7a48d8dd07272", + "status": "timeout", + "reason": "Did not finish within the wall limit; no proof or rejection witness claimed." + }, + { + "file": "builtins/UnValueData.nash", + "module": "UnValueData", + "name": "duplicate asset names reject", + "source_sha256": "6de5bc8f2ff995e4bf1ad529a2a07331fb2c13f8e7c0ee22e4d7a48d8dd07272", + "status": "timeout", + "reason": "Did not finish within the wall limit; no proof or rejection witness claimed." + }, + { + "file": "builtins/UnValueData.nash", + "module": "UnValueData", + "name": "out of order asset names reject", + "source_sha256": "6de5bc8f2ff995e4bf1ad529a2a07331fb2c13f8e7c0ee22e4d7a48d8dd07272", + "status": "timeout", + "reason": "Did not finish within the wall limit; no proof or rejection witness claimed." + }, + { + "file": "builtins/UnionValue.nash", + "module": "UnionValue", + "name": "empty value is the identity", + "source_sha256": "ad6428ffc4053362b0db1d15658123c80bbaee1465b3e7df5f28d378ccf6c83e", + "status": "not_checked" + }, + { + "file": "builtins/UnionValue.nash", + "module": "UnionValue", + "name": "adds the same asset quantities", + "source_sha256": "ad6428ffc4053362b0db1d15658123c80bbaee1465b3e7df5f28d378ccf6c83e", + "status": "not_checked" + }, + { + "file": "builtins/UnionValue.nash", + "module": "UnionValue", + "name": "quantity overflow rejects", + "source_sha256": "ad6428ffc4053362b0db1d15658123c80bbaee1465b3e7df5f28d378ccf6c83e", + "status": "timeout", + "reason": "Did not finish within the wall limit; no proof or rejection witness claimed." + }, + { + "file": "builtins/ValueContains.nash", + "module": "ValueContains", + "name": "nonnegative value contains itself", + "source_sha256": "647b4655d719739edac87da837f39fe19bb5cbb5a13b4a8f7659c4636c07d2cd", + "status": "not_checked" + }, + { + "file": "builtins/ValueContains.nash", + "module": "ValueContains", + "name": "quantity containment", + "source_sha256": "647b4655d719739edac87da837f39fe19bb5cbb5a13b4a8f7659c4636c07d2cd", + "status": "not_checked" + }, + { + "file": "builtins/ValueContains.nash", + "module": "ValueContains", + "name": "negative first quantity rejects", + "source_sha256": "647b4655d719739edac87da837f39fe19bb5cbb5a13b4a8f7659c4636c07d2cd", + "status": "timeout", + "reason": "Did not finish within the wall limit; no proof or rejection witness claimed." + }, + { + "file": "builtins/ValueContains.nash", + "module": "ValueContains", + "name": "negative second quantity rejects", + "source_sha256": "647b4655d719739edac87da837f39fe19bb5cbb5a13b4a8f7659c4636c07d2cd", + "status": "timeout", + "reason": "Did not finish within the wall limit; no proof or rejection witness claimed." + }, + { + "file": "builtins/ValueData.nash", + "module": "ValueData", + "name": "encoding and decoding recover quantities", + "source_sha256": "fbc2eef0a2db366d2c6094237004e212ffe75e802e16429e37fc4876143b1ca4", + "status": "not_checked" + }, + { + "file": "builtins/VerifyEcdsaSecp256k1Signature.nash", + "module": "VerifyEcdsaSecp256k1Signature", + "name": "empty public key rejects", + "source_sha256": "e0be60afd4e2fe6f10053ee76c578a0cf43f90739194c818e6c5f7d2350f389f", + "status": "counterexample_in_opaque_model", + "reason": "The SMT interpretation assigns unconstrained behavior to the opaque cryptographic primitive; this is not a concrete Nash counterexample." + }, + { + "file": "builtins/VerifyEcdsaSecp256k1Signature.nash", + "module": "VerifyEcdsaSecp256k1Signature", + "name": "valid key with truncated signature rejects", + "source_sha256": "e0be60afd4e2fe6f10053ee76c578a0cf43f90739194c818e6c5f7d2350f389f", + "status": "counterexample_in_opaque_model", + "reason": "The SMT interpretation assigns unconstrained behavior to the opaque cryptographic primitive; this is not a concrete Nash counterexample." + }, + { + "file": "builtins/VerifyEd25519Signature.nash", + "module": "VerifyEd25519Signature", + "name": "empty public key rejects", + "source_sha256": "4e94508fd10045bed2fbad31a12abd62d0176b0926f62d65a482014a51cb78a9", + "status": "counterexample_in_opaque_model", + "reason": "The SMT interpretation assigns unconstrained behavior to the opaque cryptographic primitive; this is not a concrete Nash counterexample." + }, + { + "file": "builtins/VerifyEd25519Signature.nash", + "module": "VerifyEd25519Signature", + "name": "valid key with truncated signature rejects", + "source_sha256": "4e94508fd10045bed2fbad31a12abd62d0176b0926f62d65a482014a51cb78a9", + "status": "counterexample_in_opaque_model", + "reason": "The SMT interpretation assigns unconstrained behavior to the opaque cryptographic primitive; this is not a concrete Nash counterexample." + }, + { + "file": "builtins/VerifyEd25519Signature.nash", + "module": "VerifyEd25519Signature", + "name": "RFC8032 empty message signature verifies", + "source_sha256": "4e94508fd10045bed2fbad31a12abd62d0176b0926f62d65a482014a51cb78a9", + "status": "not_checked" + }, + { + "file": "builtins/VerifyEd25519Signature.nash", + "module": "VerifyEd25519Signature", + "name": "changed message returns False instead of rejecting", + "source_sha256": "4e94508fd10045bed2fbad31a12abd62d0176b0926f62d65a482014a51cb78a9", + "status": "timeout", + "reason": "Did not finish within the wall limit; no proof or rejection witness claimed." + }, + { + "file": "builtins/VerifySchnorrSecp256k1Signature.nash", + "module": "VerifySchnorrSecp256k1Signature", + "name": "empty public key rejects", + "source_sha256": "69f8daa3b5c1950e974e685c6e2fab93358239e36f8ac03ffdf1dd1d9ed0ce01", + "status": "counterexample_in_opaque_model", + "reason": "The SMT interpretation assigns unconstrained behavior to the opaque cryptographic primitive; this is not a concrete Nash counterexample." + }, + { + "file": "builtins/VerifySchnorrSecp256k1Signature.nash", + "module": "VerifySchnorrSecp256k1Signature", + "name": "valid key with truncated signature rejects", + "source_sha256": "69f8daa3b5c1950e974e685c6e2fab93358239e36f8ac03ffdf1dd1d9ed0ce01", + "status": "counterexample_in_opaque_model", + "reason": "The SMT interpretation assigns unconstrained behavior to the opaque cryptographic primitive; this is not a concrete Nash counterexample." + }, + { + "file": "builtins/WriteBits.nash", + "module": "WriteBits", + "name": "write then read", + "source_sha256": "50a4ccf15143d85ca616e60b7587f98d0a11c47b6cf36307e74c0cceaf2c165a", + "status": "not_checked" + }, + { + "file": "builtins/WriteBits.nash", + "module": "WriteBits", + "name": "empty index list is identity", + "source_sha256": "50a4ccf15143d85ca616e60b7587f98d0a11c47b6cf36307e74c0cceaf2c165a", + "status": "not_checked" + }, + { + "file": "builtins/WriteBits.nash", + "module": "WriteBits", + "name": "invalid index rejects", + "source_sha256": "50a4ccf15143d85ca616e60b7587f98d0a11c47b6cf36307e74c0cceaf2c165a", + "status": "verified" + }, + { + "file": "builtins/WriteBits.nash", + "module": "WriteBits", + "name": "index above 128 bits rejects without host overflow", + "source_sha256": "50a4ccf15143d85ca616e60b7587f98d0a11c47b6cf36307e74c0cceaf2c165a", + "status": "verified" + }, + { + "file": "builtins/WriteBits.nash", + "module": "WriteBits", + "name": "negative index rejects", + "source_sha256": "50a4ccf15143d85ca616e60b7587f98d0a11c47b6cf36307e74c0cceaf2c165a", + "status": "verified" + }, + { + "file": "builtins/WriteBits.nash", + "module": "WriteBits", + "name": "all indices must be valid", + "source_sha256": "50a4ccf15143d85ca616e60b7587f98d0a11c47b6cf36307e74c0cceaf2c165a", + "status": "not_checked" + }, + { + "file": "builtins/XorByteString.nash", + "module": "XorByteString", + "name": "commutative for both padding modes", + "source_sha256": "b91410c7e60d3f970d1fae5319917cf7c282050d4ade87a43a65f9ac415dbc09", + "status": "not_checked" + }, + { + "file": "builtins/XorByteString.nash", + "module": "XorByteString", + "name": "self law", + "source_sha256": "b91410c7e60d3f970d1fae5319917cf7c282050d4ade87a43a65f9ac415dbc09", + "status": "not_checked" + }, + { + "file": "builtins/XorByteString.nash", + "module": "XorByteString", + "name": "truncating mode with empty input", + "source_sha256": "b91410c7e60d3f970d1fae5319917cf7c282050d4ade87a43a65f9ac415dbc09", + "status": "not_checked" + }, + { + "file": "pending/builtins/Bls12381G1MultiScalarMul.nash", + "module": "Bls12381G1MultiScalarMul", + "name": "singleton agrees with scalar multiplication", + "source_sha256": "c285ef425c387db5846b1572a8ccd6b1ee1b10bfb1ec5f5b997c0aafe39c9526", + "status": "compiler_blocked" + }, + { + "file": "pending/builtins/Bls12381G1MultiScalarMul.nash", + "module": "Bls12381G1MultiScalarMul", + "name": "different scalar and point counts reject", + "source_sha256": "c285ef425c387db5846b1572a8ccd6b1ee1b10bfb1ec5f5b997c0aafe39c9526", + "status": "compiler_blocked" + }, + { + "file": "pending/builtins/Bls12381G1MultiScalarMul.nash", + "module": "Bls12381G1MultiScalarMul", + "name": "scalar above the signed 4096 bit range rejects", + "source_sha256": "c285ef425c387db5846b1572a8ccd6b1ee1b10bfb1ec5f5b997c0aafe39c9526", + "status": "compiler_blocked" + }, + { + "file": "pending/builtins/Bls12381G2MultiScalarMul.nash", + "module": "Bls12381G2MultiScalarMul", + "name": "singleton agrees with scalar multiplication", + "source_sha256": "4790b57b9a70997e6c15d92b306fe49de5e3e5139a41573571886996d015a111", + "status": "compiler_blocked" + }, + { + "file": "pending/builtins/Bls12381G2MultiScalarMul.nash", + "module": "Bls12381G2MultiScalarMul", + "name": "different scalar and point counts reject", + "source_sha256": "4790b57b9a70997e6c15d92b306fe49de5e3e5139a41573571886996d015a111", + "status": "compiler_blocked" + }, + { + "file": "pending/builtins/Bls12381G2MultiScalarMul.nash", + "module": "Bls12381G2MultiScalarMul", + "name": "scalar above the signed 4096 bit range rejects", + "source_sha256": "4790b57b9a70997e6c15d92b306fe49de5e3e5139a41573571886996d015a111", + "status": "compiler_blocked" + } + ] +} diff --git a/proofs/verified.tsv b/proofs/verified.tsv new file mode 100644 index 00000000..998f0667 --- /dev/null +++ b/proofs/verified.tsv @@ -0,0 +1,66 @@ +# SMT-verified with the pinned models at 300 CEK steps; solver 10s, wall 25s. +# Moduleproof name. This is a reproducible subset, not all obligations. +AddInteger additive inverse +AddInteger boxed successor never wraps at any integer width +AddInteger commutative +AddInteger crosses negative signed 64-bit boundary +AddInteger crosses signed 64-bit boundary +AddInteger crosses unsigned 128-bit boundary +AddInteger detects wrong opcode argument order or signedness +AddInteger left identity +AddInteger right identity +AddInteger successor never wraps at any integer width +ConsByteString byte above 255 rejects in Plutus V3 +ConsByteString negative byte rejects in Plutus V3 +DecodeUtf8 invalid UTF8 rejects +DivideInteger detects wrong opcode argument order or signedness +DivideInteger division by one +DivideInteger negative rounding convention +DivideInteger zero divisor rejects every dividend +EqualsInteger detects wrong opcode argument order or signedness +EqualsInteger reflexive +HeadList empty list rejects +IndexByteString index above 128 bits rejects without host overflow +IndexByteString negative index rejects +IntegerToByteString insufficient width rejects +IntegerToByteString negative integer rejects +IntegerToByteString negative width rejects +IntegerToByteString width above 8192 rejects +LessThanEqualsInteger detects wrong opcode argument order or signedness +LessThanEqualsInteger reflexive +LessThanInteger detects wrong opcode argument order or signedness +LessThanInteger irreflexive +LessThanInteger strict successor order at every width +ModInteger detects wrong opcode argument order or signedness +ModInteger negative rounding convention +ModInteger remainder modulo one is zero +ModInteger zero divisor rejects every dividend +MultiplyInteger commutative +MultiplyInteger crosses signed 64-bit boundary +MultiplyInteger detects wrong opcode argument order or signedness +MultiplyInteger multiplicative identity +MultiplyInteger zero annihilates +QuotientInteger detects wrong opcode argument order or signedness +QuotientInteger division by one +QuotientInteger negative rounding convention +QuotientInteger zero divisor rejects every dividend +ReadBit index above 128 bits rejects without host overflow +ReadBit index equal to bit count rejects +ReadBit negative bit index rejects +RemainderInteger detects wrong opcode argument order or signedness +RemainderInteger negative rounding convention +RemainderInteger remainder modulo one is zero +RemainderInteger zero divisor rejects every dividend +ReplicateByte count above 8192 rejects +ReplicateByte invalid byte rejects +ReplicateByte negative byte rejects +ReplicateByte negative count rejects +SubtractInteger argument order +SubtractInteger detects wrong opcode argument order or signedness +SubtractInteger predecessor never wraps at any integer width +SubtractInteger right identity +SubtractInteger self subtraction is zero +TailList empty list rejects +WriteBits index above 128 bits rejects without host overflow +WriteBits invalid index rejects +WriteBits negative index rejects From fd00ffaeabf3290ffc25e179839d03956f2c63da Mon Sep 17 00:00:00 2001 From: RSoulatIOHK Date: Fri, 2 Oct 2026 09:56:17 +0200 Subject: [PATCH 7/7] refactor: separate application and builtin proof suites from backend --- .sampo/changesets/lean-blaster-proofs.md | 17 +- .../nash-driver/tests/builtin_compilation.rs | 352 --- crates/nash-driver/tests/builtin_proofs.rs | 409 --- .../fixtures/base-traits/DataConversions.nash | 521 +--- .../tests/fixtures/base-traits/Equality.nash | 278 +- .../fixtures/base-traits/IntegerMath.nash | 1218 ++------- .../builtin-compilation/Certificate.lean | 70 - .../fixtures/builtin-compilation/spec.tsv | 109 - crates/nash-driver/tests/proofs.rs | 146 - crates/nash-driver/tests/vesting.rs | 37 - crates/nash-plutus/src/machine/runtime.rs | 27 +- docs/builtin-compilation.md | 99 - docs/proofs.md | 61 - examples/vesting/README.md | 15 - examples/vesting/app/src/Vesting.nash | 81 +- examples/vesting/app/src/VestingParam.nash | 93 +- proofs/README.md | 225 -- proofs/builtins/AddInteger.nash | 103 - proofs/builtins/AndByteString.nash | 35 - proofs/builtins/AppendByteString.nash | 46 - proofs/builtins/AppendString.nash | 32 - proofs/builtins/BData.nash | 18 - proofs/builtins/Blake2b224.nash | 27 - proofs/builtins/Blake2b256.nash | 27 - proofs/builtins/Bls12381FinalVerify.nash | 32 - proofs/builtins/Bls12381G1Add.nash | 30 - proofs/builtins/Bls12381G1Compress.nash | 25 - proofs/builtins/Bls12381G1Equal.nash | 23 - proofs/builtins/Bls12381G1HashToGroup.nash | 34 - proofs/builtins/Bls12381G1Neg.nash | 29 - proofs/builtins/Bls12381G1ScalarMul.nash | 29 - proofs/builtins/Bls12381G1Uncompress.nash | 47 - proofs/builtins/Bls12381G2Add.nash | 30 - proofs/builtins/Bls12381G2Compress.nash | 25 - proofs/builtins/Bls12381G2Equal.nash | 23 - proofs/builtins/Bls12381G2HashToGroup.nash | 34 - proofs/builtins/Bls12381G2Neg.nash | 29 - proofs/builtins/Bls12381G2ScalarMul.nash | 29 - proofs/builtins/Bls12381G2Uncompress.nash | 47 - proofs/builtins/Bls12381MillerLoop.nash | 34 - proofs/builtins/Bls12381MulMlResult.nash | 40 - proofs/builtins/ByteStringToInteger.nash | 47 - proofs/builtins/ChooseData.nash | 65 - proofs/builtins/ChooseList.nash | 39 - proofs/builtins/ChooseUnit.nash | 22 - proofs/builtins/ComplementByteString.nash | 33 - proofs/builtins/ConsByteString.nash | 97 - proofs/builtins/ConstrData.nash | 33 - proofs/builtins/CountSetBits.nash | 38 - proofs/builtins/DecodeUtf8.nash | 57 - proofs/builtins/DivideInteger.nash | 54 - proofs/builtins/DropList.nash | 48 - proofs/builtins/EncodeUtf8.nash | 38 - proofs/builtins/EqualsByteString.nash | 46 - proofs/builtins/EqualsData.nash | 53 - proofs/builtins/EqualsInteger.nash | 51 - proofs/builtins/EqualsString.nash | 45 - proofs/builtins/ExpModInteger.nash | 47 - proofs/builtins/FindFirstSetBit.nash | 30 - proofs/builtins/FstPair.nash | 19 - proofs/builtins/HeadList.nash | 27 - proofs/builtins/IData.nash | 18 - proofs/builtins/IfThenElse.nash | 62 - proofs/builtins/IndexArray.nash | 66 - proofs/builtins/IndexByteString.nash | 73 - proofs/builtins/InsertCoin.nash | 106 - proofs/builtins/IntegerToByteString.nash | 73 - proofs/builtins/Keccak256.nash | 19 - proofs/builtins/LengthOfArray.nash | 26 - proofs/builtins/LengthOfByteString.nash | 38 - proofs/builtins/LessThanByteString.nash | 30 - proofs/builtins/LessThanEqualsByteString.nash | 33 - proofs/builtins/LessThanEqualsInteger.nash | 51 - proofs/builtins/LessThanInteger.nash | 52 - proofs/builtins/ListData.nash | 33 - proofs/builtins/ListToArray.nash | 27 - proofs/builtins/LookupCoin.nash | 39 - proofs/builtins/MapData.nash | 46 - proofs/builtins/MkCons.nash | 43 - proofs/builtins/MkNilData.nash | 20 - proofs/builtins/MkNilPairData.nash | 20 - proofs/builtins/MkPairData.nash | 25 - proofs/builtins/ModInteger.nash | 71 - proofs/builtins/MultiplyInteger.nash | 71 - proofs/builtins/NullList.nash | 23 - proofs/builtins/OrByteString.nash | 35 - proofs/builtins/QuotientInteger.nash | 54 - proofs/builtins/ReadBit.nash | 56 - proofs/builtins/RemainderInteger.nash | 73 - proofs/builtins/ReplicateByte.nash | 49 - proofs/builtins/Ripemd160.nash | 24 - proofs/builtins/RotateByteString.nash | 61 - proofs/builtins/ScaleValue.nash | 53 - proofs/builtins/SerialiseData.nash | 20 - proofs/builtins/Sha2256.nash | 27 - proofs/builtins/Sha3256.nash | 27 - proofs/builtins/ShiftByteString.nash | 69 - proofs/builtins/SliceByteString.nash | 48 - proofs/builtins/SndPair.nash | 19 - proofs/builtins/SubtractInteger.nash | 48 - proofs/builtins/TailList.nash | 39 - proofs/builtins/Trace.nash | 24 - proofs/builtins/UnBData.nash | 35 - proofs/builtins/UnConstrData.nash | 53 - proofs/builtins/UnIData.nash | 35 - proofs/builtins/UnListData.nash | 47 - proofs/builtins/UnMapData.nash | 61 - proofs/builtins/UnValueData.nash | 139 - proofs/builtins/UnionValue.nash | 39 - proofs/builtins/ValueContains.nash | 46 - proofs/builtins/ValueData.nash | 31 - .../VerifyEcdsaSecp256k1Signature.nash | 33 - proofs/builtins/VerifyEd25519Signature.nash | 56 - .../VerifySchnorrSecp256k1Signature.nash | 33 - proofs/builtins/WriteBits.nash | 61 - proofs/builtins/XorByteString.nash | 35 - proofs/nash.jsonc | 1 - .../builtins/Bls12381G1MultiScalarMul.nash | 63 - .../builtins/Bls12381G2MultiScalarMul.nash | 63 - proofs/pending/nash.jsonc | 1 - proofs/verification.json | 2342 ----------------- proofs/verified.tsv | 66 - 122 files changed, 466 insertions(+), 10040 deletions(-) delete mode 100644 crates/nash-driver/tests/builtin_compilation.rs delete mode 100644 crates/nash-driver/tests/builtin_proofs.rs delete mode 100644 crates/nash-driver/tests/fixtures/builtin-compilation/Certificate.lean delete mode 100644 crates/nash-driver/tests/fixtures/builtin-compilation/spec.tsv delete mode 100644 docs/builtin-compilation.md delete mode 100644 proofs/README.md delete mode 100644 proofs/builtins/AddInteger.nash delete mode 100644 proofs/builtins/AndByteString.nash delete mode 100644 proofs/builtins/AppendByteString.nash delete mode 100644 proofs/builtins/AppendString.nash delete mode 100644 proofs/builtins/BData.nash delete mode 100644 proofs/builtins/Blake2b224.nash delete mode 100644 proofs/builtins/Blake2b256.nash delete mode 100644 proofs/builtins/Bls12381FinalVerify.nash delete mode 100644 proofs/builtins/Bls12381G1Add.nash delete mode 100644 proofs/builtins/Bls12381G1Compress.nash delete mode 100644 proofs/builtins/Bls12381G1Equal.nash delete mode 100644 proofs/builtins/Bls12381G1HashToGroup.nash delete mode 100644 proofs/builtins/Bls12381G1Neg.nash delete mode 100644 proofs/builtins/Bls12381G1ScalarMul.nash delete mode 100644 proofs/builtins/Bls12381G1Uncompress.nash delete mode 100644 proofs/builtins/Bls12381G2Add.nash delete mode 100644 proofs/builtins/Bls12381G2Compress.nash delete mode 100644 proofs/builtins/Bls12381G2Equal.nash delete mode 100644 proofs/builtins/Bls12381G2HashToGroup.nash delete mode 100644 proofs/builtins/Bls12381G2Neg.nash delete mode 100644 proofs/builtins/Bls12381G2ScalarMul.nash delete mode 100644 proofs/builtins/Bls12381G2Uncompress.nash delete mode 100644 proofs/builtins/Bls12381MillerLoop.nash delete mode 100644 proofs/builtins/Bls12381MulMlResult.nash delete mode 100644 proofs/builtins/ByteStringToInteger.nash delete mode 100644 proofs/builtins/ChooseData.nash delete mode 100644 proofs/builtins/ChooseList.nash delete mode 100644 proofs/builtins/ChooseUnit.nash delete mode 100644 proofs/builtins/ComplementByteString.nash delete mode 100644 proofs/builtins/ConsByteString.nash delete mode 100644 proofs/builtins/ConstrData.nash delete mode 100644 proofs/builtins/CountSetBits.nash delete mode 100644 proofs/builtins/DecodeUtf8.nash delete mode 100644 proofs/builtins/DivideInteger.nash delete mode 100644 proofs/builtins/DropList.nash delete mode 100644 proofs/builtins/EncodeUtf8.nash delete mode 100644 proofs/builtins/EqualsByteString.nash delete mode 100644 proofs/builtins/EqualsData.nash delete mode 100644 proofs/builtins/EqualsInteger.nash delete mode 100644 proofs/builtins/EqualsString.nash delete mode 100644 proofs/builtins/ExpModInteger.nash delete mode 100644 proofs/builtins/FindFirstSetBit.nash delete mode 100644 proofs/builtins/FstPair.nash delete mode 100644 proofs/builtins/HeadList.nash delete mode 100644 proofs/builtins/IData.nash delete mode 100644 proofs/builtins/IfThenElse.nash delete mode 100644 proofs/builtins/IndexArray.nash delete mode 100644 proofs/builtins/IndexByteString.nash delete mode 100644 proofs/builtins/InsertCoin.nash delete mode 100644 proofs/builtins/IntegerToByteString.nash delete mode 100644 proofs/builtins/Keccak256.nash delete mode 100644 proofs/builtins/LengthOfArray.nash delete mode 100644 proofs/builtins/LengthOfByteString.nash delete mode 100644 proofs/builtins/LessThanByteString.nash delete mode 100644 proofs/builtins/LessThanEqualsByteString.nash delete mode 100644 proofs/builtins/LessThanEqualsInteger.nash delete mode 100644 proofs/builtins/LessThanInteger.nash delete mode 100644 proofs/builtins/ListData.nash delete mode 100644 proofs/builtins/ListToArray.nash delete mode 100644 proofs/builtins/LookupCoin.nash delete mode 100644 proofs/builtins/MapData.nash delete mode 100644 proofs/builtins/MkCons.nash delete mode 100644 proofs/builtins/MkNilData.nash delete mode 100644 proofs/builtins/MkNilPairData.nash delete mode 100644 proofs/builtins/MkPairData.nash delete mode 100644 proofs/builtins/ModInteger.nash delete mode 100644 proofs/builtins/MultiplyInteger.nash delete mode 100644 proofs/builtins/NullList.nash delete mode 100644 proofs/builtins/OrByteString.nash delete mode 100644 proofs/builtins/QuotientInteger.nash delete mode 100644 proofs/builtins/ReadBit.nash delete mode 100644 proofs/builtins/RemainderInteger.nash delete mode 100644 proofs/builtins/ReplicateByte.nash delete mode 100644 proofs/builtins/Ripemd160.nash delete mode 100644 proofs/builtins/RotateByteString.nash delete mode 100644 proofs/builtins/ScaleValue.nash delete mode 100644 proofs/builtins/SerialiseData.nash delete mode 100644 proofs/builtins/Sha2256.nash delete mode 100644 proofs/builtins/Sha3256.nash delete mode 100644 proofs/builtins/ShiftByteString.nash delete mode 100644 proofs/builtins/SliceByteString.nash delete mode 100644 proofs/builtins/SndPair.nash delete mode 100644 proofs/builtins/SubtractInteger.nash delete mode 100644 proofs/builtins/TailList.nash delete mode 100644 proofs/builtins/Trace.nash delete mode 100644 proofs/builtins/UnBData.nash delete mode 100644 proofs/builtins/UnConstrData.nash delete mode 100644 proofs/builtins/UnIData.nash delete mode 100644 proofs/builtins/UnListData.nash delete mode 100644 proofs/builtins/UnMapData.nash delete mode 100644 proofs/builtins/UnValueData.nash delete mode 100644 proofs/builtins/UnionValue.nash delete mode 100644 proofs/builtins/ValueContains.nash delete mode 100644 proofs/builtins/ValueData.nash delete mode 100644 proofs/builtins/VerifyEcdsaSecp256k1Signature.nash delete mode 100644 proofs/builtins/VerifyEd25519Signature.nash delete mode 100644 proofs/builtins/VerifySchnorrSecp256k1Signature.nash delete mode 100644 proofs/builtins/WriteBits.nash delete mode 100644 proofs/builtins/XorByteString.nash delete mode 100644 proofs/nash.jsonc delete mode 100644 proofs/pending/builtins/Bls12381G1MultiScalarMul.nash delete mode 100644 proofs/pending/builtins/Bls12381G2MultiScalarMul.nash delete mode 100644 proofs/pending/nash.jsonc delete mode 100644 proofs/verification.json delete mode 100644 proofs/verified.tsv diff --git a/.sampo/changesets/lean-blaster-proofs.md b/.sampo/changesets/lean-blaster-proofs.md index f9e56cff..33331a65 100644 --- a/.sampo/changesets/lean-blaster-proofs.md +++ b/.sampo/changesets/lean-blaster-proofs.md @@ -9,7 +9,6 @@ cargo/nash-nitpick: patch cargo/nash-report: minor cargo/nash-fmt: minor cargo/nash-codegen: minor -cargo/nash-plutus: patch cargo/nash-driver: minor cargo/nash-cli: minor cargo/nash-proof: minor @@ -20,23 +19,9 @@ with symbolic primitive and Cardano ledger domains, portable pinned Lean project structured results, explicit SMT trust, and CEK execution limits with exhaustion treated as rejection. Support partial correctness with `Proof.returns`, separate postcondition evaluation -limits, and explicit inconclusive checker exhaustion. Add universal properties -to existing conversion, ordering and arithmetic fixtures and authorization -properties to the vesting examples. Extend deadline, signer and malformed-context -regressions while preserving runtime and oracle coverage. +limits, and explicit inconclusive checker exhaustion. Represent proofs with dedicated source and canonical AST types, symbolic-domain binders, and distinct execution and successful-return obligations. Reject proof budgets during parsing and report proof-specific domain, expectation and postcondition errors before code generation. - -Add independent compilation contracts for all 101 builtins and all application -prefixes. Export kernel-checked syntax certificates and universal CEK equality -corollaries for the 91 builtins supported by pinned PlutusCoreBlaster. Document -the ten missing array/value models and the translation-validation scope. - -Add one Nash semantic specification per builtin with algebraic laws, representation -roundtrips, boundary cases and invalid-input rejection obligations. Record verified -and pending results explicitly and execute the source obligations in regressions, -including negative controls. Fix host-word index truncation, oversized index panics, -large shift/rotation panics and whole-byte left-shift copying in the local evaluator. diff --git a/crates/nash-driver/tests/builtin_compilation.rs b/crates/nash-driver/tests/builtin_compilation.rs deleted file mode 100644 index 223f5b7d..00000000 --- a/crates/nash-driver/tests/builtin_compilation.rs +++ /dev/null @@ -1,352 +0,0 @@ -//! Translation validation against an independent UPLC calling convention. -//! No expected term is compiled through Nash or read from runtime metadata. -use nash_ast::{QualifiedName, Type, primitives}; -use nash_codegen::build::{Build, Input, TraceConfig}; -use nash_driver::{Database, InMemorySource, build_graph, build_with, bundled_base}; -use nash_plutus::{arena::Arena, binder::Eval, flat, term::Term}; -use std::{collections::BTreeSet, fmt::Write, sync::Arc}; -use tokio::sync::Mutex; -use url::Url; - -#[derive(Clone, Debug)] -struct Contract { - name: &'static str, - tag: usize, - forces: usize, - arity: usize, - bind_result: bool, - lean: Option<&'static str>, -} - -fn contracts() -> Vec { - include_str!("fixtures/builtin-compilation/spec.tsv") - .lines() - .filter(|line| !line.starts_with('#') && !line.is_empty()) - .map(|line| { - let fields = line.split('\t').collect::>(); - assert_eq!(fields.len(), 6, "{line}"); - Contract { - name: fields[0], - tag: fields[1].parse().unwrap(), - forces: fields[2].parse().unwrap(), - arity: fields[3].parse().unwrap(), - bind_result: fields[5] == "1", - lean: (fields[4] != "-").then_some(fields[4]), - } - }) - .collect() -} - -#[derive(Clone, Debug, PartialEq, Eq)] -enum Syntax { - Var(usize), - Lam(Box), - Apply(Box, Box), - Force(Box), - Builtin(usize), -} - -impl Syntax { - fn lean(&self) -> String { - match self { - Self::Var(index) => format!("(.var {index})"), - Self::Lam(body) => format!("(.lam {})", body.lean()), - Self::Apply(function, argument) => { - format!("(.apply {} {})", function.lean(), argument.lean()) - } - Self::Force(body) => format!("(.force {})", body.lean()), - Self::Builtin(tag) => format!("(.builtin {tag})"), - } - } -} - -fn captured(term: &Term<'_, nash_plutus::binder::DeBruijn>) -> Syntax { - match term { - // Flat and Nash use one-based indices; the certificate uses zero-based. - Term::Var(index) => Syntax::Var(index.index().checked_sub(1).unwrap()), - Term::Lambda { body, .. } => Syntax::Lam(Box::new(captured(body))), - Term::Apply { function, argument } => { - Syntax::Apply(Box::new(captured(function)), Box::new(captured(argument))) - } - Term::Force(body) => Syntax::Force(Box::new(captured(body))), - Term::Builtin(builtin) => Syntax::Builtin(**builtin as usize), - other => panic!("unexpected builtin wrapper: {other:?}"), - } -} - -fn reference(contract: &Contract, supplied: usize) -> Syntax { - let mut body = Syntax::Builtin(contract.tag); - for _ in 0..contract.forces { - body = Syntax::Force(Box::new(body)); - } - for position in 0..supplied { - body = Syntax::Apply( - Box::new(body), - Box::new(Syntax::Var(supplied - position - 1)), - ); - } - if contract.bind_result && supplied == contract.arity { - body = Syntax::Apply( - Box::new(Syntax::Lam(Box::new(Syntax::Var(0)))), - Box::new(body), - ); - } - for _ in 0..supplied { - body = Syntax::Lam(Box::new(body)); - } - // Ordinary O0 assembly retains the root declaration's administrative let. - Syntax::Apply( - Box::new(Syntax::Lam(Box::new(Syntax::Var(0)))), - Box::new(body), - ) -} - -struct Case { - contract: Contract, - supplied: usize, - flat: Vec, - syntax: Syntax, -} - -async fn compile_cases() -> Vec { - let contracts = contracts(); - assert_eq!(contracts.len(), 101); - assert_eq!(contracts.iter().filter(|c| c.lean.is_some()).count(), 91); - let names = contracts.iter().map(|c| c.name).collect::>(); - assert_eq!(names.len(), contracts.len(), "duplicate specification name"); - assert_eq!( - names, - primitives::BUILTINS.iter().map(|b| b.name).collect(), - "new, removed or renamed builtins require an independent contract" - ); - assert_eq!( - contracts.iter().map(|c| c.tag).collect::>(), - nash_plutus::builtin::DefaultFunction::ALL - .iter() - .map(|f| *f as usize) - .collect(), - "the calling-convention table must cover every runtime tag" - ); - let mut source = String::from("module BuiltinCompilation exposing (..)\nimport Builtin\n"); - for contract in &contracts { - for supplied in 0..=contract.arity { - let parameters = (0..supplied).map(|i| format!(" p{i}")).collect::(); - writeln!( - source, - "builtin{}x{supplied}{parameters} = Builtin.{}{parameters}", - contract.tag, contract.name - ) - .unwrap(); - } - } - let memory = InMemorySource::new(); - let main = Url::parse("file:///builtin-certificates/BuiltinCompilation.nash").unwrap(); - memory.insert(main.clone(), source); - let mut modules = bundled_base::modules(); - modules.insert(main.clone(), None); - let db = Arc::new(Mutex::new(Database::new(memory))); - let graph = build_graph(db.clone(), &modules.keys().cloned().collect::>()) - .await - .unwrap(); - let (report, cases) = build_with(db, &graph, &modules, move |solved| { - let arena = Arena::new(); - let build = Build::new(solved.modules.iter().map(|m| Input { - module: m.module, - types: &m.types, - tables: &m.tables, - })); - let module = solved.modules.iter().find(|m| m.uri == main).unwrap(); - let data = arena.alloc(nash_region::Located::at_zero(Type::Named { - reference: QualifiedName { - home: primitives::primitive_home(), - name: "Data", - }, - args: &[], - })); - let mut cases = Vec::new(); - for contract in contracts { - let function = nash_codegen::builtins::by_name(contract.name).unwrap(); - assert_eq!(function as usize, contract.tag, "{} opcode", contract.name); - assert_eq!( - function.force_count(), - contract.forces, - "{} forces", - contract.name - ); - assert_eq!(function.arity(), contract.arity, "{} arity", contract.name); - for supplied in 0..=contract.arity { - let name = format!("builtin{}x{supplied}", contract.tag); - let annotation = &module.annotations[name.as_str()]; - // Instantiate representation-polymorphic parameters as Data; - // their emitted UPLC calling convention is representation-free. - let args = vec![&*data; annotation.free_vars.len()]; - let core = build - .compile( - &arena, - QualifiedName { - home: module.module.name, - name: &name, - }, - Some(&args), - TraceConfig::default(), - ) - .unwrap(); - let compiled = nash_codegen::program::assemble_core(&arena, core.core).unwrap(); - let syntax = captured(compiled.program.term); - assert_eq!( - syntax, - reference(&contract, supplied), - "{} with {supplied} supplied arguments", - contract.name - ); - let flat = flat::encode(compiled.program).unwrap(); - let decoded = flat::decode::(&arena, &flat).unwrap(); - assert_eq!( - captured(decoded.term), - syntax, - "{} Flat roundtrip", - contract.name - ); - cases.push(Case { - contract: contract.clone(), - supplied, - flat, - syntax, - }); - } - } - cases - }) - .await; - assert!(report.is_success(), "{:#?}", report.ordered_reports()); - cases.unwrap() -} - -fn certificate(cases: &[Case]) -> String { - let mut text = include_str!("fixtures/builtin-compilation/Certificate.lean").to_owned(); - for case in cases { - let contract = &case.contract; - let label = format!("case_{}_{}", contract.tag, case.supplied); - let bind_result = contract.bind_result && case.supplied == contract.arity; - writeln!( - text, - "\n-- Builtin.{} with {} supplied arguments", - contract.name, case.supplied - ) - .unwrap(); - writeln!(text, "def {label} : Syntax := {}", case.syntax.lean()).unwrap(); - writeln!( - text, - "theorem {label}_syntax : {label} = reference {} {} {} {bind_result} := by rfl", - contract.tag, contract.forces, case.supplied - ) - .unwrap(); - writeln!(text, "theorem {label}_observation {{α : Sort u}} (observe : Syntax → α) (args : List Syntax) : observe (args.foldl Syntax.apply {label}) = observe (args.foldl Syntax.apply (reference {} {} {} {bind_result})) := by rw [{label}_syntax]", contract.tag, contract.forces, case.supplied).unwrap(); - if let Some(builtin) = contract.lean { - if case.supplied == 0 { - writeln!(text, "theorem convention_{} : callingConvention (expectedArgs .{builtin}) = ({}, {}) := by rfl", contract.tag, contract.forces, contract.arity).unwrap(); - } - writeln!( - text, - "def {label}_program : Program := flatEncodedScriptFromHexM \"{}\"", - hex::encode(&case.flat) - ) - .unwrap(); - writeln!(text, "theorem {label}_model : body {label}_program = referenceTerm .{builtin} {} {} {bind_result} := by rfl", contract.forces, case.supplied).unwrap(); - writeln!(text, "theorem {label}_cek (variant : PlutusCore.Default.BuiltinSemanticsVariant) (args : List Term) (fuel : Nat) : runKeepingFuel variant (initialState (applyParams (body {label}_program) args)) fuel = runKeepingFuel variant (initialState (applyParams (referenceTerm .{builtin} {} {} {bind_result}) args)) fuel := by rw [{label}_model]", contract.forces, case.supplied).unwrap(); - writeln!(text, "#print axioms {label}_cek").unwrap(); - } - writeln!(text, "#print axioms {label}_observation").unwrap(); - } - text.push_str("\nend NashBuiltinCompilation\n"); - text -} - -#[tokio::test] -async fn all_builtin_references_and_application_prefixes_match_the_contract() { - let cases = compile_cases().await; - assert_eq!(cases.len(), 283); - assert_eq!( - cases.iter().filter(|c| c.contract.lean.is_some()).count(), - 254 - ); - // The independent specification catches opcode, forcing, and argument-order errors. - let add = &cases - .iter() - .find(|c| c.contract.tag == 0 && c.supplied == 2) - .unwrap() - .contract; - let mut wrong_tag = add.clone(); - wrong_tag.tag = 1; - assert_ne!(reference(add, 2), reference(&wrong_tag, 2)); - let mut wrong_forces = add.clone(); - wrong_forces.forces = 1; - assert_ne!(reference(add, 2), reference(&wrong_forces, 2)); - let reversed = Syntax::Apply( - Box::new(Syntax::Apply( - Box::new(Syntax::Builtin(0)), - Box::new(Syntax::Var(0)), - )), - Box::new(Syntax::Var(1)), - ); - let wrapped_reversed = Syntax::Apply( - Box::new(Syntax::Lam(Box::new(Syntax::Var(0)))), - Box::new(Syntax::Lam(Box::new(Syntax::Lam(Box::new(reversed))))), - ); - assert_ne!(reference(add, 2), wrapped_reversed); - // Always render the complete proof export, even without the external toolchain. - let text = certificate(&cases); - assert_eq!(text.matches("_syntax :").count(), cases.len()); - assert!(!text.contains("sorry")); - assert!(!text.contains("native_decide")); -} - -#[tokio::test] -#[ignore = "requires NASH_PROOF_LEAN_PROJECT with pinned PlutusCoreBlaster and Lean 4.24"] -async fn kernel_checks_builtin_compilation_certificates() { - let cases = compile_cases().await; - let directory = - std::env::temp_dir().join(format!("nash-builtin-certificates-{}", std::process::id())); - std::fs::create_dir_all(&directory).unwrap(); - let path = directory.join("BuiltinCompilation.lean"); - std::fs::write(&path, certificate(&cases)).unwrap(); - let result = std::process::Command::new("lake") - .args(["env", "lean"]) - .arg(&path) - .current_dir(std::env::var_os("NASH_PROOF_LEAN_PROJECT").expect("Lean project")) - .output() - .unwrap(); - let diagnostics = format!( - "{}\n{}", - String::from_utf8_lossy(&result.stdout), - String::from_utf8_lossy(&result.stderr) - ); - std::fs::write(directory.join("diagnostics.txt"), &diagnostics).unwrap(); - assert!( - result.status.success(), - "{diagnostics}\nCertificate preserved at {}", - path.display() - ); - assert!(!diagnostics.contains("sorryAx"), "{diagnostics}"); - assert!(!diagnostics.contains("ofReduceBool"), "{diagnostics}"); - assert_eq!( - diagnostics.matches("does not depend on any axioms").count(), - 283, - "{diagnostics}" - ); - let cek_audits = diagnostics - .lines() - .filter(|line| line.contains("_cek' ")) - .collect::>(); - assert_eq!(cek_audits.len(), 254, "{diagnostics}"); - for audit in cek_audits { - assert!( - audit.ends_with("depends on axioms: [propext, Classical.choice, Quot.sound]"), - "unexpected model assumptions: {audit}" - ); - } - println!( - "101 builtins, 283 syntax certificates, 254 CEK certificates (91 modeled builtins); preserved at {}", - path.display() - ); -} diff --git a/crates/nash-driver/tests/builtin_proofs.rs b/crates/nash-driver/tests/builtin_proofs.rs deleted file mode 100644 index cb02d0c4..00000000 --- a/crates/nash-driver/tests/builtin_proofs.rs +++ /dev/null @@ -1,409 +0,0 @@ -//! Execute and export the checked-in semantic obligations, not copies in Rust. -use nash_driver::{Database, FileSystemSource, Project, build_graph_with_tests, test_with}; -use nash_plutus::{ - arena::Arena, binder::DeBruijn, data::PlutusData, flat, machine::ExBudget, term::Term, -}; -use nash_proof::{Domain, ProofProgram}; -use std::{ - collections::BTreeSet, - path::{Path, PathBuf}, - sync::Arc, -}; -use tokio::sync::Mutex; - -fn root() -> PathBuf { - Path::new(env!("CARGO_MANIFEST_DIR")).join("../../proofs") -} - -fn verified_obligations() -> BTreeSet<(String, String)> { - include_str!("../../../proofs/verified.tsv") - .lines() - .filter(|line| !line.starts_with('#') && !line.is_empty()) - .map(|line| { - let (module, name) = line.split_once('\t').unwrap(); - (module.to_owned(), name.to_owned()) - }) - .collect() -} - -async fn compile(directory: &Path) -> Result, String> { - let project = Project::load(directory).await.unwrap(); - let db = Arc::new(Mutex::new(Database::new(FileSystemSource::new()))); - let modules = project.discover_modules(&*db.lock().await).await.unwrap(); - let roots = project - .discover_own_modules(&*db.lock().await) - .await - .unwrap(); - let graph = build_graph_with_tests( - db.clone(), - &modules.keys().cloned().collect::>(), - &roots.keys().cloned().collect::>(), - ) - .await - .unwrap(); - let (report, programs) = test_with(db, &graph, &modules, move |solved| { - nash_driver::build::compile_proofs_matching_with( - solved, - |uri| roots.contains_key(uri).then(nash_config::Build::default), - |_, _| true, - ) - }) - .await; - if !report.is_success() { - return Err(format!("{:#?}", report.ordered_reports())); - } - programs.unwrap().map_err(|e| e.message) -} - -fn sample<'a>( - arena: &'a Arena, - program: &ProofProgram, - domain: Domain, - index: usize, - case: usize, -) -> &'a Term<'a, DeBruijn> { - let integers = [-7, 0, 1, 17, 18446744073709551616, -9223372036854775809]; - let n = integers[(case + index) % integers.len()]; - match domain { - // The host Data representation stores constructor tags as u64. Lean's - // Data model has arbitrary integer tags; that separate host-model gap - // is documented and must not change the universal Nash obligation. - Domain::Int - if matches!(program.module.as_str(), "ConstrData" | "UnConstrData") && index == 0 => - { - Term::integer_from(arena, [0, 1, 17, 255, 65536, 18446744073709551615][case]) - } - Domain::Int => Term::integer_from(arena, n), - Domain::Integer => Term::data_integer_from(arena, n), - Domain::Bool => Term::bool(arena, (case + index).is_multiple_of(2)), - Domain::String => Term::string(arena, ["", "éλ", "a\0b"][(case + index) % 3]), - Domain::Bytes | Domain::ByteString => { - let bytes: &[u8] = if case == 4 && program.module.starts_with("Bls12381") { - let g2 = program.module.contains("G2") - || (!program.module.contains("G1") && index % 2 == 1); - let mut encoded = vec![0; if g2 { 96 } else { 48 }]; - encoded[0] = 0xc0; - arena.alloc_slice_copy(&encoded) - } else { - [b"".as_slice(), &[0], &[0, 1, 128, 255], &[255]][(case + index) % 4] - }; - if domain == Domain::Bytes { - Term::byte_string(arena, bytes) - } else { - Term::data_byte_string(arena, bytes) - } - } - Domain::Data => { - let value = match (case + index) % 5 { - 0 => PlutusData::integer_from(arena, n), - 1 => PlutusData::byte_string(arena, &[0, 255]), - 2 => PlutusData::constr(arena, 0, &[]), - 3 => PlutusData::list(arena, &[]), - _ => PlutusData::map(arena, &[]), - }; - Term::data(arena, value) - } - other => panic!("unexpected builtin domain {other:?}"), - } -} - -#[tokio::test] -async fn checked_in_builtin_obligations_compile_export_and_execute() { - let programs = compile(&root()).await.unwrap(); - assert_eq!( - programs - .iter() - .map(|p| &p.module) - .collect::>() - .len(), - 99 - ); - assert!(programs.len() > 300, "failure-mode obligations disappeared"); - let names = programs - .iter() - .map(|p| (p.module.clone(), p.name.clone())) - .collect::>(); - assert!( - verified_obligations().is_subset(&names), - "a previously verified obligation disappeared" - ); - - let output = - std::env::temp_dir().join(format!("nash-semantic-builtins-{}", std::process::id())); - let files = nash_proof::export(&programs, &output, 300, 300, 10).unwrap(); - assert_eq!(files.len(), programs.len()); - let mut failures = Vec::new(); - for proof in &programs { - let mut successful_returns = 0; - for case in 0..6 { - let arena = Arena::new(); - let mut computation = flat::decode::(&arena, &proof.flat).unwrap(); - let arguments = proof - .domains - .iter() - .enumerate() - .map(|(index, domain)| sample(&arena, proof, *domain, index, case)) - .collect::>(); - for argument in &arguments { - computation = computation.apply(&arena, argument); - } - let result = match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { - computation.eval_version_budget( - &arena, - nash_plutus::machine::PlutusVersion::V3, - ExBudget::max(), - ) - })) { - Ok(evaluated) => evaluated.term, - Err(_) => { - failures.push(format!( - "{} / {} / sample {case}: evaluator panicked", - proof.module, proof.name - )); - continue; - } - }; - let passed = if let Some(checker) = &proof.postcondition { - if let Ok(value) = result { - successful_returns += 1; - let mut condition = flat::decode::(&arena, &checker.flat).unwrap(); - for argument in &arguments { - condition = condition.apply(&arena, argument); - } - let checked = condition.apply(&arena, value).eval_version_budget( - &arena, - nash_plutus::machine::PlutusVersion::V3, - ExBudget::max(), - ); - checked - .term - .is_ok_and(|value| value == Term::bool(&arena, true)) - } else { - true - } - } else { - match proof.expect { - nash_source::Expect::Pass => { - result.is_ok_and(|value| value == Term::unit(&arena)) - } - nash_source::Expect::Fail => result.is_err(), - nash_source::Expect::FailOnce => { - panic!("builtin suite must not search for witnesses") - } - } - }; - if !passed { - failures.push(format!("{} / {} / sample {case}", proof.module, proof.name)); - } - if proof.domains.is_empty() { - break; - } - } - if proof.postcondition.is_some() && successful_returns == 0 { - failures.push(format!( - "{} / {}: all samples vacuous; provide a valid input", - proof.module, proof.name - )); - } - } - assert!(failures.is_empty(), "{}", failures.join("\n")); -} - -#[test] -fn every_builtin_has_its_own_nash_specification() { - let mut covered = BTreeSet::new(); - for builtin in nash_ast::primitives::BUILTINS { - let module = builtin - .name - .split('_') - .map(|part| format!("{}{}", part[..1].to_uppercase(), &part[1..])) - .collect::(); - let file = format!("{module}.nash"); - let active = root().join("builtins").join(&file); - let pending = root().join("pending/builtins").join(&file); - assert_ne!( - active.is_file(), - pending.is_file(), - "exactly one spec for {}", - builtin.name - ); - let source = - std::fs::read_to_string(if active.is_file() { active } else { pending }).unwrap(); - assert!( - source.contains(&format!("Builtin.{}", builtin.name)) && source.contains("\nproof\n"), - "{} has no executable obligation", - builtin.name - ); - covered.insert(module); - } - assert_eq!(covered.len(), 101); -} - -#[tokio::test] -async fn pending_multi_scalar_obligations_fail_explicitly_in_codegen() { - let error = compile(&root().join("pending")).await.unwrap_err(); - assert!( - error.contains("Bls12_381G1Element") && error.contains("unavailable"), - "{error}" - ); -} - -#[tokio::test] -async fn semantic_obligations_detect_wrong_opcodes_argument_order_and_wrapping() { - let mutations = [ - ( - "AddInteger", - "add = Builtin.addInteger", - "add = Builtin.subtractInteger", - "commutative", - 0, - ), - ( - "AddInteger", - "add = Builtin.addInteger", - "add = Builtin.multiplyInteger", - "left identity", - 1, - ), - ( - "AddInteger", - "add = Builtin.addInteger", - "add x _ = x", - "commutative", - 0, - ), - ( - "AddInteger", - "add = Builtin.addInteger", - "add x y = Builtin.modInteger (Builtin.addInteger x y) 18446744073709551616", - "successor never wraps at any integer width", - 18446744073709551615, - ), - ( - "AddInteger", - "add = Builtin.addInteger", - "add x y = let sum = Builtin.addInteger x y in if sum > 9223372036854775807 then 9223372036854775807 else sum", - "successor never wraps at any integer width", - 9223372036854775807, - ), - ( - "AppendByteString", - "Builtin.appendByteString p0 p1", - "Builtin.appendByteString p1 p0", - "preserves byte order", - 0, - ), - ( - "FstPair", - "Builtin.fstPair p0", - "Builtin.sndPair p0", - "projects the correct component", - 0, - ), - ]; - let directory = - std::env::temp_dir().join(format!("nash-builtin-mutants-{}", std::process::id())); - std::fs::create_dir_all(directory.join("src")).unwrap(); - std::fs::write(directory.join("nash.jsonc"), "{\"type\":\"application\"}\n").unwrap(); - for (i, (module, original, mutant, _, _)) in mutations.iter().enumerate() { - let source = - std::fs::read_to_string(root().join("builtins").join(format!("{module}.nash"))) - .unwrap(); - assert!( - source.contains(original), - "mutation no longer applies to {module}" - ); - let source = source - .replacen( - &format!("module {module} "), - &format!("module Mutant{i} "), - 1, - ) - .replacen(original, mutant, 1); - std::fs::write( - directory.join("src").join(format!("Mutant{i}.nash")), - source, - ) - .unwrap(); - } - let programs = compile(&directory).await.unwrap(); - for (i, (_, _, _, obligation, witness)) in mutations.iter().enumerate() { - let proof = programs - .iter() - .find(|p| p.module == format!("Mutant{i}") && p.name == *obligation) - .unwrap(); - let arena = Arena::new(); - let mut program = flat::decode::(&arena, &proof.flat).unwrap(); - for (index, domain) in proof.domains.iter().enumerate() { - let input = match domain { - Domain::Int => Term::integer_from(&arena, witness + index as i128), - Domain::Data => Term::data_integer_from(&arena, index as i128), - other => panic!("unexpected mutant domain {other:?}"), - }; - program = program.apply(&arena, input); - } - assert!( - program - .eval_version_budget( - &arena, - nash_plutus::machine::PlutusVersion::V3, - ExBudget::max() - ) - .term - .is_err(), - "mutation {i} escaped {obligation}" - ); - } -} - -#[tokio::test] -#[ignore = "requires NASH_PROOF_LEAN_PROJECT, the pinned Lean dependencies and Z3"] -async fn live_builtin_semantics() { - let matching = std::env::var("NASH_PROOF_MATCH").ok(); - let verified = verified_obligations(); - let programs = compile(&root()) - .await - .unwrap() - .into_iter() - .filter(|p| { - matching.as_ref().map_or_else( - || verified.contains(&(p.module.clone(), p.name.clone())), - |pattern| format!("{}.{{{}}}", p.module, p.name).contains(pattern), - ) - }) - .collect::>(); - assert!(!programs.is_empty(), "no obligations matched {matching:?}"); - let config = nash_proof::Config { - lean_project: std::env::var_os("NASH_PROOF_LEAN_PROJECT") - .expect("Lean project") - .into(), - fuel: 300, - postcondition_fuel: 300, - solver_timeout: 10, - wall_timeout: std::time::Duration::from_secs(60), - }; - let output = std::env::temp_dir().join(format!( - "nash-live-builtin-semantics-{}", - std::process::id() - )); - let files = nash_proof::export( - &programs, - &output, - config.fuel, - config.postcondition_fuel, - config.solver_timeout, - ) - .unwrap(); - let mut failures = Vec::new(); - for (proof, source) in programs.iter().zip(files) { - let outcome = nash_proof::run(proof, &source, &config).unwrap(); - eprintln!("{}.{{{}}}: {:?}", proof.module, proof.name, outcome.status); - if !outcome.passed() { - failures.push(format!( - "{} / {}: {:?}: {}", - proof.module, proof.name, outcome.status, outcome.diagnostics - )); - } - } - assert!(failures.is_empty(), "{}", failures.join("\n")); -} diff --git a/crates/nash-driver/tests/fixtures/base-traits/DataConversions.nash b/crates/nash-driver/tests/fixtures/base-traits/DataConversions.nash index b9ab5d8b..828171a6 100644 --- a/crates/nash-driver/tests/fixtures/base-traits/DataConversions.nash +++ b/crates/nash-driver/tests/fixtures/base-traits/DataConversions.nash @@ -1,478 +1,207 @@ module Main exposing (..) - -type count - = Count int - +type count = Count int impl ToData count where toData (Count n) = toData n - impl FromData count where fromData raw = Count (fromData raw) - impl Decode count where decode raw = do n <- decode raw Some (Count n) - asInt : Data -> option int asInt = decode - - asBytes : Data -> option bytes asBytes = decode - - asString : Data -> option string asString = decode - - asBool : Data -> option bool asBool = decode - - asUnit : Data -> option unit asUnit = decode - - asList : Data -> option (list int) asList = decode - - asBigList : Data -> option (List Int) asBigList = decode - - asMap : Data -> option (Map Int Bytes) asMap = decode - - asOption : Data -> option (option int) asOption = decode - - asBigOption : Data -> option (Option Int) asBigOption = decode - - asData : Data -> option Data asData = decode - - unchecked : Data -> Int unchecked = fromData - - checked : Data -> Int checked = validate - - little : Data -> int little = fromData - - text : Data -> string text = fromData - - countValue : count countValue = fromData (I 42) - - countDecoded : option count countDecoded = decode (I 42) - -entries = - [ Builtin.mkPairData (I 1) (B #"aa"), Builtin.mkPairData (I 1) (B #"bb") ] - +entries = [Builtin.mkPairData (I 1) (B #"aa"), Builtin.mkPairData (I 1) (B #"bb")] number : int number = 42 - - rawBytes : bytes rawBytes = #"aaff" - - yes : bool yes = True - - no : bool no = False - - numbers : list int -numbers = [ 1, 2 ] - - +numbers = [1, 2] bigOne : Int bigOne = 1 - - firstBytes : Bytes firstBytes = #"aa" - - secondBytes : Bytes secondBytes = #"bb" - - three : int three = 3 - fromList : Data -> list int fromList = fromData - - fromOption : Data -> option int fromOption = fromData - - fromBool : Data -> bool fromBool = fromData - - fromUnit : Data -> unit fromUnit = fromData - - fromBytes : Data -> bytes fromBytes = fromData - - bigBool : Data -> option Bool bigBool = decode - tests - test "little list unpacking" = - do - assert (fromList (toData numbers) == numbers) - - test "little option unpacking" = - do - assert (fromOption (toData (Some number)) == Some number) - - test "little bool unpacking" = - do - assert (fromBool (toData yes) == yes) - - test "little unit unpacking" = - do - assert (fromUnit (toData ()) == ()) - - test "little bytes unpacking" = - do - assert (fromBytes (toData rawBytes) == rawBytes) - - test "Big Bool optional decoding" = - do - assert (bigBool (toData yes) == Some Bool.True) - - test "Big Bool optional rejection" = - do - assert (bigBool (Builtin.constrData 0 [ I 1 ]) == None) - - test "little string unpacking rejects bad UTF8" fail = - do - let - _ = text (B #"80") - in - () - - test "little scalar conversions 1" = - do - assert (toData number == I 42) - - test "little scalar conversions 2" = - do - assert (little (I 42) == 42) - - test "little scalar conversions 3" = - do - assert (asBytes (toData rawBytes) == Some #"aaff") - - test "little scalar conversions 4" = - do - assert (asString (toData "é") == Some "é") - - test "little scalar conversions 5" = - do - assert (text (toData "é") == "é") - - test "little scalar conversions 6" = - do - assert (asBool (toData yes) == Some True) - - test "little scalar conversions 7" = - do - assert (asBool (toData no) == Some False) - - test "little scalar conversions 8" = - do - assert (asUnit (toData ()) == Some ()) - - test "user little conversions and ordinary option composition 1" = - do - assert (toData countValue == I 42) - - test "user little conversions and ordinary option composition 2" = - do - assert - ( - case countDecoded of - Some (Count n) -> - n == 42 - - None -> - False - ) - - test "Big blanket conversion remains unchecked 1" = - do - assert (toData (unchecked (B #"aa")) == B #"aa") - - test "Big blanket conversion remains unchecked 2" = - do - assert (toData (checked (I 42)) == I 42) - - test "independent validation fails" fail = - do - let - _ = checked (B #"aa") - in - () - - test "little unpacking fails" fail = - do - let - _ = little (B #"aa") - in - () - - test "scalar decode wrong shapes 1" = - do - assert (asInt (B #"aa") == None) - - test "scalar decode wrong shapes 2" = - do - assert (asBytes (I 1) == None) - - test "scalar decode wrong shapes 3" = - do - assert (asString (I 1) == None) - - test "scalar decode wrong shapes 4" = - do - assert (asInt (List []) == None) - - test "scalar decode wrong shapes 5" = - do - assert (asInt (Map []) == None) - - test "scalar decode wrong shapes 6" = - do - assert (asInt (Builtin.constrData 0 []) == None) - - test "boolean and unit exact shape 1" = - do - assert (asBool (Builtin.constrData 2 []) == None) - - test "boolean and unit exact shape 2" = - do - assert (asBool (Builtin.constrData 0 [ I 1 ]) == None) - - test "boolean and unit exact shape 3" = - do - assert (asBool (I 0) == None) - - test "boolean and unit exact shape 4" = - do - assert (asUnit (Builtin.constrData 1 []) == None) - - test "boolean and unit exact shape 5" = - do - assert (asUnit (Builtin.constrData 0 [ I 1 ]) == None) - - test "UTF8 accepts boundaries 1" = - do - assert (asString (B #"00c280e0a080eda080") == None) - - test "UTF8 accepts boundaries 2" = - do - assert (asString (B #"00c280e0a080ed9fbff0908080f48fbfbf") /= None) - - test "UTF8 rejects malformed sequences 1" = - do - assert (asString (B #"80") == None) - - test "UTF8 rejects malformed sequences 2" = - do - assert (asString (B #"c080") == None) - - test "UTF8 rejects malformed sequences 3" = - do - assert (asString (B #"c2") == None) - - test "UTF8 rejects malformed sequences 4" = - do - assert (asString (B #"e08080") == None) - - test "UTF8 rejects malformed sequences 5" = - do - assert (asString (B #"eda080") == None) - - test "UTF8 rejects malformed sequences 6" = - do - assert (asString (B #"f0808080") == None) - - test "UTF8 rejects malformed sequences 7" = - do - assert (asString (B #"f4908080") == None) - - test "UTF8 rejects malformed sequences 8" = - do - assert (asString (B #"f5808080") == None) - - test "little collection conversion 1" = - do - assert (asList (toData numbers) == Some [ 1, 2 ]) - - test "little collection conversion 2" = - do - assert (asList (List []) == Some []) - - test "little collection conversion 3" = - do - assert (asList (List [ I 1, B #"aa" ]) == None) - - test "little collection conversion 4" = - do - assert (asList (I 1) == None) - - test "Big collection decode validates children 1" = - do - assert (asBigList (List [ I 1, I 2 ]) == Some (lift [ 1, 2 ])) - - test "Big collection decode validates children 2" = - do - assert (asBigList (List [ B #"aa" ]) == None) - - test "Big collection decode validates children 3" = - do - assert - ( - asMap (Map entries) == - Some - ( - Builtin.mapData - [ Builtin.mkPairData bigOne firstBytes - , Builtin.mkPairData bigOne secondBytes - ] - ) - ) - - test "Big collection decode validates children 4" = - do - assert - (asMap (Map [ Builtin.mkPairData (B #"aa") (B #"bb") ]) == None) - - test "Big collection decode validates children 5" = - do - assert (asMap (Map [ Builtin.mkPairData (I 1) (I 2) ]) == None) - - test "optional values exact shape 1" = - do - assert (asOption (toData (Some three)) == Some (Some 3)) - - test "optional values exact shape 2" = - do - assert (asOption (Builtin.constrData 1 []) == Some None) - - test "optional values exact shape 3" = - do - assert (asOption (Builtin.constrData 0 []) == None) - - test "optional values exact shape 4" = - do - assert (asOption (Builtin.constrData 0 [ B #"aa" ]) == None) - - test "optional values exact shape 5" = - do - assert (asOption (Builtin.constrData 1 [ I 1 ]) == None) - - test "optional values exact shape 6" = - do - assert - ( - asBigOption (Builtin.constrData 0 [ I 3 ]) == - Some (Option.Some 3) - ) - - test "optional values exact shape 7" = - do - assert (asBigOption (Builtin.constrData 0 [ B #"aa" ]) == None) - - test "Data decode preserves any shape 1" = - do - assert (asData (Map entries) == Some (Map entries)) - - -proof - import Proof - - prop "integer Data roundtrip" = - let - value via Proof.int - in - do - assert (little (toData value) == value) - - prop "boolean Data roundtrip" = - let - value via Proof.bool - in - do - assert (fromBool (toData value) == value) - - prop "bytes Data roundtrip" = - let - value via Proof.bytes - in - do - assert (fromBytes (toData value) == value) - - prop "integer optional decoding" = - let - value via Proof.int - in - do - assert (asInt (toData value) == Some value) - - prop "integer decoder rejects bytes of any content" = - let - value via Proof.bytes - in - do - assert (asInt (B value) == None) - - prop "bytes decoder rejects every integer" = - let - value via Proof.int - in - do - assert (asBytes (I value) == None) - - prop "boolean decoder rejects a constructor with a payload" = - let - value via Proof.int - in - do - assert (asBool (Builtin.constrData 0 [ I value ]) == None) + test "little list unpacking" = do + assert (fromList (toData numbers) == numbers) + test "little option unpacking" = do + assert (fromOption (toData (Some number)) == Some number) + test "little bool unpacking" = do + assert (fromBool (toData yes) == yes) + test "little unit unpacking" = do + assert (fromUnit (toData ()) == ()) + test "little bytes unpacking" = do + assert (fromBytes (toData rawBytes) == rawBytes) + test "Big Bool optional decoding" = do + assert (bigBool (toData yes) == Some Bool.True) + test "Big Bool optional rejection" = do + assert (bigBool (Builtin.constrData 0 [I 1]) == None) + test "little string unpacking rejects bad UTF8" fail = do + let _ = text (B #"80") in () + test "little scalar conversions 1" = do + assert (toData number == I 42) + test "little scalar conversions 2" = do + assert (little (I 42) == 42) + test "little scalar conversions 3" = do + assert (asBytes (toData rawBytes) == Some #"aaff") + test "little scalar conversions 4" = do + assert (asString (toData "é") == Some "é") + test "little scalar conversions 5" = do + assert (text (toData "é") == "é") + test "little scalar conversions 6" = do + assert (asBool (toData yes) == Some True) + test "little scalar conversions 7" = do + assert (asBool (toData no) == Some False) + test "little scalar conversions 8" = do + assert (asUnit (toData ()) == Some ()) + test "user little conversions and ordinary option composition 1" = do + assert (toData countValue == I 42) + test "user little conversions and ordinary option composition 2" = do + assert (case countDecoded of + Some (Count n) -> n == 42 + None -> False) + test "Big blanket conversion remains unchecked 1" = do + assert (toData (unchecked (B #"aa")) == B #"aa") + test "Big blanket conversion remains unchecked 2" = do + assert (toData (checked (I 42)) == I 42) + test "independent validation fails" fail = do + let _ = checked (B #"aa") in () + test "little unpacking fails" fail = do + let _ = little (B #"aa") in () + test "scalar decode wrong shapes 1" = do + assert (asInt (B #"aa") == None) + test "scalar decode wrong shapes 2" = do + assert (asBytes (I 1) == None) + test "scalar decode wrong shapes 3" = do + assert (asString (I 1) == None) + test "scalar decode wrong shapes 4" = do + assert (asInt (List []) == None) + test "scalar decode wrong shapes 5" = do + assert (asInt (Map []) == None) + test "scalar decode wrong shapes 6" = do + assert (asInt (Builtin.constrData 0 []) == None) + test "boolean and unit exact shape 1" = do + assert (asBool (Builtin.constrData 2 []) == None) + test "boolean and unit exact shape 2" = do + assert (asBool (Builtin.constrData 0 [I 1]) == None) + test "boolean and unit exact shape 3" = do + assert (asBool (I 0) == None) + test "boolean and unit exact shape 4" = do + assert (asUnit (Builtin.constrData 1 []) == None) + test "boolean and unit exact shape 5" = do + assert (asUnit (Builtin.constrData 0 [I 1]) == None) + test "UTF8 accepts boundaries 1" = do + assert (asString (B #"00c280e0a080eda080") == None) + test "UTF8 accepts boundaries 2" = do + assert (asString (B #"00c280e0a080ed9fbff0908080f48fbfbf") /= None) + test "UTF8 rejects malformed sequences 1" = do + assert (asString (B #"80") == None) + test "UTF8 rejects malformed sequences 2" = do + assert (asString (B #"c080") == None) + test "UTF8 rejects malformed sequences 3" = do + assert (asString (B #"c2") == None) + test "UTF8 rejects malformed sequences 4" = do + assert (asString (B #"e08080") == None) + test "UTF8 rejects malformed sequences 5" = do + assert (asString (B #"eda080") == None) + test "UTF8 rejects malformed sequences 6" = do + assert (asString (B #"f0808080") == None) + test "UTF8 rejects malformed sequences 7" = do + assert (asString (B #"f4908080") == None) + test "UTF8 rejects malformed sequences 8" = do + assert (asString (B #"f5808080") == None) + test "little collection conversion 1" = do + assert (asList (toData numbers) == Some [1, 2]) + test "little collection conversion 2" = do + assert (asList (List []) == Some []) + test "little collection conversion 3" = do + assert (asList (List [I 1, B #"aa"]) == None) + test "little collection conversion 4" = do + assert (asList (I 1) == None) + test "Big collection decode validates children 1" = do + assert (asBigList (List [I 1, I 2]) == Some (lift [1, 2])) + test "Big collection decode validates children 2" = do + assert (asBigList (List [B #"aa"]) == None) + test "Big collection decode validates children 3" = do + assert (asMap (Map entries) == Some (Builtin.mapData [Builtin.mkPairData bigOne firstBytes, Builtin.mkPairData bigOne secondBytes])) + test "Big collection decode validates children 4" = do + assert (asMap (Map [Builtin.mkPairData (B #"aa") (B #"bb")]) == None) + test "Big collection decode validates children 5" = do + assert (asMap (Map [Builtin.mkPairData (I 1) (I 2)]) == None) + test "optional values exact shape 1" = do + assert (asOption (toData (Some three)) == Some (Some 3)) + test "optional values exact shape 2" = do + assert (asOption (Builtin.constrData 1 []) == Some None) + test "optional values exact shape 3" = do + assert (asOption (Builtin.constrData 0 []) == None) + test "optional values exact shape 4" = do + assert (asOption (Builtin.constrData 0 [B #"aa"]) == None) + test "optional values exact shape 5" = do + assert (asOption (Builtin.constrData 1 [I 1]) == None) + test "optional values exact shape 6" = do + assert (asBigOption (Builtin.constrData 0 [I 3]) == Some (Option.Some 3)) + test "optional values exact shape 7" = do + assert (asBigOption (Builtin.constrData 0 [B #"aa"]) == None) + test "Data decode preserves any shape 1" = do + assert (asData (Map entries) == Some (Map entries)) diff --git a/crates/nash-driver/tests/fixtures/base-traits/Equality.nash b/crates/nash-driver/tests/fixtures/base-traits/Equality.nash index 4fe6f8ca..3b359afa 100644 --- a/crates/nash-driver/tests/fixtures/base-traits/Equality.nash +++ b/crates/nash-driver/tests/fixtures/base-traits/Equality.nash @@ -1,214 +1,92 @@ module Main exposing (..) - -type Token - = Token Int - - +type Token = Token Int bigList : List Int -bigList = lift [ Builtin.iData 1, Builtin.iData 2 ] - - +bigList = lift [Builtin.iData 1, Builtin.iData 2] bigMap : Map Int Bytes -bigMap = - Builtin.mapData - [ Builtin.mkPairData (Builtin.iData 1) (Builtin.bData #"aa") ] - +bigMap = Builtin.mapData [Builtin.mkPairData (Builtin.iData 1) (Builtin.bData #"aa")] nativeMap : map Int Int -nativeMap = [ Builtin.mkPairData 1 2, Builtin.mkPairData 1 3 ] - - +nativeMap = [Builtin.mkPairData 1 2, Builtin.mkPairData 1 3] reversedMap : map Int Int -reversedMap = [ Builtin.mkPairData 1 3, Builtin.mkPairData 1 2 ] - - +reversedMap = [Builtin.mkPairData 1 3, Builtin.mkPairData 1 2] emptyMap : map Int Int emptyMap = [] - - sameGeneric : Eq 'a => 'a -> 'a -> bool sameGeneric = eq - - mixedMap : map int (list Data) -mixedMap = [ Builtin.unConstrData (Builtin.constrData 7 []) ] - +mixedMap = [Builtin.unConstrData (Builtin.constrData 7 [])] tests - test "boolean equality and ordering truth tables" = - do - assert (eq False False && eq True True) - assert (neq False True && neq True False) - assert (compare False False == EQ) - assert (compare False True == LT) - assert (compare True False == GT) - assert (compare True True == EQ) - - test "native map alias equality" = - do - assert (sameGeneric nativeMap nativeMap) - assert (nativeMap /= reversedMap) - assert (emptyMap == emptyMap) - assert (emptyMap /= nativeMap) - - test "native map operation results" = - do - assert (Map.union nativeMap emptyMap == nativeMap) - assert - ( - Map.toList nativeMap == - [ Builtin.mkPairData 1 2, Builtin.mkPairData 1 3 ] - ) - - test "mixed native map operations" = - do - assert (Map.get 7 mixedMap == Some []) - assert (Map.remove 7 mixedMap == []) - - test "primitive equality" = - do - assert (1 == 1) - assert (1 /= 2) - assert (#"aa" == #"aa") - assert (#"aa" /= #"ab") - assert ("a" == "a") - assert ("a" /= "b") - assert (True /= False) - assert (() == ()) - - test "list and pair equality" = - do - assert ([ 1, 2 ] == [ 1, 2 ]) - assert ([ 1 ] /= [ 1, 2 ]) - assert ([ 1, 2 ] /= [ 1 ]) - assert ([ 1, 2 ] /= [ 1, 3 ]) - assert - ( - Builtin.unConstrData (Builtin.constrData 1 []) == - Builtin.unConstrData (Builtin.constrData 1 []) - ) - assert - ( - Builtin.unConstrData (Builtin.constrData 1 []) /= - Builtin.unConstrData (Builtin.constrData 2 []) - ) - - test "Big equality" = - do - assert (Token 1 == Token 1) - assert (Token 1 /= Token 2) - assert (bigList == lift [ Builtin.iData 1, Builtin.iData 2 ]) - assert (bigMap == bigMap) - assert (I 1 /= B #"01") - assert (Bool.True /= Bool.False) - assert (Unit.Unit == Unit.Unit) - assert (Option.Some (Token 1) /= Option.None) - assert (Ordering.LT /= Ordering.GT) - - test "little twin equality" = - do - assert (Some 1 == Some 1) - assert (Some 1 /= None) - assert (LT /= GT) - - test "ordering and defaults" = - do - assert (compare 1 2 == LT) - assert (compare 2 2 == EQ) - assert (compare 3 2 == GT) - assert (compare #"00ff" #"01" == LT) - assert (compare "a" "b" == LT) - - test "boolean and list ordering" = - do - assert (compare False True == LT) - assert (compare () () == EQ) - assert (compare [ 1, 2 ] [ 1, 3 ] == LT) - assert (compare [ 1 ] [ 1, 0 ] == LT) - assert (compare [ 1, 0 ] [ 1 ] == GT) - assert (Int.compare (Builtin.iData 2) (Builtin.iData 1) == GT) - assert - ( - Bytes.compare (Builtin.bData #"aa") (Builtin.bData #"bb") == - LT - ) - - test "ordering operators and bounds" = - do - assert (1 < 2 && 2 <= 2 && 3 > 2 && 3 >= 3) - assert (max 1 2 == 2 && min 1 2 == 1) - - test "tuple equality and ordering" = - do - assert (( 1, True ) == ( 1, True )) - assert (( 1, True, #"aa" ) /= ( 1, False, #"aa" )) - assert (( 1, 2, 3, 4 ) /= ( 1, 2, 3, 5 )) - assert (compare ( 1, 2 ) ( 1, 3 ) == LT) - assert (compare ( 1, 2, 3 ) ( 1, 2, 2 ) == GT) - assert (compare ( 1, 2, 3, 4 ) ( 1, 2, 3, 4 ) == EQ) - - -proof - import Proof - - prop "integer ordering agrees with comparison" = - let - left via Proof.int - right via Proof.int - in - do - assert - ( - case compare left right of - LT -> - left < right - - EQ -> - left == right - - GT -> - left > right - ) - - prop "integer ordering is antisymmetric" = - let - left via Proof.int - right via Proof.int - in - do - assert - ( - case ( compare left right, compare right left ) of - ( LT, GT ) -> - True - - ( EQ, EQ ) -> - True - - ( GT, LT ) -> - True - - _ -> - False - ) - - prop "minimum and maximum partition integer inputs" = - let - left via Proof.int - right via Proof.int - in - do - assert (min left right + max left right == left + right) - - prop "boolean equality agrees with logic" = - let - left via Proof.bool - right via Proof.bool - in - do - assert - ( - (left == right) == - ((left && right) || (not left && not right)) - ) + test "boolean equality and ordering truth tables" = do + assert (eq False False && eq True True) + assert (neq False True && neq True False) + assert (compare False False == EQ) + assert (compare False True == LT) + assert (compare True False == GT) + assert (compare True True == EQ) + test "native map alias equality" = do + assert (sameGeneric nativeMap nativeMap) + assert (nativeMap /= reversedMap) + assert (emptyMap == emptyMap) + assert (emptyMap /= nativeMap) + test "native map operation results" = do + assert (Map.union nativeMap emptyMap == nativeMap) + assert (Map.toList nativeMap == [Builtin.mkPairData 1 2, Builtin.mkPairData 1 3]) + test "mixed native map operations" = do + assert (Map.get 7 mixedMap == Some []) + assert (Map.remove 7 mixedMap == []) + test "primitive equality" = do + assert (1 == 1) + assert (1 /= 2) + assert (#"aa" == #"aa") + assert (#"aa" /= #"ab") + assert ("a" == "a") + assert ("a" /= "b") + assert (True /= False) + assert (() == ()) + test "list and pair equality" = do + assert ([1, 2] == [1, 2]) + assert ([1] /= [1, 2]) + assert ([1, 2] /= [1]) + assert ([1, 2] /= [1, 3]) + assert (Builtin.unConstrData (Builtin.constrData 1 []) == Builtin.unConstrData (Builtin.constrData 1 [])) + assert (Builtin.unConstrData (Builtin.constrData 1 []) /= Builtin.unConstrData (Builtin.constrData 2 [])) + test "Big equality" = do + assert (Token 1 == Token 1) + assert (Token 1 /= Token 2) + assert (bigList == lift [Builtin.iData 1, Builtin.iData 2]) + assert (bigMap == bigMap) + assert (I 1 /= B #"01") + assert (Bool.True /= Bool.False) + assert (Unit.Unit == Unit.Unit) + assert (Option.Some (Token 1) /= Option.None) + assert (Ordering.LT /= Ordering.GT) + test "little twin equality" = do + assert (Some 1 == Some 1) + assert (Some 1 /= None) + assert (LT /= GT) + test "ordering and defaults" = do + assert (compare 1 2 == LT) + assert (compare 2 2 == EQ) + assert (compare 3 2 == GT) + assert (compare #"00ff" #"01" == LT) + assert (compare "a" "b" == LT) + test "boolean and list ordering" = do + assert (compare False True == LT) + assert (compare () () == EQ) + assert (compare [1, 2] [1, 3] == LT) + assert (compare [1] [1, 0] == LT) + assert (compare [1, 0] [1] == GT) + assert (Int.compare (Builtin.iData 2) (Builtin.iData 1) == GT) + assert (Bytes.compare (Builtin.bData #"aa") (Builtin.bData #"bb") == LT) + test "ordering operators and bounds" = do + assert (1 < 2 && 2 <= 2 && 3 > 2 && 3 >= 3) + assert (max 1 2 == 2 && min 1 2 == 1) + test "tuple equality and ordering" = do + assert ((1, True) == (1, True)) + assert ((1, True, #"aa") /= (1, False, #"aa")) + assert ((1, 2, 3, 4) /= (1, 2, 3, 5)) + assert (compare (1, 2) (1, 3) == LT) + assert (compare (1, 2, 3) (1, 2, 2) == GT) + assert (compare (1, 2, 3, 4) (1, 2, 3, 4) == EQ) diff --git a/crates/nash-driver/tests/fixtures/base-traits/IntegerMath.nash b/crates/nash-driver/tests/fixtures/base-traits/IntegerMath.nash index 390f7b89..5d641cc8 100644 --- a/crates/nash-driver/tests/fixtures/base-traits/IntegerMath.nash +++ b/crates/nash-driver/tests/fixtures/base-traits/IntegerMath.nash @@ -1,1006 +1,232 @@ module Main exposing (..) - big : Int big = 48 - - bigGcd : Int bigGcd = 6 - - bigLcm : Int bigLcm = 144 - tests - test "gcd mixed inputs" = - do - assert (Int.gcd big 18 == 6) - - test "gcd zero" = - do - assert (Int.gcd 0 0 == 0) - - test "gcd signs" = - do - assert (Int.gcd (-48) (-18) == 6) - - test "lcm mixed inputs" = - do - assert (Int.lcm 18 big == 144) - - test "lcm zero" = - do - assert (Int.lcm 0 0 == 0) - - test "lcm signs" = - do - assert (Int.lcm (-48) 18 == 144) - - test "sqrt zero" = - do - assert (Int.isqrt 0 == 0) - - test "sqrt one" = - do - assert (Int.isqrt 1 == 1) - - test "sqrt rounds down" = - do - assert (Int.isqrt big == 6) - - test "sqrt exact" = - do - assert (Int.isqrt 49 == 7) - - test "sqrt rejects negative" fail = - do - let - _ = Int.isqrt (-1) - in - () - - test "oracle gcd 0" = - do - assert (Int.gcd 0 0 == 0) - - test "oracle lcm 0" = - do - assert (Int.lcm 0 0 == 0) - - test "oracle isGcd 0" = - do - assert (Int.isGcd 0 0 0) - - test "oracle isGcd rejects 0" = - do - assert (not (Int.isGcd 0 0 1)) - - test "oracle isLcm 0" = - do - assert (Int.isLcm 0 0 0) - - test "oracle isLcm rejects 0" = - do - assert (not (Int.isLcm 0 0 1)) - - test "oracle gcd 1" = - do - assert (Int.gcd 0 (-18) == 18) - - test "oracle lcm 1" = - do - assert (Int.lcm 0 (-18) == 0) - - test "oracle isGcd 1" = - do - assert (Int.isGcd 0 (-18) 18) - - test "oracle isGcd rejects 1" = - do - assert (not (Int.isGcd 0 (-18) 19)) - - test "oracle isLcm 1" = - do - assert (Int.isLcm 0 (-18) 0) - - test "oracle isLcm rejects 1" = - do - assert (not (Int.isLcm 0 (-18) 1)) - - test "oracle gcd 2" = - do - assert (Int.gcd (-48) 18 == 6) - - test "oracle lcm 2" = - do - assert (Int.lcm (-48) 18 == 144) - - test "oracle isGcd 2" = - do - assert (Int.isGcd (-48) 18 6) - - test "oracle isGcd rejects 2" = - do - assert (not (Int.isGcd (-48) 18 7)) - - test "oracle isLcm 2" = - do - assert (Int.isLcm (-48) 18 144) - - test "oracle isLcm rejects 2" = - do - assert (not (Int.isLcm (-48) 18 145)) - - test "oracle gcd 3" = - do - assert (Int.gcd 48 (-18) == 6) - - test "oracle lcm 3" = - do - assert (Int.lcm 48 (-18) == 144) - - test "oracle isGcd 3" = - do - assert (Int.isGcd 48 (-18) 6) - - test "oracle isGcd rejects 3" = - do - assert (not (Int.isGcd 48 (-18) 7)) - - test "oracle isLcm 3" = - do - assert (Int.isLcm 48 (-18) 144) - - test "oracle isLcm rejects 3" = - do - assert (not (Int.isLcm 48 (-18) 145)) - - test "oracle gcd 4" = - do - assert (Int.gcd (-48) (-18) == 6) - - test "oracle lcm 4" = - do - assert (Int.lcm (-48) (-18) == 144) - - test "oracle isGcd 4" = - do - assert (Int.isGcd (-48) (-18) 6) - - test "oracle isGcd rejects 4" = - do - assert (not (Int.isGcd (-48) (-18) 7)) - - test "oracle isLcm 4" = - do - assert (Int.isLcm (-48) (-18) 144) - - test "oracle isLcm rejects 4" = - do - assert (not (Int.isLcm (-48) (-18) 145)) - - test "oracle gcd 5" = - do - assert (Int.gcd 35 64 == 1) - - test "oracle lcm 5" = - do - assert (Int.lcm 35 64 == 2240) - - test "oracle isGcd 5" = - do - assert (Int.isGcd 35 64 1) - - test "oracle isGcd rejects 5" = - do - assert (not (Int.isGcd 35 64 2)) - - test "oracle isLcm 5" = - do - assert (Int.isLcm 35 64 2240) - - test "oracle isLcm rejects 5" = - do - assert (not (Int.isLcm 35 64 2241)) - - test "oracle gcd 6" = - do - assert - ( - Int.gcd - ( - Int.fromBytes - True - #"1500000000000000000000000000000015" - ) - ( - Int.fromBytes - True - #"2300000000000000000000000000000023" - ) == - Int.fromBytes True #"0700000000000000000000000000000007" - ) - - test "oracle lcm 6" = - do - assert - ( - Int.lcm - ( - Int.fromBytes - True - #"1500000000000000000000000000000015" - ) - ( - Int.fromBytes - True - #"2300000000000000000000000000000023" - ) == - Int.fromBytes True #"6900000000000000000000000000000069" - ) - - test "oracle isGcd 6" = - do - assert - ( - Int.isGcd - ( - Int.fromBytes - True - #"1500000000000000000000000000000015" - ) - ( - Int.fromBytes - True - #"2300000000000000000000000000000023" - ) - ( - Int.fromBytes - True - #"0700000000000000000000000000000007" - ) - ) - - test "oracle isGcd rejects 6" = - do - assert - ( - not - ( - Int.isGcd - ( - Int.fromBytes - True - #"1500000000000000000000000000000015" - ) - ( - Int.fromBytes - True - #"2300000000000000000000000000000023" - ) - ( - Int.fromBytes - True - #"0700000000000000000000000000000008" - ) - ) - ) - - test "oracle isLcm 6" = - do - assert - ( - Int.isLcm - ( - Int.fromBytes - True - #"1500000000000000000000000000000015" - ) - ( - Int.fromBytes - True - #"2300000000000000000000000000000023" - ) - ( - Int.fromBytes - True - #"6900000000000000000000000000000069" - ) - ) - - test "oracle isLcm rejects 6" = - do - assert - ( - not - ( - Int.isLcm - ( - Int.fromBytes - True - #"1500000000000000000000000000000015" - ) - ( - Int.fromBytes - True - #"2300000000000000000000000000000023" - ) - ( - Int.fromBytes - True - #"690000000000000000000000000000006a" - ) - ) - ) - - test "oracle sqrt 0" = - do - assert (Int.isqrt 0 == 0) - - test "oracle isSqrt 0" = - do - assert (Int.isSqrt 0 0) - - test "oracle isSqrt rejects 0" = - do - assert (not (Int.isSqrt 0 1)) - - test "oracle sqrt 1" = - do - assert (Int.isqrt 1 == 1) - - test "oracle isSqrt 1" = - do - assert (Int.isSqrt 1 1) - - test "oracle isSqrt rejects 1" = - do - assert (not (Int.isSqrt 1 2)) - - test "oracle sqrt 2" = - do - assert (Int.isqrt 2 == 1) - - test "oracle isSqrt 2" = - do - assert (Int.isSqrt 2 1) - - test "oracle isSqrt rejects 2" = - do - assert (not (Int.isSqrt 2 2)) - - test "oracle sqrt 3" = - do - assert (Int.isqrt 3 == 1) - - test "oracle isSqrt 3" = - do - assert (Int.isSqrt 3 1) - - test "oracle isSqrt rejects 3" = - do - assert (not (Int.isSqrt 3 2)) - - test "oracle sqrt 4" = - do - assert (Int.isqrt 4 == 2) - - test "oracle isSqrt 4" = - do - assert (Int.isSqrt 4 2) - - test "oracle isSqrt rejects 4" = - do - assert (not (Int.isSqrt 4 3)) - - test "oracle sqrt 5" = - do - assert (Int.isqrt 15 == 3) - - test "oracle isSqrt 5" = - do - assert (Int.isSqrt 15 3) - - test "oracle isSqrt rejects 5" = - do - assert (not (Int.isSqrt 15 4)) - - test "oracle sqrt 6" = - do - assert (Int.isqrt 16 == 4) - - test "oracle isSqrt 6" = - do - assert (Int.isSqrt 16 4) - - test "oracle isSqrt rejects 6" = - do - assert (not (Int.isSqrt 16 5)) - - test "oracle sqrt 7" = - do - assert (Int.isqrt 17 == 4) - - test "oracle isSqrt 7" = - do - assert (Int.isSqrt 17 4) - - test "oracle isSqrt rejects 7" = - do - assert (not (Int.isSqrt 17 5)) - - test "oracle sqrt 8" = - do - assert - ( - Int.isqrt - ( - Int.fromBytes - True - #"0100000000000000020000000000000000" - ) == - 18446744073709551616 - ) - - test "oracle isSqrt 8" = - do - assert - ( - Int.isSqrt - ( - Int.fromBytes - True - #"0100000000000000020000000000000000" - ) - 18446744073709551616 - ) - - test "oracle isSqrt rejects 8" = - do - assert - ( - not - ( - Int.isSqrt - ( - Int.fromBytes - True - #"0100000000000000020000000000000000" - ) - 18446744073709551617 - ) - ) - - test "oracle sqrt 9" = - do - assert - ( - Int.isqrt - ( - Int.fromBytes - True - #"0100000000000000020000000000000001" - ) == - 18446744073709551617 - ) - - test "oracle isSqrt 9" = - do - assert - ( - Int.isSqrt - ( - Int.fromBytes - True - #"0100000000000000020000000000000001" - ) - 18446744073709551617 - ) - - test "oracle isSqrt rejects 9" = - do - assert - ( - not - ( - Int.isSqrt - ( - Int.fromBytes - True - #"0100000000000000020000000000000001" - ) - 18446744073709551618 - ) - ) - - test "oracle sqrt 10" = - do - assert - ( - Int.isqrt - ( - Int.fromBytes - True - #"0100000000000000020000000000000002" - ) == - 18446744073709551617 - ) - - test "oracle isSqrt 10" = - do - assert - ( - Int.isSqrt - ( - Int.fromBytes - True - #"0100000000000000020000000000000002" - ) - 18446744073709551617 - ) - - test "oracle isSqrt rejects 10" = - do - assert - ( - not - ( - Int.isSqrt - ( - Int.fromBytes - True - #"0100000000000000020000000000000002" - ) - 18446744073709551618 - ) - ) - - test "oracle sqrt 11" = - do - assert - ( - Int.isqrt - ( - Int.fromBytes - True - #"0100000000000000040000000000000003" - ) == - 18446744073709551617 - ) - - test "oracle isSqrt 11" = - do - assert - ( - Int.isSqrt - ( - Int.fromBytes - True - #"0100000000000000040000000000000003" - ) - 18446744073709551617 - ) - - test "oracle isSqrt rejects 11" = - do - assert - ( - not - ( - Int.isSqrt - ( - Int.fromBytes - True - #"0100000000000000040000000000000003" - ) - 18446744073709551618 - ) - ) - - test "oracle sqrt 12" = - do - assert - ( - Int.isqrt - ( - Int.fromBytes - True - #"01000000000000000000000000000000f600000000000000000000000000003b18" - ) == - Int.fromBytes True #"010000000000000000000000000000007a" - ) - - test "oracle isSqrt 12" = - do - assert - ( - Int.isSqrt - ( - Int.fromBytes - True - #"01000000000000000000000000000000f600000000000000000000000000003b18" - ) - ( - Int.fromBytes - True - #"010000000000000000000000000000007a" - ) - ) - - test "oracle isSqrt rejects 12" = - do - assert - ( - not - ( - Int.isSqrt - ( - Int.fromBytes - True - #"01000000000000000000000000000000f600000000000000000000000000003b18" - ) - ( - Int.fromBytes - True - #"010000000000000000000000000000007b" - ) - ) - ) - - test "oracle sqrt 13" = - do - assert - ( - Int.isqrt - ( - Int.fromBytes - True - #"01000000000000000000000000000000f600000000000000000000000000003b19" - ) == - Int.fromBytes True #"010000000000000000000000000000007b" - ) - - test "oracle isSqrt 13" = - do - assert - ( - Int.isSqrt - ( - Int.fromBytes - True - #"01000000000000000000000000000000f600000000000000000000000000003b19" - ) - ( - Int.fromBytes - True - #"010000000000000000000000000000007b" - ) - ) - - test "oracle isSqrt rejects 13" = - do - assert - ( - not - ( - Int.isSqrt - ( - Int.fromBytes - True - #"01000000000000000000000000000000f600000000000000000000000000003b19" - ) - ( - Int.fromBytes - True - #"010000000000000000000000000000007c" - ) - ) - ) - - test "oracle sqrt 14" = - do - assert - ( - Int.isqrt - ( - Int.fromBytes - True - #"01000000000000000000000000000000f600000000000000000000000000003b1a" - ) == - Int.fromBytes True #"010000000000000000000000000000007b" - ) - - test "oracle isSqrt 14" = - do - assert - ( - Int.isSqrt - ( - Int.fromBytes - True - #"01000000000000000000000000000000f600000000000000000000000000003b1a" - ) - ( - Int.fromBytes - True - #"010000000000000000000000000000007b" - ) - ) - - test "oracle isSqrt rejects 14" = - do - assert - ( - not - ( - Int.isSqrt - ( - Int.fromBytes - True - #"01000000000000000000000000000000f600000000000000000000000000003b1a" - ) - ( - Int.fromBytes - True - #"010000000000000000000000000000007c" - ) - ) - ) - - test "oracle sqrt 15" = - do - assert - ( - Int.isqrt - ( - Int.fromBytes - True - #"01000000000000000000000000000000f800000000000000000000000000003c0f" - ) == - Int.fromBytes True #"010000000000000000000000000000007b" - ) - - test "oracle isSqrt 15" = - do - assert - ( - Int.isSqrt - ( - Int.fromBytes - True - #"01000000000000000000000000000000f800000000000000000000000000003c0f" - ) - ( - Int.fromBytes - True - #"010000000000000000000000000000007b" - ) - ) - - test "oracle isSqrt rejects 15" = - do - assert - ( - not - ( - Int.isSqrt - ( - Int.fromBytes - True - #"01000000000000000000000000000000f800000000000000000000000000003c0f" - ) - ( - Int.fromBytes - True - #"010000000000000000000000000000007c" - ) - ) - ) - - test "gcd negative expected" = - do - assert (not (Int.isGcd 12 18 (-6))) - - test "lcm negative expected" = - do - assert (not (Int.isLcm 12 18 (-36))) - - test "sqrt negative expected" = - do - assert (not (Int.isSqrt 4 (-2))) - - test "sqrt negative input" = - do - assert (not (Int.isSqrt (-4) 2)) - - test "reject non-greatest common divisor" = - do - assert (not (Int.isGcd 12 18 3)) - - test "reject non-least common multiple" = - do - assert (not (Int.isLcm 12 18 72)) - - test "predicate mixed Big inputs" = - do - assert - ( - Int.isGcd big 18 bigGcd && - Int.isLcm big 18 bigLcm && - Int.isSqrt big bigGcd - ) - - test "gcd predicate exhaustive small inputs" = - do - assert - ( - List.all - ( - \a -> - List.all - ( - \b -> - List.all - ( - \g -> - Int.isGcd a b g == - (Int.gcd a b == g) - ) - (List.range (-1) 6) - ) - (List.range (-3) 4) - ) - (List.range (-3) 4) - ) - - test "lcm predicate exhaustive small inputs" = - do - assert - ( - List.all - ( - \a -> - List.all - ( - \b -> - List.all - ( - \m -> - Int.isLcm a b m == - (Int.lcm a b == m) - ) - (List.range (-1) 14) - ) - (List.range (-3) 4) - ) - (List.range (-3) 4) - ) - - test "sqrt predicate exhaustive small inputs" = - do - assert - ( - List.all - ( - \n -> - List.all - (\r -> Int.isSqrt n r == (Int.isqrt n == r)) - (List.range (-1) 12) - ) - (List.range 0 101) - ) - - -proof - import Proof - - -- Partial correctness checks successful results, without requiring termination. - -- The adjacent-squares predicate does not call the square-root algorithm. - prop "square root satisfies adjacent squares when it returns" = - let - value via Proof.int - in - do - Proof.returns - (Int.isqrt value) - ( - \root -> - root >= - 0 && - root * - root <= - value && - value < - (root + 1) * - (root + 1) - ) - - -- Every positive common divisor must divide the returned gcd. - prop "gcd satisfies the greatest common divisor specification" = - let - left via Proof.int - right via Proof.int - candidate via Proof.int - in - do - Proof.returns - (Int.gcd left right) - ( - \divisor -> - ( - if divisor == 0 then - left == 0 && right == 0 - else - divisor > - 0 && - left % - divisor == - 0 && - right % - divisor == - 0 - ) && - ( - if candidate > - 0 && - left % - candidate == - 0 && - right % - candidate == - 0 then - divisor % candidate == 0 - else - True - ) - ) - - -- Every positive common multiple must be divisible by the returned lcm. - prop "lcm satisfies the least common multiple specification" = - let - left via Proof.int - right via Proof.int - candidate via Proof.int - in - do - Proof.returns - (Int.lcm left right) - ( - \multiple -> - if left == 0 || right == 0 then - multiple == 0 - else - multiple > - 0 && - multiple % - left == - 0 && - multiple % - right == - 0 && - ( - if candidate > - 0 && - candidate % - left == - 0 && - candidate % - right == - 0 then - candidate % multiple == 0 - else - True - ) - ) - - prop "square root rejects every negative input" fail = - let - value via Proof.int - in - do - let - _ = Int.isqrt (-1 - Int.abs value) - in - () + test "gcd mixed inputs" = do + assert (Int.gcd big 18 == 6) + test "gcd zero" = do + assert (Int.gcd 0 0 == 0) + test "gcd signs" = do + assert (Int.gcd -48 -18 == 6) + test "lcm mixed inputs" = do + assert (Int.lcm 18 big == 144) + test "lcm zero" = do + assert (Int.lcm 0 0 == 0) + test "lcm signs" = do + assert (Int.lcm -48 18 == 144) + test "sqrt zero" = do + assert (Int.isqrt 0 == 0) + test "sqrt one" = do + assert (Int.isqrt 1 == 1) + test "sqrt rounds down" = do + assert (Int.isqrt big == 6) + test "sqrt exact" = do + assert (Int.isqrt 49 == 7) + test "sqrt rejects negative" fail = do + let _ = Int.isqrt -1 in () + test "oracle gcd 0" = do + assert (Int.gcd (0) (0) == 0) + test "oracle lcm 0" = do + assert (Int.lcm (0) (0) == 0) + test "oracle isGcd 0" = do + assert (Int.isGcd (0) (0) 0) + test "oracle isGcd rejects 0" = do + assert (not (Int.isGcd (0) (0) 1)) + test "oracle isLcm 0" = do + assert (Int.isLcm (0) (0) 0) + test "oracle isLcm rejects 0" = do + assert (not (Int.isLcm (0) (0) 1)) + test "oracle gcd 1" = do + assert (Int.gcd (0) (-18) == 18) + test "oracle lcm 1" = do + assert (Int.lcm (0) (-18) == 0) + test "oracle isGcd 1" = do + assert (Int.isGcd (0) (-18) 18) + test "oracle isGcd rejects 1" = do + assert (not (Int.isGcd (0) (-18) 19)) + test "oracle isLcm 1" = do + assert (Int.isLcm (0) (-18) 0) + test "oracle isLcm rejects 1" = do + assert (not (Int.isLcm (0) (-18) 1)) + test "oracle gcd 2" = do + assert (Int.gcd (-48) (18) == 6) + test "oracle lcm 2" = do + assert (Int.lcm (-48) (18) == 144) + test "oracle isGcd 2" = do + assert (Int.isGcd (-48) (18) 6) + test "oracle isGcd rejects 2" = do + assert (not (Int.isGcd (-48) (18) 7)) + test "oracle isLcm 2" = do + assert (Int.isLcm (-48) (18) 144) + test "oracle isLcm rejects 2" = do + assert (not (Int.isLcm (-48) (18) 145)) + test "oracle gcd 3" = do + assert (Int.gcd (48) (-18) == 6) + test "oracle lcm 3" = do + assert (Int.lcm (48) (-18) == 144) + test "oracle isGcd 3" = do + assert (Int.isGcd (48) (-18) 6) + test "oracle isGcd rejects 3" = do + assert (not (Int.isGcd (48) (-18) 7)) + test "oracle isLcm 3" = do + assert (Int.isLcm (48) (-18) 144) + test "oracle isLcm rejects 3" = do + assert (not (Int.isLcm (48) (-18) 145)) + test "oracle gcd 4" = do + assert (Int.gcd (-48) (-18) == 6) + test "oracle lcm 4" = do + assert (Int.lcm (-48) (-18) == 144) + test "oracle isGcd 4" = do + assert (Int.isGcd (-48) (-18) 6) + test "oracle isGcd rejects 4" = do + assert (not (Int.isGcd (-48) (-18) 7)) + test "oracle isLcm 4" = do + assert (Int.isLcm (-48) (-18) 144) + test "oracle isLcm rejects 4" = do + assert (not (Int.isLcm (-48) (-18) 145)) + test "oracle gcd 5" = do + assert (Int.gcd (35) (64) == 1) + test "oracle lcm 5" = do + assert (Int.lcm (35) (64) == 2240) + test "oracle isGcd 5" = do + assert (Int.isGcd (35) (64) 1) + test "oracle isGcd rejects 5" = do + assert (not (Int.isGcd (35) (64) 2)) + test "oracle isLcm 5" = do + assert (Int.isLcm (35) (64) 2240) + test "oracle isLcm rejects 5" = do + assert (not (Int.isLcm (35) (64) 2241)) + test "oracle gcd 6" = do + assert (Int.gcd ((Int.fromBytes True #"1500000000000000000000000000000015")) ((Int.fromBytes True #"2300000000000000000000000000000023")) == (Int.fromBytes True #"0700000000000000000000000000000007")) + test "oracle lcm 6" = do + assert (Int.lcm ((Int.fromBytes True #"1500000000000000000000000000000015")) ((Int.fromBytes True #"2300000000000000000000000000000023")) == (Int.fromBytes True #"6900000000000000000000000000000069")) + test "oracle isGcd 6" = do + assert (Int.isGcd ((Int.fromBytes True #"1500000000000000000000000000000015")) ((Int.fromBytes True #"2300000000000000000000000000000023")) (Int.fromBytes True #"0700000000000000000000000000000007")) + test "oracle isGcd rejects 6" = do + assert (not (Int.isGcd ((Int.fromBytes True #"1500000000000000000000000000000015")) ((Int.fromBytes True #"2300000000000000000000000000000023")) (Int.fromBytes True #"0700000000000000000000000000000008"))) + test "oracle isLcm 6" = do + assert (Int.isLcm ((Int.fromBytes True #"1500000000000000000000000000000015")) ((Int.fromBytes True #"2300000000000000000000000000000023")) (Int.fromBytes True #"6900000000000000000000000000000069")) + test "oracle isLcm rejects 6" = do + assert (not (Int.isLcm ((Int.fromBytes True #"1500000000000000000000000000000015")) ((Int.fromBytes True #"2300000000000000000000000000000023")) (Int.fromBytes True #"690000000000000000000000000000006a"))) + test "oracle sqrt 0" = do + assert (Int.isqrt 0 == 0) + test "oracle isSqrt 0" = do + assert (Int.isSqrt 0 0) + test "oracle isSqrt rejects 0" = do + assert (not (Int.isSqrt 0 1)) + test "oracle sqrt 1" = do + assert (Int.isqrt 1 == 1) + test "oracle isSqrt 1" = do + assert (Int.isSqrt 1 1) + test "oracle isSqrt rejects 1" = do + assert (not (Int.isSqrt 1 2)) + test "oracle sqrt 2" = do + assert (Int.isqrt 2 == 1) + test "oracle isSqrt 2" = do + assert (Int.isSqrt 2 1) + test "oracle isSqrt rejects 2" = do + assert (not (Int.isSqrt 2 2)) + test "oracle sqrt 3" = do + assert (Int.isqrt 3 == 1) + test "oracle isSqrt 3" = do + assert (Int.isSqrt 3 1) + test "oracle isSqrt rejects 3" = do + assert (not (Int.isSqrt 3 2)) + test "oracle sqrt 4" = do + assert (Int.isqrt 4 == 2) + test "oracle isSqrt 4" = do + assert (Int.isSqrt 4 2) + test "oracle isSqrt rejects 4" = do + assert (not (Int.isSqrt 4 3)) + test "oracle sqrt 5" = do + assert (Int.isqrt 15 == 3) + test "oracle isSqrt 5" = do + assert (Int.isSqrt 15 3) + test "oracle isSqrt rejects 5" = do + assert (not (Int.isSqrt 15 4)) + test "oracle sqrt 6" = do + assert (Int.isqrt 16 == 4) + test "oracle isSqrt 6" = do + assert (Int.isSqrt 16 4) + test "oracle isSqrt rejects 6" = do + assert (not (Int.isSqrt 16 5)) + test "oracle sqrt 7" = do + assert (Int.isqrt 17 == 4) + test "oracle isSqrt 7" = do + assert (Int.isSqrt 17 4) + test "oracle isSqrt rejects 7" = do + assert (not (Int.isSqrt 17 5)) + test "oracle sqrt 8" = do + assert (Int.isqrt (Int.fromBytes True #"0100000000000000020000000000000000") == 18446744073709551616) + test "oracle isSqrt 8" = do + assert (Int.isSqrt (Int.fromBytes True #"0100000000000000020000000000000000") 18446744073709551616) + test "oracle isSqrt rejects 8" = do + assert (not (Int.isSqrt (Int.fromBytes True #"0100000000000000020000000000000000") 18446744073709551617)) + test "oracle sqrt 9" = do + assert (Int.isqrt (Int.fromBytes True #"0100000000000000020000000000000001") == 18446744073709551617) + test "oracle isSqrt 9" = do + assert (Int.isSqrt (Int.fromBytes True #"0100000000000000020000000000000001") 18446744073709551617) + test "oracle isSqrt rejects 9" = do + assert (not (Int.isSqrt (Int.fromBytes True #"0100000000000000020000000000000001") 18446744073709551618)) + test "oracle sqrt 10" = do + assert (Int.isqrt (Int.fromBytes True #"0100000000000000020000000000000002") == 18446744073709551617) + test "oracle isSqrt 10" = do + assert (Int.isSqrt (Int.fromBytes True #"0100000000000000020000000000000002") 18446744073709551617) + test "oracle isSqrt rejects 10" = do + assert (not (Int.isSqrt (Int.fromBytes True #"0100000000000000020000000000000002") 18446744073709551618)) + test "oracle sqrt 11" = do + assert (Int.isqrt (Int.fromBytes True #"0100000000000000040000000000000003") == 18446744073709551617) + test "oracle isSqrt 11" = do + assert (Int.isSqrt (Int.fromBytes True #"0100000000000000040000000000000003") 18446744073709551617) + test "oracle isSqrt rejects 11" = do + assert (not (Int.isSqrt (Int.fromBytes True #"0100000000000000040000000000000003") 18446744073709551618)) + test "oracle sqrt 12" = do + assert (Int.isqrt (Int.fromBytes True #"01000000000000000000000000000000f600000000000000000000000000003b18") == (Int.fromBytes True #"010000000000000000000000000000007a")) + test "oracle isSqrt 12" = do + assert (Int.isSqrt (Int.fromBytes True #"01000000000000000000000000000000f600000000000000000000000000003b18") (Int.fromBytes True #"010000000000000000000000000000007a")) + test "oracle isSqrt rejects 12" = do + assert (not (Int.isSqrt (Int.fromBytes True #"01000000000000000000000000000000f600000000000000000000000000003b18") (Int.fromBytes True #"010000000000000000000000000000007b"))) + test "oracle sqrt 13" = do + assert (Int.isqrt (Int.fromBytes True #"01000000000000000000000000000000f600000000000000000000000000003b19") == (Int.fromBytes True #"010000000000000000000000000000007b")) + test "oracle isSqrt 13" = do + assert (Int.isSqrt (Int.fromBytes True #"01000000000000000000000000000000f600000000000000000000000000003b19") (Int.fromBytes True #"010000000000000000000000000000007b")) + test "oracle isSqrt rejects 13" = do + assert (not (Int.isSqrt (Int.fromBytes True #"01000000000000000000000000000000f600000000000000000000000000003b19") (Int.fromBytes True #"010000000000000000000000000000007c"))) + test "oracle sqrt 14" = do + assert (Int.isqrt (Int.fromBytes True #"01000000000000000000000000000000f600000000000000000000000000003b1a") == (Int.fromBytes True #"010000000000000000000000000000007b")) + test "oracle isSqrt 14" = do + assert (Int.isSqrt (Int.fromBytes True #"01000000000000000000000000000000f600000000000000000000000000003b1a") (Int.fromBytes True #"010000000000000000000000000000007b")) + test "oracle isSqrt rejects 14" = do + assert (not (Int.isSqrt (Int.fromBytes True #"01000000000000000000000000000000f600000000000000000000000000003b1a") (Int.fromBytes True #"010000000000000000000000000000007c"))) + test "oracle sqrt 15" = do + assert (Int.isqrt (Int.fromBytes True #"01000000000000000000000000000000f800000000000000000000000000003c0f") == (Int.fromBytes True #"010000000000000000000000000000007b")) + test "oracle isSqrt 15" = do + assert (Int.isSqrt (Int.fromBytes True #"01000000000000000000000000000000f800000000000000000000000000003c0f") (Int.fromBytes True #"010000000000000000000000000000007b")) + test "oracle isSqrt rejects 15" = do + assert (not (Int.isSqrt (Int.fromBytes True #"01000000000000000000000000000000f800000000000000000000000000003c0f") (Int.fromBytes True #"010000000000000000000000000000007c"))) + test "gcd negative expected" = do + assert (not (Int.isGcd 12 18 -6)) + test "lcm negative expected" = do + assert (not (Int.isLcm 12 18 -36)) + test "sqrt negative expected" = do + assert (not (Int.isSqrt 4 -2)) + test "sqrt negative input" = do + assert (not (Int.isSqrt -4 2)) + test "reject non-greatest common divisor" = do + assert (not (Int.isGcd 12 18 3)) + test "reject non-least common multiple" = do + assert (not (Int.isLcm 12 18 72)) + test "predicate mixed Big inputs" = do + assert (Int.isGcd big 18 bigGcd && Int.isLcm big 18 bigLcm && Int.isSqrt big bigGcd) + test "gcd predicate exhaustive small inputs" = do + assert (List.all (\a -> List.all (\b -> List.all (\g -> Int.isGcd a b g == (Int.gcd a b == g)) (List.range -1 6)) (List.range -3 4)) (List.range -3 4)) + test "lcm predicate exhaustive small inputs" = do + assert (List.all (\a -> List.all (\b -> List.all (\m -> Int.isLcm a b m == (Int.lcm a b == m)) (List.range -1 14)) (List.range -3 4)) (List.range -3 4)) + test "sqrt predicate exhaustive small inputs" = do + assert (List.all (\n -> List.all (\r -> Int.isSqrt n r == (Int.isqrt n == r)) (List.range -1 12)) (List.range 0 101)) diff --git a/crates/nash-driver/tests/fixtures/builtin-compilation/Certificate.lean b/crates/nash-driver/tests/fixtures/builtin-compilation/Certificate.lean deleted file mode 100644 index d154bab0..00000000 --- a/crates/nash-driver/tests/fixtures/builtin-compilation/Certificate.lean +++ /dev/null @@ -1,70 +0,0 @@ -import PlutusCore.UPLC.ScriptEncoding -import PlutusCore.UPLC.Builtins -import PlutusCore.UPLC.CekMachine - -/- Kernel certificates for captured O0 output, not a formalization of the Rust compiler. - The reference is independent of Nash's builtin metadata. No SMT or admitted theorem - is used. The generic syntax includes tags missing from PlutusCoreBlaster's model. -/ -namespace NashBuiltinCompilation - -open PlutusCore.UPLC.Term -open PlutusCore.UPLC.ScriptEncoding -open PlutusCore.UPLC.Builtins -open PlutusCore.UPLC.CekMachine - -inductive Syntax where - | var : Nat → Syntax - | lam : Syntax → Syntax - | apply : Syntax → Syntax → Syntax - | force : Syntax → Syntax - | builtin : Nat → Syntax - -def forced (tag : Nat) : Nat → Syntax - | 0 => .builtin tag - | n + 1 => .force (forced tag n) - -def lambdas : Nat → Syntax → Syntax - | 0, term => term - | n + 1, term => .lam (lambdas n term) - -def reference (tag forces supplied : Nat) (bindResult : Bool) : Syntax := - let call := (List.range supplied).foldl - (fun term i => .apply term (.var (supplied - i - 1))) (forced tag forces) - let call := if bindResult then .apply (.lam (.var 0)) call else call - .apply (.lam (.var 0)) (lambdas supplied call) - -def callingConvention : ExpectedBuiltinArgs → Nat × Nat - | .One .ArgQ => (1, 0) - | .One .ArgV => (0, 1) - | .More arg rest => - let (forces, values) := callingConvention rest - match arg with - | .ArgQ => (forces + 1, values) - | .ArgV => (forces, values + 1) - -def forcedTerm (builtin : BuiltinFun) : Nat → Term - | 0 => .Builtin builtin - | n + 1 => .Force (forcedTerm builtin n) - --- Match the Flat decoder's display-only binder names as well as its relative indices. -def lambdaTerms : Nat → Nat → Term → Term - | 0, _, term => term - | n + 1, depth, term => .Lam s!"dbi_{depth}" (lambdaTerms n (depth + 1) term) - -def referenceTerm (builtin : BuiltinFun) (forces supplied : Nat) (bindResult : Bool) : Term := - let call := (List.range supplied).foldl - (fun term i => .Apply term (.Var (supplied - i - 1))) (forcedTerm builtin forces) - let call := if bindResult then .Apply (.Lam s!"dbi_{supplied}" (.Var 0)) call else call - .Apply (.Lam "dbi_0" (.Var 0)) (lambdaTerms supplied 0 call) - -def body : Program → Term - | .Program _ term => term - --- Preserve unfinished states at exhaustion, as Nash's proof backend does. -def runKeepingFuel (variant : PlutusCore.Default.BuiltinSemanticsVariant) - (state : State) (fuel : Nat) : State := - match fuel, state with - | _, .Halt _ => state - | _, .Error => state - | 0, _ => state - | n + 1, _ => runKeepingFuel variant (step variant state) n diff --git a/crates/nash-driver/tests/fixtures/builtin-compilation/spec.tsv b/crates/nash-driver/tests/fixtures/builtin-compilation/spec.tsv deleted file mode 100644 index a8a05f9d..00000000 --- a/crates/nash-driver/tests/fixtures/builtin-compilation/spec.tsv +++ /dev/null @@ -1,109 +0,0 @@ -# Frozen UPLC compilation contract: Nash name, Flat tag, forces, value arity, Lean constructor. -# Tags and modeled calling conventions: PlutusCoreBlaster 41fe7ead, -# PlutusCore/UPLC/FlatEncoding/Basic.lean and PlutusCore/UPLC/Builtins.lean. -# Array/value signatures and tags: Plutus Core specification, Tables 4.18, 4.20, C.8. -# https://plutus.cardano.intersectmbo.org/resources/plutus-core-spec.pdf -# Array/value entries are absent from the pinned Lean model. -# Final column: strict result binding for saturated decoder/projection calls in O0 assembly. -# Keep this independent of the compiler inventory and runtime metadata at test time. -addInteger 0 0 2 AddInteger 0 -subtractInteger 1 0 2 SubtractInteger 0 -multiplyInteger 2 0 2 MultiplyInteger 0 -divideInteger 3 0 2 DivideInteger 0 -quotientInteger 4 0 2 QuotientInteger 0 -remainderInteger 5 0 2 RemainderInteger 0 -modInteger 6 0 2 ModInteger 0 -equalsInteger 7 0 2 EqualsInteger 0 -lessThanInteger 8 0 2 LessThanInteger 0 -lessThanEqualsInteger 9 0 2 LessThanEqualsInteger 0 -appendByteString 10 0 2 AppendByteString 0 -consByteString 11 0 2 ConsByteString 0 -sliceByteString 12 0 3 SliceByteString 0 -lengthOfByteString 13 0 1 LengthOfByteString 0 -indexByteString 14 0 2 IndexByteString 0 -equalsByteString 15 0 2 EqualsByteString 0 -lessThanByteString 16 0 2 LessThanByteString 0 -lessThanEqualsByteString 17 0 2 LessThanEqualsByteString 0 -sha2_256 18 0 1 Sha2_256 0 -sha3_256 19 0 1 Sha3_256 0 -blake2b_256 20 0 1 Blake2b_256 0 -verifyEd25519Signature 21 0 3 VerifyEd25519Signature 0 -appendString 22 0 2 AppendString 0 -equalsString 23 0 2 EqualsString 0 -encodeUtf8 24 0 1 EncodeUtf8 0 -decodeUtf8 25 0 1 DecodeUtf8 0 -ifThenElse 26 1 3 IfThenElse 0 -chooseUnit 27 1 2 ChooseUnit 0 -trace 28 1 2 Trace 0 -fstPair 29 2 1 FstPair 1 -sndPair 30 2 1 SndPair 1 -chooseList 31 2 3 ChooseList 0 -mkCons 32 1 2 MkCons 0 -headList 33 1 1 HeadList 1 -tailList 34 1 1 TailList 1 -nullList 35 1 1 NullList 0 -chooseData 36 1 6 ChooseData 0 -constrData 37 0 2 ConstrData 0 -mapData 38 0 1 MapData 0 -listData 39 0 1 ListData 0 -iData 40 0 1 IData 0 -bData 41 0 1 BData 0 -unConstrData 42 0 1 UnConstrData 1 -unMapData 43 0 1 UnMapData 0 -unListData 44 0 1 UnListData 1 -unIData 45 0 1 UnIData 0 -unBData 46 0 1 UnBData 0 -equalsData 47 0 2 EqualsData 0 -mkPairData 48 0 2 MkPairData 0 -mkNilData 49 0 1 MkNilData 0 -mkNilPairData 50 0 1 MkNilPairData 0 -serialiseData 51 0 1 SerializeData 0 -verifyEcdsaSecp256k1Signature 52 0 3 VerifyEcdsaSecp256k1Signature 0 -verifySchnorrSecp256k1Signature 53 0 3 VerifySchnorrSecp256k1Signature 0 -bls12_381_g1_add 54 0 2 Bls12_381_G1_add 0 -bls12_381_g1_neg 55 0 1 Bls12_381_G1_neg 0 -bls12_381_g1_scalarMul 56 0 2 Bls12_381_G1_scalarMul 0 -bls12_381_g1_equal 57 0 2 Bls12_381_G1_equal 0 -bls12_381_g1_compress 58 0 1 Bls12_381_G1_compress 0 -bls12_381_g1_uncompress 59 0 1 Bls12_381_G1_uncompress 0 -bls12_381_g1_hashToGroup 60 0 2 Bls12_381_G1_hashToGroup 0 -bls12_381_g2_add 61 0 2 Bls12_381_G2_add 0 -bls12_381_g2_neg 62 0 1 Bls12_381_G2_neg 0 -bls12_381_g2_scalarMul 63 0 2 Bls12_381_G2_scalarMul 0 -bls12_381_g2_equal 64 0 2 Bls12_381_G2_equal 0 -bls12_381_g2_compress 65 0 1 Bls12_381_G2_compress 0 -bls12_381_g2_uncompress 66 0 1 Bls12_381_G2_uncompress 0 -bls12_381_g2_hashToGroup 67 0 2 Bls12_381_G2_hashToGroup 0 -bls12_381_millerLoop 68 0 2 Bls12_381_millerLoop 0 -bls12_381_mulMlResult 69 0 2 Bls12_381_mulMlResult 0 -bls12_381_finalVerify 70 0 2 Bls12_381_finalVerify 0 -keccak_256 71 0 1 Keccak_256 0 -blake2b_224 72 0 1 Blake2b_224 0 -integerToByteString 73 0 3 IntegerToByteString 0 -byteStringToInteger 74 0 2 ByteStringToInteger 0 -andByteString 75 0 3 AndByteString 0 -orByteString 76 0 3 OrByteString 0 -xorByteString 77 0 3 XorByteString 0 -complementByteString 78 0 1 ComplementByteString 0 -readBit 79 0 2 ReadBit 0 -writeBits 80 0 3 WriteBits 0 -replicateByte 81 0 2 ReplicateByte 0 -shiftByteString 82 0 2 ShiftByteString 0 -rotateByteString 83 0 2 RotateByteString 0 -countSetBits 84 0 1 CountSetBits 0 -findFirstSetBit 85 0 1 FindFirstSetBit 0 -ripemd_160 86 0 1 Ripemd_160 0 -expModInteger 87 0 3 ExpModInteger 0 -dropList 88 1 2 DropList 0 -lengthOfArray 89 1 1 - 0 -listToArray 90 1 1 - 0 -indexArray 91 1 2 - 0 -bls12_381_g1_multiScalarMul 92 0 2 Bls12_381_G1_multiScalarMul 0 -bls12_381_g2_multiScalarMul 93 0 2 Bls12_381_G2_multiScalarMul 0 -insertCoin 94 0 4 - 0 -lookupCoin 95 0 3 - 0 -unionValue 96 0 2 - 0 -valueContains 97 0 2 - 0 -valueData 98 0 1 - 0 -unValueData 99 0 1 - 0 -scaleValue 100 0 2 - 0 diff --git a/crates/nash-driver/tests/proofs.rs b/crates/nash-driver/tests/proofs.rs index aa92f41f..9836dfce 100644 --- a/crates/nash-driver/tests/proofs.rs +++ b/crates/nash-driver/tests/proofs.rs @@ -310,152 +310,6 @@ proof } } -const EXISTING_FIXTURES: &[(&str, &str, usize)] = &[ - ( - "DataConversions", - include_str!("fixtures/base-traits/DataConversions.nash"), - 7, - ), - ( - "Equality", - include_str!("fixtures/base-traits/Equality.nash"), - 4, - ), - ( - "IntegerMath", - include_str!("fixtures/base-traits/IntegerMath.nash"), - 4, - ), -]; - -#[tokio::test] -async fn existing_test_fixtures_compile_and_export_proofs() { - let directory = std::env::temp_dir().join(format!( - "nash-fixture-export-{}-{}", - std::process::id(), - std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .unwrap() - .as_nanos() - )); - for (name, source, count) in EXISTING_FIXTURES { - let programs = compile(source) - .await - .unwrap_or_else(|error| panic!("{name}: {error}")); - assert_eq!(programs.len(), *count, "{name}"); - let files = nash_proof::export(&programs, &directory, 500, 1000, 30).unwrap(); - assert_eq!(files.len(), *count, "{name}"); - assert!(files.iter().all(|path| path.is_file())); - assert!(files[0].parent().unwrap().join("proofs.json").is_file()); - if *name == "IntegerMath" { - assert_eq!( - programs - .iter() - .filter(|p| p.postcondition.is_some()) - .count(), - 3 - ); - } - } -} - -/// Runs the actual proof blocks in the existing fixtures, rather than copies of them. -/// Defaults to the verified conversion/Boolean suite. NASH_PROOF_MATCH can select -/// the expensive arithmetic/ordering candidates; these must still fail closed. -#[tokio::test] -#[ignore = "requires the pinned Lean project and Z3"] -async fn live_existing_test_proofs() { - let matching = std::env::var("NASH_PROOF_MATCH").unwrap_or_default(); - let fuel = std::env::var("NASH_PROOF_FUEL").map_or(500, |s| s.parse().unwrap()); - let directory = - std::env::temp_dir().join(format!("nash-existing-proofs-{}", std::process::id())); - let config = nash_proof::Config { - lean_project: std::env::var_os("NASH_PROOF_LEAN_PROJECT") - .expect("Lean project") - .into(), - fuel, - postcondition_fuel: 1000, - solver_timeout: 30, - wall_timeout: std::time::Duration::from_secs(60), - }; - let mut checked = 0; - let mut failures = Vec::new(); - for (fixture, source, _) in EXISTING_FIXTURES { - let programs = compile(source).await.unwrap(); - let programs: Vec<_> = programs - .into_iter() - .filter(|p| { - if matching.is_empty() { - *fixture == "DataConversions" || p.name == "boolean equality agrees with logic" - } else { - format!("{fixture}/{}", p.name).contains(&matching) - } - }) - .collect(); - if programs.is_empty() { - continue; - } - let files = nash_proof::export(&programs, &directory, fuel, 1000, 30).unwrap(); - for (program, source) in programs.iter().zip(files) { - let outcome = nash_proof::run(program, &source, &config).unwrap(); - eprintln!("{fixture}/{}: {:?}", program.name, outcome.status); - if !outcome.passed() { - failures.push(format!( - "{fixture}/{}: {:?}: {}", - program.name, outcome.status, outcome.diagnostics - )); - } - checked += 1; - } - } - assert!(checked > 0, "no existing proofs matched"); - assert!(failures.is_empty(), "{}", failures.join("\n")); -} - -#[tokio::test] -async fn vesting_authorization_proofs_compile_and_export() { - use nash_driver::{FileSystemSource, Project, build_graph_with_tests}; - let root = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../../examples/vesting"); - let project = Project::load(root).await.unwrap(); - let db = Arc::new(Mutex::new(Database::new(FileSystemSource::new()))); - let modules = project.discover_modules(&*db.lock().await).await.unwrap(); - let roots = project - .discover_own_modules(&*db.lock().await) - .await - .unwrap(); - let graph = build_graph_with_tests( - db.clone(), - &modules.keys().cloned().collect::>(), - &roots.keys().cloned().collect::>(), - ) - .await - .unwrap(); - let (report, programs) = test_with(db, &graph, &modules, move |solved| { - nash_driver::build::compile_proofs_matching_with( - solved, - |uri| roots.contains_key(uri).then(nash_config::Build::default), - |_, _| true, - ) - }) - .await; - assert!(report.is_success(), "{report:#?}"); - let programs = programs.unwrap().unwrap(); - assert_eq!(programs.len(), 2); - assert!( - programs - .iter() - .all(|p| p.postcondition.as_ref().unwrap().result == nash_proof::ReturnDomain::Unit) - ); - assert_eq!(programs[0].domains, vec![nash_proof::Domain::Spending(3)]); - assert_eq!( - programs[1].domains, - vec![nash_proof::Domain::Int, nash_proof::Domain::Spending(3)] - ); - let output = std::env::temp_dir().join(format!("nash-vesting-proofs-{}", std::process::id())); - let files = nash_proof::export(&programs, &output, 1000, 1500, 30).unwrap(); - assert_eq!(files.len(), 2); -} - #[tokio::test] async fn proof_aliases_and_independent_name_scopes_survive_canonicalization() { let programs = compile( diff --git a/crates/nash-driver/tests/vesting.rs b/crates/nash-driver/tests/vesting.rs index c0c01952..60755278 100644 --- a/crates/nash-driver/tests/vesting.rs +++ b/crates/nash-driver/tests/vesting.rs @@ -76,48 +76,11 @@ async fn bundled_base_vesting_artifacts_execute_all_ledger_cases() { output.module ); let parameterized = output.module == "VestingParam"; - let applied = if parameterized { - program.apply(&arena, Term::integer_from(&arena, 5)) - } else { - program - }; - for malformed in [ - PlutusData::integer_from(&arena, 0), - PlutusData::constr(&arena, 0, &[]), - PlutusData::constr(&arena, 1, &[]), - ] { - assert!( - applied - .apply(&arena, Term::data(&arena, malformed)) - .eval(&arena) - .term - .is_err(), - "{}: malformed context accepted", - output.module - ); - } for (name, deadline, redeemer, signer, expected) in [ ("claim after deadline", 10, 0, &b""[..], true), ("claim before deadline", 30, 0, &b""[..], false), - ( - "claim exactly at deadline", - if parameterized { 15 } else { 20 }, - 0, - &b""[..], - false, - ), - ( - "claim just after deadline", - if parameterized { 14 } else { 19 }, - 0, - &b""[..], - true, - ), - ("claim with negative deadline", -100, 0, &b""[..], true), ("cancel signed by owner", 10, 1, &[0xaa][..], true), ("cancel unsigned", 10, 1, &b""[..], false), - ("cancel signed by another key", 10, 1, &[0xcc][..], false), - ("cancel ignores deadline", 100, 1, &[0xaa][..], true), ] { let datum = PlutusData::constr( &arena, diff --git a/crates/nash-plutus/src/machine/runtime.rs b/crates/nash-plutus/src/machine/runtime.rs index 82ac4a91..551e680f 100644 --- a/crates/nash-plutus/src/machine/runtime.rs +++ b/crates/nash-plutus/src/machine/runtime.rs @@ -626,10 +626,10 @@ impl<'a, B: BuiltinCostModel, V: Eval<'a>> Machine<'a, B, V> { self.spend_budget(budget)?; - if let Ok(index) = usize::try_from(arg2) - && let Some(byte) = arg1.get(index) - { - let result: Integer = (*byte).into(); + let index: i128 = arg2.try_into().unwrap(); + + if 0 <= index && (index as usize) < arg1.len() { + let result: Integer = arg1[index as usize].into(); let new = self.arena.alloc_integer(result); let value = Value::integer(self.arena, new); @@ -2756,7 +2756,10 @@ impl<'a, B: BuiltinCostModel, V: Eval<'a>> Machine<'a, B, V> { let bytes = runtime.args[0].unwrap_byte_string()?; let shift = runtime.args[1].unwrap_integer()?; - let arg1 = i64::try_from(shift.abs()).unwrap_or(i64::MAX); + let arg1: i64 = u64::try_from(shift.abs()) + .unwrap() + .try_into() + .unwrap_or(i64::MAX); let budget = self .costs @@ -2784,7 +2787,7 @@ impl<'a, B: BuiltinCostModel, V: Eval<'a>> Machine<'a, B, V> { if is_shift_left { if bit_shift == 0 { // If we can shift entire bytes, that's much simpler - let copy_len = length - byte_shift; + let copy_len = length - bit_shift; // For example, consider the following byte array [1,0,1,0,1] being shifted 8 bits (1 byte) // Result: [0,1,0,1,0] result[..copy_len].copy_from_slice(&bytes[byte_shift..]); @@ -2846,7 +2849,10 @@ impl<'a, B: BuiltinCostModel, V: Eval<'a>> Machine<'a, B, V> { let bytes = runtime.args[0].unwrap_byte_string()?; let shift = runtime.args[1].unwrap_integer()?; - let arg1 = i64::try_from(shift.abs()).unwrap_or(i64::MAX); + let arg1: i64 = u64::try_from(shift.abs()) + .unwrap() + .try_into() + .unwrap_or(i64::MAX); let budget = self .costs @@ -3125,9 +3131,10 @@ impl<'a, B: BuiltinCostModel, V: Eval<'a>> Machine<'a, B, V> { .ok_or(MachineError::NoCostForBuiltin(DefaultFunction::IndexArray))?; self.spend_budget(budget)?; - if let Ok(index) = usize::try_from(arg1) - && let Some(element) = array.get(index) - { + let index: i128 = arg1.try_into().unwrap(); + + if 0 <= index && (index as usize) < array.len() { + let element = array[index as usize]; let value = Value::con(self.arena, element); Ok(value) } else { diff --git a/docs/builtin-compilation.md b/docs/builtin-compilation.md deleted file mode 100644 index a20f8903..00000000 --- a/docs/builtin-compilation.md +++ /dev/null @@ -1,99 +0,0 @@ -# Builtin compilation certificates - -The readable [Nash semantic proof files](../proofs/README.md) specify each -builtin's algebra, representations, boundaries and failure modes. The certificates -below complement those properties by checking the independent calling convention. - -The builtin regression validates all **101 entries** in Nash's synthetic -`Builtin` module against a frozen UPLC compilation contract. It generates -**283 source declarations**: a first-class reference and every application -prefix up to full saturation for each builtin. Each declaration passes through -ordinary parsing, canonicalization, inference, specialization, O0 assembly, -and Flat encoding/decoding. - -The expected opcode and calling convention are independent of Nash's compiler -inventory and runtime metadata. The contract is recorded in -`crates/nash-driver/tests/fixtures/builtin-compilation/spec.tsv`. Its tags and -signatures come from the [Plutus Core specification](https://plutus.cardano.intersectmbo.org/resources/plutus-core-spec.pdf) -(Tables 4.18, 4.20 and C.8); modeled calling conventions also agree with the pinned -PlutusCoreBlaster `expectedArgs` definition. Inventory changes fail the regression -until the independent contract is updated. - -For every declaration, the emitted term must contain the correct builtin, -required forces, supplied arguments in their original order, and the expected -lambda and strict administrative bindings. Reference terms are constructed -directly; the oracle is never compiled through Nash. The regression checks the -opcode, forces and arity separately and rejects swapped opcodes, extra forces -and reversed arguments. The six saturated decoder/projection calls retain a -strict result binding, and all roots retain their O0 declaration binding. -These bindings remain in both terms, so their fuel overhead is included. - -## What Lean checks - -The opt-in suite preserves a generated `BuiltinCompilation.lean` and diagnostic -log in the system temporary directory. The reusable definitions are in -`crates/nash-driver/tests/fixtures/builtin-compilation/Certificate.lean`. - -| Coverage | Certificate | -|---|---| -| All 101 builtins, 283 declarations | Captured syntax equals the independent reference; applying arbitrary argument syntax gives equal results under any observation function | -| 91 builtins supported by pinned PlutusCoreBlaster, 254 declarations | Import the actual Flat bytes with PlutusCoreBlaster's independent decoder and prove the resulting term equals the reference UPLC term | -| Those same 254 declarations | Equal CEK states for every argument term list, semantics variant and fuel limit, preserving unfinished states at exhaustion | -| 91 modeled calling conventions | Force count and value arity agree with PlutusCoreBlaster's `expectedArgs` | - -These are Lean kernel-checked equality proofs, using `rfl` and equality rewriting; -no SMT solver, `native_decide`, or admitted Nash theorem is used. The assumption -audit requires all 283 generic observation certificates to be axiom-free. -The CEK corollaries depend only on Lean's standard `propext`, `Classical.choice` -and `Quot.sound` axioms through the imported evaluator; the regression rejects -other dependencies, including `sorryAx` and reduction-oracle axioms. - -The statements quantify over arbitrary UPLC argument terms, including errors, -function values and malformed inputs. They do not use a successful-return guard, -so rejection and exhaustion cannot make a wrong compilation pass vacuously. -They compare the complete CEK state at the same fuel, rather than only successful -return values. Crypto builtins are covered as calls to the same primitive; -this does not establish correctness of cryptographic implementations. - -## Limits - -This is **translation validation of the captured builtin declarations**, not a -formal verification of the Rust compiler for all Nash programs. The source -fixtures instantiate representation-polymorphic parameters as Data. Certificates -do not establish correctness of all type schemes, representation specializations, -compiler intrinsics, or optimizer passes. Those require separate contracts and -simulation theorems. Source `Primitive.coerce` is an intrinsic outside the 101-entry -runtime inventory and is not included. - -The pinned Lean model lacks `lengthOfArray`, `listToArray`, `indexArray`, -`insertCoin`, `lookupCoin`, `unionValue`, `valueContains`, `valueData`, `unValueData` -and `scaleValue`. Their compilation syntax is certified, but no PlutusCoreBlaster -CEK theorem is claimed for them. Completing that connection requires array/value -types, Flat decoding, calling conventions, evaluation and corresponding cost -models in PlutusCoreBlaster. CardanoLedgerApiBlaster is not needed for the current -compilation certificates because no ledger-context assumptions are involved. - -The link from the actual bytes to Lean terms uses the library's compile-time -Flat decoder. That importer, the Rust syntax capture and the compiler executable -are part of the validation setup; the decoder itself has not been formally -verified. Mathematical fidelity of the pinned builtin models and Cardano's -implementation remains a separate obligation. - -## Run - -The ordinary compilation regression needs only the Rust toolchain: - -```sh -cargo test -p nash-driver --test builtin_compilation -``` - -With the pinned Lean project already built: - -```sh -NASH_PROOF_LEAN_PROJECT=/path/to/built/CardanoLedgerApiBlaster \ - cargo test -p nash-driver --test builtin_compilation -- --ignored --nocapture -``` - -This follows the same opt-in toolchain arrangement as the live Nash proof -regressions. It does not require Z3 because the certificates use structural -equality rather than SMT verification. diff --git a/docs/proofs.md b/docs/proofs.md index abc19562..ac175f30 100644 --- a/docs/proofs.md +++ b/docs/proofs.md @@ -177,67 +177,6 @@ cross this boundary yet. `fail` and `fail once` modifiers are rejected for statements does not select partial correctness: only the direct body call is recognized. Outside that position it is an ordinary strict assertion helper. -## Builtin semantic specifications - -The [builtin proof project](../proofs/README.md) contains one Nash file per -builtin and 324 readable obligations: algebraic laws, unbounded integer behavior, -conversion roundtrips, boundary cases and invalid-input rejection. Its explicit -verification snapshot records 64 SMT-verified obligations and all remaining -limitations. The standard regression executes the actual source obligations, -requires nonvacuous partial-correctness samples, and rejects deliberate opcode, -argument-order, wrap and saturation mutations. This suite also exposed and guards -the local evaluator's index and shift fixes. - -## Compiler validation - -A separate [builtin compilation suite](builtin-compilation.md) validates all -101 builtin entries across 283 reference/application forms. Its Lean kernel -certificates establish syntax equality and, for 91 modeled builtins, equality -of CEK states for arbitrary arguments and fuel. This uses an independent UPLC -reference rather than a Nash postcondition compiled through the same backend. - -## Coverage of existing tests - -The existing Base fixtures now include 15 proof declarations alongside their -runtime checks. They use the actual bundled functions and compile through the -same backend as validators. The three fixtures have also been formatted with -`nash format`. - -| Fixture | Proof coverage | Current verification | -|---|---|---| -| `DataConversions.nash` | arbitrary integer, Boolean and byte roundtrips; optional integer decoding; malformed scalar encodings | all seven verified with 500 execution steps | -| `Equality.nash` | Boolean equality, integer comparison, antisymmetry, min/max partition | Boolean equality verified; three integer obligations currently time out | -| `IntegerMath.nash` | adjacent-square root specification; greatest-common-divisor and least-common-multiple divisibility specifications; negative square-root rejection | compile/export checked; symbolic execution currently times out | - -The gcd and lcm specifications quantify over an additional candidate divisor or -multiple. They use arithmetic divisibility, not another call to the algorithm -under verification. Existing oracle examples, finite-grid tests, UTF-8 cases, -collection tests and cross-language Cardano goldens remain active while the -more expensive obligations need backend improvements. - -The real `Vesting` and `VestingParam` examples also have partial-correctness -proofs over `Proof.spendingV3`: accepting a ledger-valid context must imply the -claim deadline or owner-signature policy. They use the existing unchecked -datum/redeemer casts and do not establish full application encoding validation. Both export successfully; verification -currently times out. Their serialized-artifact tests now cover exact deadlines, -just-after deadlines, negative deadlines, different signers, cancellation after -the deadline, and malformed outer contexts. Artifact encoding, traces, budgets -and optimized/unoptimized comparisons remain runtime checks. - -The default optional regression suite runs the eight verified conversion/Boolean -obligations directly from their existing fixture files: - -```sh -NASH_PROOF_LEAN_PROJECT=/path/to/prebuilt/lean-project \ - cargo test -p nash-driver --test proofs live_existing_test_proofs -- --ignored -``` - -Use `NASH_PROOF_MATCH=Equality` or `NASH_PROOF_MATCH=IntegerMath` to attempt the -expensive candidates; `NASH_PROOF_MATCH=/` selects every fixture obligation. -`NASH_PROOF_FUEL` overrides the default 500 execution steps. Unverified results -fail the test. This optional suite requires Lean and Z3; regular workspace tests -always type-check and compile/export all 15 declarations. - ## Command and generated artifacts ```sh diff --git a/examples/vesting/README.md b/examples/vesting/README.md index 8871c57f..aa362609 100644 --- a/examples/vesting/README.md +++ b/examples/vesting/README.md @@ -26,18 +26,3 @@ The codegen snapshots and serialized artifact tests apply exactly one context and exercise successful and failed claims and cancellations. The snapshots compare unoptimized and optimized Core/UPLC and execution outcomes. Budget regressions run separately in `tools/optimizer-perf`. - -Both validators include a `proof` block for partial correctness: every successful -return within the execution limit must satisfy the deadline or owner-signature -policy. `Proof.spendingV3` quantifies over CardanoLedgerApiBlaster's ledger-valid -spending contexts. These are candidate obligations: export and type checking -pass, but the current symbolic evaluator times out during verification. - -```sh -nash proof examples/vesting --emit-only -nash proof examples/vesting --fuel 1000 --postcondition-fuel 1500 -``` - -The runtime suite retains artifact and optimizer checks and adds exact-deadline, -just-after-deadline, negative-deadline, wrong-signer and malformed-context cases. -See [proof semantics and current coverage](../../docs/proofs.md). diff --git a/examples/vesting/app/src/Vesting.nash b/examples/vesting/app/src/Vesting.nash index 934f2f4e..306797b7 100644 --- a/examples/vesting/app/src/Vesting.nash +++ b/examples/vesting/app/src/Vesting.nash @@ -1,85 +1,24 @@ -validator module Vesting exposing ( main ) - +validator module Vesting exposing (main) import Builtin exposing (..) -import Eq exposing ( Eq ) -import Lift exposing ( Lift ) -import VestingTx exposing ( validAfter, signedBy ) - - -type Datum - = Datum { owner : Bytes, deadline : Int } - +import Eq exposing (Eq) +import Lift exposing (Lift) +import VestingTx exposing (validAfter, signedBy) -type Redeemer - = Claim - | Cancel +type Datum = Datum { owner : Bytes, deadline : Int } +type Redeemer = Claim | Cancel -- The ledger supplies one V3 ScriptContext; spending datum is in ScriptInfo. main : Data -> unit main ctx = case ctx of - Constr - pair(0, [ tx - , action - , Constr pair(1, [ _, Constr pair(0, [ datum ]) ]) - ]) -> + Constr pair(0, [ tx, action, Constr pair(1, [ _, Constr pair(0, [ datum ]) ]) ]) -> check (Primitive.coerce datum) (Primitive.coerce action) tx - - _ -> - fail - + _ -> fail check : Datum -> Redeemer -> Data -> unit check datum redeemer tx = case redeemer of - Claim -> - assert - (Builtin.lessThanInteger (lower datum.deadline) (validAfter tx)) - - Cancel -> - assert (signedBy tx datum.owner) - - -proof - import Proof - - -- Verify authorization with the same unchecked application casts as main. - -- This does not claim that the datum/redeemer encoding is fully validated. - prop "accepted spending contexts satisfy authorization" = - let - ctx via Proof.spendingV3 - in - do - Proof.returns - (main ctx) - ( - \_ -> - case ctx of - Constr - pair(0, [ tx - , rawAction - , Constr - pair(1, [ _ - , Constr pair(0, [ rawDatum ]) - ]) - ]) -> - let - datum : Datum - datum = Primitive.coerce rawDatum - - action : Redeemer - action = Primitive.coerce rawAction - in - case action of - Claim -> - lower datum.deadline < - validAfter tx - - Cancel -> - signedBy tx datum.owner - - _ -> - False - ) + Claim -> assert (Builtin.lessThanInteger (lower datum.deadline) (validAfter tx)) + Cancel -> assert (signedBy tx datum.owner) diff --git a/examples/vesting/app/src/VestingParam.nash b/examples/vesting/app/src/VestingParam.nash index a19c7838..bfc82b55 100644 --- a/examples/vesting/app/src/VestingParam.nash +++ b/examples/vesting/app/src/VestingParam.nash @@ -1,95 +1,28 @@ -validator module VestingParam exposing ( main ) - +validator module VestingParam exposing (main) import Builtin exposing (..) -import Eq exposing ( Eq ) -import Lift exposing ( Lift ) -import VestingTx exposing ( validAfter, signedBy ) - - -type Datum - = Datum { owner : Bytes, deadline : Int } - +import Eq exposing (Eq) +import Lift exposing (Lift) +import VestingTx exposing (validAfter, signedBy) -type Redeemer - = Claim - | Cancel +type Datum = Datum { owner : Bytes, deadline : Int } +type Redeemer = Claim | Cancel -- The ledger supplies one V3 ScriptContext; spending datum is in ScriptInfo. main : int -> Data -> unit main minimumLock ctx = case ctx of - Constr - pair(0, [ tx - , action - , Constr pair(1, [ _, Constr pair(0, [ datum ]) ]) - ]) -> - check - minimumLock - (Primitive.coerce datum) - (Primitive.coerce action) - tx - - _ -> - fail - + Constr pair(0, [ tx, action, Constr pair(1, [ _, Constr pair(0, [ datum ]) ]) ]) -> + check minimumLock (Primitive.coerce datum) (Primitive.coerce action) tx + _ -> fail check : int -> Datum -> Redeemer -> Data -> unit check minimumLock datum redeemer tx = case redeemer of Claim -> assert - ( - Builtin.lessThanInteger - (Builtin.addInteger (lower datum.deadline) minimumLock) - (validAfter tx) - ) - - Cancel -> - assert (signedBy tx datum.owner) - - -proof - import Proof - - -- Verify authorization with the same unchecked application casts as main. - -- This does not claim that the datum/redeemer encoding is fully validated. - prop "accepted spending contexts satisfy authorization" = - let - minimumLock via Proof.int - ctx via Proof.spendingV3 - in - do - Proof.returns - (main minimumLock ctx) - ( - \_ -> - case ctx of - Constr - pair(0, [ tx - , rawAction - , Constr - pair(1, [ _ - , Constr pair(0, [ rawDatum ]) - ]) - ]) -> - let - datum : Datum - datum = Primitive.coerce rawDatum - - action : Redeemer - action = Primitive.coerce rawAction - in - case action of - Claim -> - lower datum.deadline + - minimumLock < - validAfter tx - - Cancel -> - signedBy tx datum.owner - - _ -> - False - ) + (Builtin.lessThanInteger + (Builtin.addInteger (lower datum.deadline) minimumLock) + (validAfter tx)) + Cancel -> assert (signedBy tx datum.owner) diff --git a/proofs/README.md b/proofs/README.md deleted file mode 100644 index 872da67b..00000000 --- a/proofs/README.md +++ /dev/null @@ -1,225 +0,0 @@ -# Semantic builtin proofs - -Each builtin has its own Nash file, its compiled source binding, and readable -`proof` blocks describing its behavior. Start with [AddInteger.nash](builtins/AddInteger.nash): -commutativity, associativity, identities, inverses, an unbounded successor law, -Data-represented integer handling, and explicit 64/128-bit boundary witnesses. - -There are **324 obligations in 101 files**. The active project compiles and exports -**318 obligations in 99 files**. The two multi-scalar files have six further -obligations in [pending/builtins](pending/builtins); Nash currently cannot encode -the BLS element type in their native list constants. They have a separate project -so this compiler limitation does not prevent exporting the active suite. - -The checked-in [verification snapshot](verification.json) records every obligation -as verified, unchecked, timed out, blocked, or refuted in an opaque model. -**64 obligations are SMT-verified** at 300 CEK steps with the pinned Lean 4.24 -dependencies. [verified.tsv](verified.tsv) lists that reproducible subset. -Successful SMT verification has the backend's existing `smt_verified` trust level; -it is not a reconstructed Lean kernel proof. - -`Proof.int` and `Proof.integer` quantify over arbitrary mathematical integers, -without a bit-width bound. The successor law would fail for wrapping or saturating -addition. Large closed witnesses are constructed from bytes when they exceed the -parser's integer-literal range. Value quantities have a distinct signed 128-bit -contract and must reject overflow rather than inherit unbounded arithmetic laws. - -Passing execution obligations require success within the execution limit. -`fail` includes script error and exhaustion. `Proof.returns` checks successful -results only; native regressions also require a successful sample for every such -obligation, including valid compressed BLS points. Native examples supplement the -symbolic checks and do not turn unchecked obligations into universal proofs. - -## Run - -```sh -# Compile/export every active obligation without Lean. -nash proof proofs --emit-only --fuel 300 --postcondition-fuel 300 - -# Run all addition laws, including the currently unverified associativity law. -nash proof proofs --lean-project /path/to/built/CardanoLedgerApiBlaster \ - --fuel 300 --postcondition-fuel 300 --match AddInteger --exact - -# Execute the actual source obligations and deliberate compiler mutations. -cargo test -p nash-driver --test builtin_proofs - -# Recheck the recorded verified subset with Lean and Z3. -NASH_PROOF_LEAN_PROJECT=/path/to/built/CardanoLedgerApiBlaster \ - cargo test -p nash-driver --test builtin_proofs live_builtin_semantics -- --ignored --nocapture - -# Attempt any pending active obligation; unsuccessful results fail the test. -NASH_PROOF_MATCH='AddInteger.{associative}' \ -NASH_PROOF_LEAN_PROJECT=/path/to/built/CardanoLedgerApiBlaster \ - cargo test -p nash-driver --test builtin_proofs live_builtin_semantics -- --ignored --nocapture -``` - -The standard regression checks the one-file-per-builtin inventory, compiles and -exports the source proof blocks, executes representative inputs, checks successful -partial-correctness samples, and detects seven deliberate mutations: subtraction -or multiplication substituted for addition, a dropped argument, wrapping, -saturation, reversed byte append, and the wrong pair projection. A live Lean -check also rejected the closed addition contract compiled with subtraction. - -## Failure modes and obligations - -The contracts follow the [Plutus Core specification](https://plutus.cardano.intersectmbo.org/resources/plutus-core-spec.pdf) -and the pinned model. Each linked file contains the actual Nash statements. - -| Possible failure | Corresponding obligations | -|---|---| -| Wrong integer primitive, lost argument, wrap, saturation, signedness | [addition](builtins/AddInteger.nash), [subtraction](builtins/SubtractInteger.nash), [multiplication](builtins/MultiplyInteger.nash), comparison laws and distinguishing results | -| Floor division confused with truncation; zero divisor accepted | [divide](builtins/DivideInteger.nash), [quotient](builtins/QuotientInteger.nash), [mod](builtins/ModInteger.nash), [remainder](builtins/RemainderInteger.nash): reconstruction, signs, rounding, zero-divisor rejection | -| Equality always True, wrong Data variant or comparison ordering | [integer equality](builtins/EqualsInteger.nash), [byte equality](builtins/EqualsByteString.nash), [Data equality](builtins/EqualsData.nash), ordered comparisons | -| Appended inputs reversed; bytes counted as characters | [append](builtins/AppendByteString.nash), [length](builtins/LengthOfByteString.nash): order witnesses and additive lengths | -| Slice offset/count interchanged; negative values mishandled | [slice](builtins/SliceByteString.nash): clamping, empty slices, start/end boundaries | -| Index truncates to a host word or panics above 128 bits | [byte index](builtins/IndexByteString.nash), [array index](builtins/IndexArray.nash), [read bit](builtins/ReadBit.nash), [write bits](builtins/WriteBits.nash): negative, one-past-end, 64-bit and 129-bit indices | -| Byte value out of range; output size exceeds 8192; integer does not fit | [cons](builtins/ConsByteString.nash), [replicate](builtins/ReplicateByte.nash), [integer encoding](builtins/IntegerToByteString.nash): rejection and accepted boundary cases | -| Endianness or padding incorrect; big integers truncated | [integer encoding](builtins/IntegerToByteString.nash), [integer decoding](builtins/ByteStringToInteger.nash): known encodings, roundtrips, arbitrary widths | -| Invalid UTF8 accepted or Unicode/embedded zero corrupted | [encode](builtins/EncodeUtf8.nash), [decode](builtins/DecodeUtf8.nash): roundtrips, non-ASCII witnesses, overlong/truncated/surrogate/out-of-range rejection | -| Pair projection swapped; empty-list access succeeds; branch selection wrong | pair/list/choice files: projections, tails, empty access rejection, all five Data branches, strict conditional arguments | -| Wrong Data decoder accepts another variant or loses payload | Data constructor/decoder files: roundtrips, integer/byte/constructor shape mismatches | -| Whole-byte shifts copy wrong lengths; enormous shifts/rotations panic | [shift](builtins/ShiftByteString.nash), [rotate](builtins/RotateByteString.nash): direction, zero fill, width/inverse laws, 129-bit counts | -| Wrong hash primitive or digest width | six hash files: digest width and empty-message known answers where supplied; cryptographic model contracts remain pending | -| Malformed signature confused with well-formed verification False | signature files: invalid lengths, [RFC8032](https://www.rfc-editor.org/rfc/rfc8032) valid Ed25519 vector and changed-message False | -| Invalid compressed point/DST accepted; group operation or MSM incorrect | BLS files: valid-point algebra, compressed roundtrips, wrong widths, oversized DST, mismatched MSM lengths and scalar range | -| Wrong value update, zero removal, quantity overflow or negative containment | [insert](builtins/InsertCoin.nash), [union](builtins/UnionValue.nash), [scale](builtins/ScaleValue.nash), [containment](builtins/ValueContains.nash) | -| Malformed asset map accepted | [unValueData](builtins/UnValueData.nash): wrong Data variant, empty inner map, zero quantities, duplicate and unsorted names | - -## Findings and open model work - -The new obligations reproduced and now guard these local evaluator fixes: -`indexByteString` and `indexArray` reject unrepresentable indices instead of -truncating or panicking; shift/rotate counts no longer panic above u64; whole-byte -left shifts copy the correct number of bytes. The array rejection examples use -a monomorphic `array int` binding: an unused generalized literal template has no -runtime specialization in Nash, so it cannot exercise a rejection obligation. - -Associativity and several symbolic arithmetic/ordering obligations hit the -preparation wall limit. The ten array/value builtins lack a pinned Lean model. -BLS validation reaches unsupported `Fin`, and some symbolic bytestring operations -reach unsupported `BitVec` in the SMT translator. These outcomes remain pending. - -Cryptographic primitives and modular exponentiation are opaque in the pinned -model and lack supporting semantic lemmas. SMT can assign them behavior that -violates the intended rejection contract; the 12 such model counterexamples in -the snapshot are **not concrete Nash counterexamples**. Upstream work should add -contracts for shape validation, digest lengths, group laws and modular-exponentiation -error conditions, alongside the missing array/value model and translator support. -Native vector checks establish the listed examples independently of those SMT -abstractions; they do not verify cryptographic algorithms. - -The Rust Data representation stores constructor tags as u64 while Lean permits -arbitrary integer tags. Native smoke inputs for constructor roundtrips use the -common representable range; the Nash obligations retain their full symbolic -domains. The native `dropList` cost calculation also still converts the absolute -count through u64; counts above that range need an overflow-safe costing fix and -a corresponding large-count regression. Fixing these host gaps and proving all -compiler/type/optimizer specializations remain separate work. The existing independent -[calling-convention certificates](../docs/builtin-compilation.md) check opcodes, -forces and application prefixes across all 101 builtins. - -## Index - -| Builtin source file | Obligations | SMT-verified | -|---|---:|---:| -| [AddInteger](builtins/AddInteger.nash) | 11 | 10 | -| [AndByteString](builtins/AndByteString.nash) | 3 | 0 | -| [AppendByteString](builtins/AppendByteString.nash) | 4 | 0 | -| [AppendString](builtins/AppendString.nash) | 3 | 0 | -| [BData](builtins/BData.nash) | 1 | 0 | -| [Blake2b224](builtins/Blake2b224.nash) | 2 | 0 | -| [Blake2b256](builtins/Blake2b256.nash) | 2 | 0 | -| [Bls12381FinalVerify](builtins/Bls12381FinalVerify.nash) | 1 | 0 | -| [Bls12381G1Add](builtins/Bls12381G1Add.nash) | 1 | 0 | -| [Bls12381G1Compress](builtins/Bls12381G1Compress.nash) | 1 | 0 | -| [Bls12381G1Equal](builtins/Bls12381G1Equal.nash) | 1 | 0 | -| [Bls12381G1HashToGroup](builtins/Bls12381G1HashToGroup.nash) | 2 | 0 | -| [Bls12381G1Neg](builtins/Bls12381G1Neg.nash) | 1 | 0 | -| [Bls12381G1ScalarMul](builtins/Bls12381G1ScalarMul.nash) | 1 | 0 | -| [Bls12381G1Uncompress](builtins/Bls12381G1Uncompress.nash) | 4 | 0 | -| [Bls12381G2Add](builtins/Bls12381G2Add.nash) | 1 | 0 | -| [Bls12381G2Compress](builtins/Bls12381G2Compress.nash) | 1 | 0 | -| [Bls12381G2Equal](builtins/Bls12381G2Equal.nash) | 1 | 0 | -| [Bls12381G2HashToGroup](builtins/Bls12381G2HashToGroup.nash) | 2 | 0 | -| [Bls12381G2Neg](builtins/Bls12381G2Neg.nash) | 1 | 0 | -| [Bls12381G2ScalarMul](builtins/Bls12381G2ScalarMul.nash) | 1 | 0 | -| [Bls12381G2Uncompress](builtins/Bls12381G2Uncompress.nash) | 4 | 0 | -| [Bls12381MillerLoop](builtins/Bls12381MillerLoop.nash) | 1 | 0 | -| [Bls12381MulMlResult](builtins/Bls12381MulMlResult.nash) | 1 | 0 | -| [ByteStringToInteger](builtins/ByteStringToInteger.nash) | 4 | 0 | -| [ChooseData](builtins/ChooseData.nash) | 5 | 0 | -| [ChooseList](builtins/ChooseList.nash) | 3 | 0 | -| [ChooseUnit](builtins/ChooseUnit.nash) | 2 | 0 | -| [ComplementByteString](builtins/ComplementByteString.nash) | 3 | 0 | -| [ConsByteString](builtins/ConsByteString.nash) | 6 | 2 | -| [ConstrData](builtins/ConstrData.nash) | 1 | 0 | -| [CountSetBits](builtins/CountSetBits.nash) | 2 | 0 | -| [DecodeUtf8](builtins/DecodeUtf8.nash) | 7 | 1 | -| [DivideInteger](builtins/DivideInteger.nash) | 5 | 4 | -| [DropList](builtins/DropList.nash) | 5 | 0 | -| [EncodeUtf8](builtins/EncodeUtf8.nash) | 4 | 0 | -| [EqualsByteString](builtins/EqualsByteString.nash) | 4 | 0 | -| [EqualsData](builtins/EqualsData.nash) | 4 | 0 | -| [EqualsInteger](builtins/EqualsInteger.nash) | 5 | 2 | -| [EqualsString](builtins/EqualsString.nash) | 4 | 0 | -| [ExpModInteger](builtins/ExpModInteger.nash) | 6 | 0 | -| [FindFirstSetBit](builtins/FindFirstSetBit.nash) | 2 | 0 | -| [FstPair](builtins/FstPair.nash) | 1 | 0 | -| [HeadList](builtins/HeadList.nash) | 2 | 1 | -| [IData](builtins/IData.nash) | 1 | 0 | -| [IfThenElse](builtins/IfThenElse.nash) | 4 | 0 | -| [IndexArray](builtins/IndexArray.nash) | 5 | 0 | -| [IndexByteString](builtins/IndexByteString.nash) | 6 | 2 | -| [InsertCoin](builtins/InsertCoin.nash) | 7 | 0 | -| [IntegerToByteString](builtins/IntegerToByteString.nash) | 8 | 4 | -| [Keccak256](builtins/Keccak256.nash) | 1 | 0 | -| [LengthOfArray](builtins/LengthOfArray.nash) | 2 | 0 | -| [LengthOfByteString](builtins/LengthOfByteString.nash) | 4 | 0 | -| [LessThanByteString](builtins/LessThanByteString.nash) | 3 | 0 | -| [LessThanEqualsByteString](builtins/LessThanEqualsByteString.nash) | 2 | 0 | -| [LessThanEqualsInteger](builtins/LessThanEqualsInteger.nash) | 4 | 2 | -| [LessThanInteger](builtins/LessThanInteger.nash) | 5 | 3 | -| [ListData](builtins/ListData.nash) | 1 | 0 | -| [ListToArray](builtins/ListToArray.nash) | 1 | 0 | -| [LookupCoin](builtins/LookupCoin.nash) | 3 | 0 | -| [MapData](builtins/MapData.nash) | 1 | 0 | -| [MkCons](builtins/MkCons.nash) | 3 | 0 | -| [MkNilData](builtins/MkNilData.nash) | 2 | 0 | -| [MkNilPairData](builtins/MkNilPairData.nash) | 2 | 0 | -| [MkPairData](builtins/MkPairData.nash) | 1 | 0 | -| [ModInteger](builtins/ModInteger.nash) | 6 | 4 | -| [MultiplyInteger](builtins/MultiplyInteger.nash) | 8 | 5 | -| [NullList](builtins/NullList.nash) | 2 | 0 | -| [OrByteString](builtins/OrByteString.nash) | 3 | 0 | -| [QuotientInteger](builtins/QuotientInteger.nash) | 5 | 4 | -| [ReadBit](builtins/ReadBit.nash) | 4 | 3 | -| [RemainderInteger](builtins/RemainderInteger.nash) | 6 | 4 | -| [ReplicateByte](builtins/ReplicateByte.nash) | 7 | 4 | -| [Ripemd160](builtins/Ripemd160.nash) | 2 | 0 | -| [RotateByteString](builtins/RotateByteString.nash) | 4 | 0 | -| [ScaleValue](builtins/ScaleValue.nash) | 5 | 0 | -| [SerialiseData](builtins/SerialiseData.nash) | 1 | 0 | -| [Sha2256](builtins/Sha2256.nash) | 2 | 0 | -| [Sha3256](builtins/Sha3256.nash) | 2 | 0 | -| [ShiftByteString](builtins/ShiftByteString.nash) | 5 | 0 | -| [SliceByteString](builtins/SliceByteString.nash) | 6 | 0 | -| [SndPair](builtins/SndPair.nash) | 1 | 0 | -| [SubtractInteger](builtins/SubtractInteger.nash) | 6 | 5 | -| [TailList](builtins/TailList.nash) | 3 | 1 | -| [Trace](builtins/Trace.nash) | 2 | 0 | -| [UnBData](builtins/UnBData.nash) | 3 | 0 | -| [UnConstrData](builtins/UnConstrData.nash) | 3 | 0 | -| [UnIData](builtins/UnIData.nash) | 3 | 0 | -| [UnListData](builtins/UnListData.nash) | 3 | 0 | -| [UnMapData](builtins/UnMapData.nash) | 3 | 0 | -| [UnValueData](builtins/UnValueData.nash) | 6 | 0 | -| [UnionValue](builtins/UnionValue.nash) | 3 | 0 | -| [ValueContains](builtins/ValueContains.nash) | 4 | 0 | -| [ValueData](builtins/ValueData.nash) | 1 | 0 | -| [VerifyEcdsaSecp256k1Signature](builtins/VerifyEcdsaSecp256k1Signature.nash) | 2 | 0 | -| [VerifyEd25519Signature](builtins/VerifyEd25519Signature.nash) | 4 | 0 | -| [VerifySchnorrSecp256k1Signature](builtins/VerifySchnorrSecp256k1Signature.nash) | 2 | 0 | -| [WriteBits](builtins/WriteBits.nash) | 6 | 3 | -| [XorByteString](builtins/XorByteString.nash) | 3 | 0 | -| [Bls12381G1MultiScalarMul](pending/builtins/Bls12381G1MultiScalarMul.nash) | 3 | 0 | -| [Bls12381G2MultiScalarMul](pending/builtins/Bls12381G2MultiScalarMul.nash) | 3 | 0 | diff --git a/proofs/builtins/AddInteger.nash b/proofs/builtins/AddInteger.nash deleted file mode 100644 index 2dca4367..00000000 --- a/proofs/builtins/AddInteger.nash +++ /dev/null @@ -1,103 +0,0 @@ -module AddInteger exposing (..) - - -import Builtin - - -{-| UPLC addition uses arbitrary-precision integers. Both native int values and -Data-represented Int values must preserve that behavior through Nash compilation. -The symbolic domains below contain every mathematical integer, with no bit bound. --} -add : int -> int -> int -add = Builtin.addInteger - - -boxedAdd : Int -> Int -> Int -boxedAdd x y = Builtin.iData (add (Builtin.unIData x) (Builtin.unIData y)) - - -proof - import Proof - - prop "commutative" = - let - x via Proof.int - y via Proof.int - in - do - assert (add x y == add y x) - - prop "associative" = - let - x via Proof.int - y via Proof.int - z via Proof.int - in - do - assert (add (add x y) z == add x (add y z)) - - prop "left identity" = - let - x via Proof.int - in - do - assert (add 0 x == x) - - prop "right identity" = - let - x via Proof.int - in - do - assert (add x 0 == x) - - prop "additive inverse" = - let - x via Proof.int - in - do - assert (add x (Builtin.subtractInteger 0 x) == 0) - - prop "successor never wraps at any integer width" = - let - x via Proof.int - in - do - assert (add x 1 > x) - - prop "boxed successor never wraps at any integer width" = - let - x via Proof.integer - in - do - assert - ( - Builtin.unIData (boxedAdd x (Builtin.iData 1)) > - Builtin.unIData x - ) - - test "crosses signed 64-bit boundary" = - do - assert (add 9223372036854775807 1 == 9223372036854775808) - - test "crosses unsigned 128-bit boundary" = - do - let - before = - Builtin.byteStringToInteger - True - #"ffffffffffffffffffffffffffffffff" - - after = - Builtin.byteStringToInteger - True - #"0100000000000000000000000000000000" - in - assert (add before 1 == after) - - test "crosses negative signed 64-bit boundary" = - do - assert (add (-9223372036854775808) (-1) == -9223372036854775809) - - test "detects wrong opcode argument order or signedness" = - do - assert (add 17 5 == 22) diff --git a/proofs/builtins/AndByteString.nash b/proofs/builtins/AndByteString.nash deleted file mode 100644 index 63717c95..00000000 --- a/proofs/builtins/AndByteString.nash +++ /dev/null @@ -1,35 +0,0 @@ -module AndByteString exposing (..) - - -import Builtin - - -operation p0 p1 p2 = Builtin.andByteString p0 p1 p2 - - -proof - import Proof - - prop "commutative for both padding modes" = - let - pad via Proof.bool - x via Proof.bytes - y via Proof.bytes - in - do - assert (operation pad x y == operation pad y x) - - prop "self law" = - let - pad via Proof.bool - x via Proof.bytes - in - do - assert (operation pad x x == x) - - prop "truncating mode with empty input" = - let - x via Proof.bytes - in - do - assert (operation False x #"" == #"") diff --git a/proofs/builtins/AppendByteString.nash b/proofs/builtins/AppendByteString.nash deleted file mode 100644 index f243f0e0..00000000 --- a/proofs/builtins/AppendByteString.nash +++ /dev/null @@ -1,46 +0,0 @@ -module AppendByteString exposing (..) - - -import Builtin - - -operation p0 p1 = Builtin.appendByteString p0 p1 - - -proof - import Proof - - prop "left and right identity" = - let - x via Proof.bytes - in - do - assert (operation #"" x == x && operation x #"" == x) - - prop "associative" = - let - x via Proof.bytes - y via Proof.bytes - z via Proof.bytes - in - do - assert - (operation (operation x y) z == operation x (operation y z)) - - prop "length is additive" = - let - x via Proof.bytes - y via Proof.bytes - in - do - assert - ( - Builtin.lengthOfByteString (operation x y) == - Builtin.addInteger - (Builtin.lengthOfByteString x) - (Builtin.lengthOfByteString y) - ) - - test "preserves byte order" = - do - assert (operation #"00ff" #"0180" == #"00ff0180") diff --git a/proofs/builtins/AppendString.nash b/proofs/builtins/AppendString.nash deleted file mode 100644 index 97c9161e..00000000 --- a/proofs/builtins/AppendString.nash +++ /dev/null @@ -1,32 +0,0 @@ -module AppendString exposing (..) - - -import Builtin - - -operation p0 p1 = Builtin.appendString p0 p1 - - -proof - import Proof - - prop "left and right identity" = - let - x via Proof.string - in - do - assert (operation "" x == x && operation x "" == x) - - prop "associative" = - let - x via Proof.string - y via Proof.string - z via Proof.string - in - do - assert - (operation (operation x y) z == operation x (operation y z)) - - test "preserves Unicode characters" = - do - assert (operation "é" "λ" == "éλ") diff --git a/proofs/builtins/BData.nash b/proofs/builtins/BData.nash deleted file mode 100644 index 43a08d5f..00000000 --- a/proofs/builtins/BData.nash +++ /dev/null @@ -1,18 +0,0 @@ -module BData exposing (..) - - -import Builtin - - -operation p0 = Builtin.bData p0 - - -proof - import Proof - - prop "bytes roundtrip" = - let - x via Proof.bytes - in - do - assert (Builtin.unBData (operation x) == x) diff --git a/proofs/builtins/Blake2b224.nash b/proofs/builtins/Blake2b224.nash deleted file mode 100644 index 9874e843..00000000 --- a/proofs/builtins/Blake2b224.nash +++ /dev/null @@ -1,27 +0,0 @@ -module Blake2b224 exposing (..) - - -import Builtin - - -{-| Digest length is the semantic obligation here. This does not claim collision resistance or correctness of the underlying cryptographic implementation. The pinned Lean model exposes an opaque hash without length lemmas; this obligation is pending there. -} -operation p0 = Builtin.blake2b_224 p0 - - -proof - import Proof - - prop "digest has the specified byte length" = - let - message via Proof.bytes - in - do - assert (Builtin.lengthOfByteString (operation message) == 28) - - test "empty message known answer detects the wrong hash primitive" = - do - assert - ( - operation #"" == - #"836cc68931c2e4e3e838602eca1902591d216837bafddfe6f0c8cb07" - ) diff --git a/proofs/builtins/Blake2b256.nash b/proofs/builtins/Blake2b256.nash deleted file mode 100644 index 96e5f905..00000000 --- a/proofs/builtins/Blake2b256.nash +++ /dev/null @@ -1,27 +0,0 @@ -module Blake2b256 exposing (..) - - -import Builtin - - -{-| Digest length is the semantic obligation here. This does not claim collision resistance or correctness of the underlying cryptographic implementation. The pinned Lean model exposes an opaque hash without length lemmas; this obligation is pending there. -} -operation p0 = Builtin.blake2b_256 p0 - - -proof - import Proof - - prop "digest has the specified byte length" = - let - message via Proof.bytes - in - do - assert (Builtin.lengthOfByteString (operation message) == 32) - - test "empty message known answer detects the wrong hash primitive" = - do - assert - ( - operation #"" == - #"0e5751c026e543b2e8ab2eb06099daa1d1e5df47778f7787faab45cdf12fe3a8" - ) diff --git a/proofs/builtins/Bls12381FinalVerify.nash b/proofs/builtins/Bls12381FinalVerify.nash deleted file mode 100644 index 8a22668b..00000000 --- a/proofs/builtins/Bls12381FinalVerify.nash +++ /dev/null @@ -1,32 +0,0 @@ -module Bls12381FinalVerify exposing (..) - - -import Builtin - - -{-| Pairing consistency is pending opaque cryptographic model contracts. -} -operation p0 p1 = Builtin.bls12_381_finalVerify p0 p1 - - -g1 : bytes -> bls_g1 -g1 encoded = Builtin.bls12_381_g1_uncompress encoded - - -g2 : bytes -> bls_g2 -g2 encoded = Builtin.bls12_381_g2_uncompress encoded - - -ml : bytes -> bytes -> bls_mlr -ml x y = Builtin.bls12_381_millerLoop (g1 x) (g2 y) - - -proof - import Proof - - prop "reflexive on valid Miller loop results" = - let - x via Proof.bytes - y via Proof.bytes - in - do - Proof.returns (operation (ml x y) (ml x y)) (\holds -> holds) diff --git a/proofs/builtins/Bls12381G1Add.nash b/proofs/builtins/Bls12381G1Add.nash deleted file mode 100644 index be316a35..00000000 --- a/proofs/builtins/Bls12381G1Add.nash +++ /dev/null @@ -1,30 +0,0 @@ -module Bls12381G1Add exposing (..) - - -import Builtin - - -{-| These group laws are partial correctness over successfully decoded points. They do not assert that arbitrary bytes decode to a valid point. The pinned Lean model has opaque group operations without algebraic lemmas; these obligations are pending there. -} -operation p0 p1 = Builtin.bls12_381_g1_add p0 p1 - - -point : bytes -> bls_g1 -point encoded = Builtin.bls12_381_g1_uncompress encoded - - -proof - import Proof - - prop "commutative on valid decoded points" = - let - x via Proof.bytes - y via Proof.bytes - in - do - Proof.returns - ( - Builtin.bls12_381_g1_equal - (operation (point x) (point y)) - (operation (point y) (point x)) - ) - (\holds -> holds) diff --git a/proofs/builtins/Bls12381G1Compress.nash b/proofs/builtins/Bls12381G1Compress.nash deleted file mode 100644 index 9ed8ebe2..00000000 --- a/proofs/builtins/Bls12381G1Compress.nash +++ /dev/null @@ -1,25 +0,0 @@ -module Bls12381G1Compress exposing (..) - - -import Builtin - - -{-| These group laws are partial correctness over successfully decoded points. They do not assert that arbitrary bytes decode to a valid point. The pinned Lean model has opaque group operations without algebraic lemmas; these obligations are pending there. -} -operation p0 = Builtin.bls12_381_g1_compress p0 - - -point : bytes -> bls_g1 -point encoded = Builtin.bls12_381_g1_uncompress encoded - - -proof - import Proof - - prop "fixed compressed width on valid points" = - let - x via Proof.bytes - in - do - Proof.returns - (Builtin.lengthOfByteString (operation (point x)) == 48) - (\holds -> holds) diff --git a/proofs/builtins/Bls12381G1Equal.nash b/proofs/builtins/Bls12381G1Equal.nash deleted file mode 100644 index b73653a8..00000000 --- a/proofs/builtins/Bls12381G1Equal.nash +++ /dev/null @@ -1,23 +0,0 @@ -module Bls12381G1Equal exposing (..) - - -import Builtin - - -{-| These group laws are partial correctness over successfully decoded points. They do not assert that arbitrary bytes decode to a valid point. The pinned Lean model has opaque group operations without algebraic lemmas; these obligations are pending there. -} -operation p0 p1 = Builtin.bls12_381_g1_equal p0 p1 - - -point : bytes -> bls_g1 -point encoded = Builtin.bls12_381_g1_uncompress encoded - - -proof - import Proof - - prop "reflexive on valid points" = - let - x via Proof.bytes - in - do - Proof.returns (operation (point x) (point x)) (\holds -> holds) diff --git a/proofs/builtins/Bls12381G1HashToGroup.nash b/proofs/builtins/Bls12381G1HashToGroup.nash deleted file mode 100644 index c21d79d5..00000000 --- a/proofs/builtins/Bls12381G1HashToGroup.nash +++ /dev/null @@ -1,34 +0,0 @@ -module Bls12381G1HashToGroup exposing (..) - - -import Builtin - - -{-| Uses a short fixed domain separation tag. Pending cryptographic contracts in the pinned Lean model. -} -operation p0 p1 = Builtin.bls12_381_g1_hashToGroup p0 p1 - - -proof - import Proof - - prop "hash to group yields a compressed point of fixed width" = - let - message via Proof.bytes - in - do - assert - ( - Builtin.lengthOfByteString - ( - Builtin.bls12_381_g1_compress - (operation message #"4e617368") - ) == - 48 - ) - - test "oversized domain separator rejects" fail = - do - let - _ = operation #"" (Builtin.replicateByte 256 0) - in - () diff --git a/proofs/builtins/Bls12381G1Neg.nash b/proofs/builtins/Bls12381G1Neg.nash deleted file mode 100644 index adad4897..00000000 --- a/proofs/builtins/Bls12381G1Neg.nash +++ /dev/null @@ -1,29 +0,0 @@ -module Bls12381G1Neg exposing (..) - - -import Builtin - - -{-| These group laws are partial correctness over successfully decoded points. They do not assert that arbitrary bytes decode to a valid point. The pinned Lean model has opaque group operations without algebraic lemmas; these obligations are pending there. -} -operation p0 = Builtin.bls12_381_g1_neg p0 - - -point : bytes -> bls_g1 -point encoded = Builtin.bls12_381_g1_uncompress encoded - - -proof - import Proof - - prop "negation is an involution on valid points" = - let - x via Proof.bytes - in - do - Proof.returns - ( - Builtin.bls12_381_g1_equal - (operation (operation (point x))) - (point x) - ) - (\holds -> holds) diff --git a/proofs/builtins/Bls12381G1ScalarMul.nash b/proofs/builtins/Bls12381G1ScalarMul.nash deleted file mode 100644 index f8e6b56a..00000000 --- a/proofs/builtins/Bls12381G1ScalarMul.nash +++ /dev/null @@ -1,29 +0,0 @@ -module Bls12381G1ScalarMul exposing (..) - - -import Builtin - - -{-| These group laws are partial correctness over successfully decoded points. They do not assert that arbitrary bytes decode to a valid point. The pinned Lean model has opaque group operations without algebraic lemmas; these obligations are pending there. -} -operation p0 p1 = Builtin.bls12_381_g1_scalarMul p0 p1 - - -point : bytes -> bls_g1 -point encoded = Builtin.bls12_381_g1_uncompress encoded - - -proof - import Proof - - prop "scalar one is the identity on valid points" = - let - x via Proof.bytes - in - do - Proof.returns - ( - Builtin.bls12_381_g1_equal - (operation 1 (point x)) - (point x) - ) - (\holds -> holds) diff --git a/proofs/builtins/Bls12381G1Uncompress.nash b/proofs/builtins/Bls12381G1Uncompress.nash deleted file mode 100644 index d8a9bc6a..00000000 --- a/proofs/builtins/Bls12381G1Uncompress.nash +++ /dev/null @@ -1,47 +0,0 @@ -module Bls12381G1Uncompress exposing (..) - - -import Builtin - - -{-| These group laws are partial correctness over successfully decoded points. They do not assert that arbitrary bytes decode to a valid point. The pinned Lean model has opaque group operations without algebraic lemmas; these obligations are pending there. -} -operation p0 = Builtin.bls12_381_g1_uncompress p0 - - -proof - import Proof - - prop "canonical compressed roundtrip on valid encodings" = - let - x via Proof.bytes - in - do - Proof.returns - (Builtin.bls12_381_g1_compress (operation x) == x) - (\holds -> holds) - - test "empty encoding rejects" fail = - do - let - _ = operation #"" - in - () - - test "wrong compressed width rejects" fail = - do - let - _ = operation #"00" - in - () - - test "canonical infinity is accepted" = - do - assert - ( - Builtin.bls12_381_g1_compress - ( - operation - #"c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" - ) == - #"c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" - ) diff --git a/proofs/builtins/Bls12381G2Add.nash b/proofs/builtins/Bls12381G2Add.nash deleted file mode 100644 index 7fb74adf..00000000 --- a/proofs/builtins/Bls12381G2Add.nash +++ /dev/null @@ -1,30 +0,0 @@ -module Bls12381G2Add exposing (..) - - -import Builtin - - -{-| These group laws are partial correctness over successfully decoded points. They do not assert that arbitrary bytes decode to a valid point. The pinned Lean model has opaque group operations without algebraic lemmas; these obligations are pending there. -} -operation p0 p1 = Builtin.bls12_381_g2_add p0 p1 - - -point : bytes -> bls_g2 -point encoded = Builtin.bls12_381_g2_uncompress encoded - - -proof - import Proof - - prop "commutative on valid decoded points" = - let - x via Proof.bytes - y via Proof.bytes - in - do - Proof.returns - ( - Builtin.bls12_381_g2_equal - (operation (point x) (point y)) - (operation (point y) (point x)) - ) - (\holds -> holds) diff --git a/proofs/builtins/Bls12381G2Compress.nash b/proofs/builtins/Bls12381G2Compress.nash deleted file mode 100644 index 61d3faa1..00000000 --- a/proofs/builtins/Bls12381G2Compress.nash +++ /dev/null @@ -1,25 +0,0 @@ -module Bls12381G2Compress exposing (..) - - -import Builtin - - -{-| These group laws are partial correctness over successfully decoded points. They do not assert that arbitrary bytes decode to a valid point. The pinned Lean model has opaque group operations without algebraic lemmas; these obligations are pending there. -} -operation p0 = Builtin.bls12_381_g2_compress p0 - - -point : bytes -> bls_g2 -point encoded = Builtin.bls12_381_g2_uncompress encoded - - -proof - import Proof - - prop "fixed compressed width on valid points" = - let - x via Proof.bytes - in - do - Proof.returns - (Builtin.lengthOfByteString (operation (point x)) == 96) - (\holds -> holds) diff --git a/proofs/builtins/Bls12381G2Equal.nash b/proofs/builtins/Bls12381G2Equal.nash deleted file mode 100644 index 59e089d2..00000000 --- a/proofs/builtins/Bls12381G2Equal.nash +++ /dev/null @@ -1,23 +0,0 @@ -module Bls12381G2Equal exposing (..) - - -import Builtin - - -{-| These group laws are partial correctness over successfully decoded points. They do not assert that arbitrary bytes decode to a valid point. The pinned Lean model has opaque group operations without algebraic lemmas; these obligations are pending there. -} -operation p0 p1 = Builtin.bls12_381_g2_equal p0 p1 - - -point : bytes -> bls_g2 -point encoded = Builtin.bls12_381_g2_uncompress encoded - - -proof - import Proof - - prop "reflexive on valid points" = - let - x via Proof.bytes - in - do - Proof.returns (operation (point x) (point x)) (\holds -> holds) diff --git a/proofs/builtins/Bls12381G2HashToGroup.nash b/proofs/builtins/Bls12381G2HashToGroup.nash deleted file mode 100644 index bb9c4889..00000000 --- a/proofs/builtins/Bls12381G2HashToGroup.nash +++ /dev/null @@ -1,34 +0,0 @@ -module Bls12381G2HashToGroup exposing (..) - - -import Builtin - - -{-| Uses a short fixed domain separation tag. Pending cryptographic contracts in the pinned Lean model. -} -operation p0 p1 = Builtin.bls12_381_g2_hashToGroup p0 p1 - - -proof - import Proof - - prop "hash to group yields a compressed point of fixed width" = - let - message via Proof.bytes - in - do - assert - ( - Builtin.lengthOfByteString - ( - Builtin.bls12_381_g2_compress - (operation message #"4e617368") - ) == - 96 - ) - - test "oversized domain separator rejects" fail = - do - let - _ = operation #"" (Builtin.replicateByte 256 0) - in - () diff --git a/proofs/builtins/Bls12381G2Neg.nash b/proofs/builtins/Bls12381G2Neg.nash deleted file mode 100644 index e7eb63d4..00000000 --- a/proofs/builtins/Bls12381G2Neg.nash +++ /dev/null @@ -1,29 +0,0 @@ -module Bls12381G2Neg exposing (..) - - -import Builtin - - -{-| These group laws are partial correctness over successfully decoded points. They do not assert that arbitrary bytes decode to a valid point. The pinned Lean model has opaque group operations without algebraic lemmas; these obligations are pending there. -} -operation p0 = Builtin.bls12_381_g2_neg p0 - - -point : bytes -> bls_g2 -point encoded = Builtin.bls12_381_g2_uncompress encoded - - -proof - import Proof - - prop "negation is an involution on valid points" = - let - x via Proof.bytes - in - do - Proof.returns - ( - Builtin.bls12_381_g2_equal - (operation (operation (point x))) - (point x) - ) - (\holds -> holds) diff --git a/proofs/builtins/Bls12381G2ScalarMul.nash b/proofs/builtins/Bls12381G2ScalarMul.nash deleted file mode 100644 index 216ce134..00000000 --- a/proofs/builtins/Bls12381G2ScalarMul.nash +++ /dev/null @@ -1,29 +0,0 @@ -module Bls12381G2ScalarMul exposing (..) - - -import Builtin - - -{-| These group laws are partial correctness over successfully decoded points. They do not assert that arbitrary bytes decode to a valid point. The pinned Lean model has opaque group operations without algebraic lemmas; these obligations are pending there. -} -operation p0 p1 = Builtin.bls12_381_g2_scalarMul p0 p1 - - -point : bytes -> bls_g2 -point encoded = Builtin.bls12_381_g2_uncompress encoded - - -proof - import Proof - - prop "scalar one is the identity on valid points" = - let - x via Proof.bytes - in - do - Proof.returns - ( - Builtin.bls12_381_g2_equal - (operation 1 (point x)) - (point x) - ) - (\holds -> holds) diff --git a/proofs/builtins/Bls12381G2Uncompress.nash b/proofs/builtins/Bls12381G2Uncompress.nash deleted file mode 100644 index 19f98ea2..00000000 --- a/proofs/builtins/Bls12381G2Uncompress.nash +++ /dev/null @@ -1,47 +0,0 @@ -module Bls12381G2Uncompress exposing (..) - - -import Builtin - - -{-| These group laws are partial correctness over successfully decoded points. They do not assert that arbitrary bytes decode to a valid point. The pinned Lean model has opaque group operations without algebraic lemmas; these obligations are pending there. -} -operation p0 = Builtin.bls12_381_g2_uncompress p0 - - -proof - import Proof - - prop "canonical compressed roundtrip on valid encodings" = - let - x via Proof.bytes - in - do - Proof.returns - (Builtin.bls12_381_g2_compress (operation x) == x) - (\holds -> holds) - - test "empty encoding rejects" fail = - do - let - _ = operation #"" - in - () - - test "wrong compressed width rejects" fail = - do - let - _ = operation #"00" - in - () - - test "canonical infinity is accepted" = - do - assert - ( - Builtin.bls12_381_g2_compress - ( - operation - #"c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" - ) == - #"c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" - ) diff --git a/proofs/builtins/Bls12381MillerLoop.nash b/proofs/builtins/Bls12381MillerLoop.nash deleted file mode 100644 index 8416a69f..00000000 --- a/proofs/builtins/Bls12381MillerLoop.nash +++ /dev/null @@ -1,34 +0,0 @@ -module Bls12381MillerLoop exposing (..) - - -import Builtin - - -{-| Basic pairing consistency, pending opaque cryptographic model contracts. -} -operation p0 p1 = Builtin.bls12_381_millerLoop p0 p1 - - -g1 : bytes -> bls_g1 -g1 encoded = Builtin.bls12_381_g1_uncompress encoded - - -g2 : bytes -> bls_g2 -g2 encoded = Builtin.bls12_381_g2_uncompress encoded - - -proof - import Proof - - prop "pairing the same points gives equivalent results" = - let - x via Proof.bytes - y via Proof.bytes - in - do - Proof.returns - ( - Builtin.bls12_381_finalVerify - (operation (g1 x) (g2 y)) - (operation (g1 x) (g2 y)) - ) - (\holds -> holds) diff --git a/proofs/builtins/Bls12381MulMlResult.nash b/proofs/builtins/Bls12381MulMlResult.nash deleted file mode 100644 index c27c28a5..00000000 --- a/proofs/builtins/Bls12381MulMlResult.nash +++ /dev/null @@ -1,40 +0,0 @@ -module Bls12381MulMlResult exposing (..) - - -import Builtin - - -{-| Pairing algebra is pending opaque cryptographic model contracts. -} -operation p0 p1 = Builtin.bls12_381_mulMlResult p0 p1 - - -g1 : bytes -> bls_g1 -g1 encoded = Builtin.bls12_381_g1_uncompress encoded - - -g2 : bytes -> bls_g2 -g2 encoded = Builtin.bls12_381_g2_uncompress encoded - - -ml : bytes -> bytes -> bls_mlr -ml x y = Builtin.bls12_381_millerLoop (g1 x) (g2 y) - - -proof - import Proof - - prop "commutative under final verification" = - let - leftG1 via Proof.bytes - leftG2 via Proof.bytes - rightG1 via Proof.bytes - rightG2 via Proof.bytes - in - do - Proof.returns - ( - Builtin.bls12_381_finalVerify - (operation (ml leftG1 leftG2) (ml rightG1 rightG2)) - (operation (ml rightG1 rightG2) (ml leftG1 leftG2)) - ) - (\holds -> holds) diff --git a/proofs/builtins/ByteStringToInteger.nash b/proofs/builtins/ByteStringToInteger.nash deleted file mode 100644 index 7c63f865..00000000 --- a/proofs/builtins/ByteStringToInteger.nash +++ /dev/null @@ -1,47 +0,0 @@ -module ByteStringToInteger exposing (..) - - -import Builtin - - -operation p0 p1 = Builtin.byteStringToInteger p0 p1 - - -proof - import Proof - - prop "empty bytes mean zero" = - let - bigEndian via Proof.bool - in - do - assert (operation bigEndian #"" == 0) - - test "endianness" = - do - assert - ( - operation True #"0102" == - 258 && - operation False #"0102" == - 513 - ) - - prop "arbitrary width conversion stays nonnegative" = - let - bigEndian via Proof.bool - x via Proof.bytes - in - do - assert (operation bigEndian x >= 0) - - prop "leading zero does not alter big endian value" = - let - x via Proof.bytes - in - do - assert - ( - operation True (Builtin.consByteString 0 x) == - operation True x - ) diff --git a/proofs/builtins/ChooseData.nash b/proofs/builtins/ChooseData.nash deleted file mode 100644 index b7222acc..00000000 --- a/proofs/builtins/ChooseData.nash +++ /dev/null @@ -1,65 +0,0 @@ -module ChooseData exposing (..) - - -import Builtin - - -operation p0 p1 p2 p3 p4 p5 = Builtin.chooseData p0 p1 p2 p3 p4 p5 - - -emptyMap : Map Data Data -emptyMap = Builtin.mapData [] - - -emptyList : List Data -emptyList = Builtin.listData [] - - -proof - import Proof - - test "selects the constructor branch" = - do - assert (operation (Builtin.constrData 0 []) 10 20 30 40 50 == 10) - - test "selects the map branch" = - do - assert (operation (Primitive.coerce emptyMap) 10 20 30 40 50 == 20) - - test "selects the list branch" = - do - assert (operation (Primitive.coerce emptyList) 10 20 30 40 50 == 30) - - prop "selects the integer branch" = - let - x via Proof.int - in - do - assert - ( - operation - (Primitive.coerce (Builtin.iData x)) - 10 - 20 - 30 - 40 - 50 == - 40 - ) - - prop "selects the bytes branch" = - let - x via Proof.bytes - in - do - assert - ( - operation - (Primitive.coerce (Builtin.bData x)) - 10 - 20 - 30 - 40 - 50 == - 50 - ) diff --git a/proofs/builtins/ChooseList.nash b/proofs/builtins/ChooseList.nash deleted file mode 100644 index 4e04e741..00000000 --- a/proofs/builtins/ChooseList.nash +++ /dev/null @@ -1,39 +0,0 @@ -module ChooseList exposing (..) - - -import Builtin - - -operation : list Data -> int -> int -> int -operation p0 p1 p2 = Builtin.chooseList p0 p1 p2 - - -proof - import Proof - - prop "empty list chooses the empty branch" = - let - x via Proof.int - y via Proof.int - in - do - assert (operation [] x y == x) - - prop "nonempty list chooses the nonempty branch" = - let - item via Proof.data - x via Proof.int - y via Proof.int - in - do - assert (operation [ item ] x y == y) - - test "detects wrong opcode argument order or signedness" = - do - assert - ( - operation [] 17 5 == - 17 && - operation [ Builtin.constrData 0 [] ] 17 5 == - 5 - ) diff --git a/proofs/builtins/ChooseUnit.nash b/proofs/builtins/ChooseUnit.nash deleted file mode 100644 index 4eba303c..00000000 --- a/proofs/builtins/ChooseUnit.nash +++ /dev/null @@ -1,22 +0,0 @@ -module ChooseUnit exposing (..) - - -import Builtin - - -operation p0 p1 = Builtin.chooseUnit p0 p1 - - -proof - import Proof - - prop "returns the second argument" = - let - x via Proof.data - in - do - assert (operation () x == x) - - test "detects wrong opcode argument order or signedness" = - do - assert (operation () 17 == 17) diff --git a/proofs/builtins/ComplementByteString.nash b/proofs/builtins/ComplementByteString.nash deleted file mode 100644 index 638ceb41..00000000 --- a/proofs/builtins/ComplementByteString.nash +++ /dev/null @@ -1,33 +0,0 @@ -module ComplementByteString exposing (..) - - -import Builtin - - -operation p0 = Builtin.complementByteString p0 - - -proof - import Proof - - prop "involution" = - let - x via Proof.bytes - in - do - assert (operation (operation x) == x) - - prop "preserves length" = - let - x via Proof.bytes - in - do - assert - ( - Builtin.lengthOfByteString (operation x) == - Builtin.lengthOfByteString x - ) - - test "complements every bit" = - do - assert (operation #"00ff0f" == #"ff00f0") diff --git a/proofs/builtins/ConsByteString.nash b/proofs/builtins/ConsByteString.nash deleted file mode 100644 index 87f0f382..00000000 --- a/proofs/builtins/ConsByteString.nash +++ /dev/null @@ -1,97 +0,0 @@ -module ConsByteString exposing (..) - - -import Builtin - - -{-| Plutus V3 uses strict byte validation; V1/V2 reduce the byte modulo 256. -} -operation p0 p1 = Builtin.consByteString p0 p1 - - -proof - import Proof - - prop "prepends zero and preserves length" = - let - x via Proof.bytes - in - do - assert - ( - Builtin.indexByteString (operation 0 x) 0 == - 0 && - Builtin.lengthOfByteString (operation 0 x) == - Builtin.addInteger (Builtin.lengthOfByteString x) 1 - ) - - prop "valid byte survives at the head" = - let - n via Proof.int - x via Proof.bytes - in - do - assert - ( - if n >= 0 && n <= 255 then - Builtin.indexByteString (operation n x) 0 == n - else - True - ) - - prop "negative byte rejects in Plutus V3" fail = - let - x via Proof.bytes - in - do - let - _ = operation (-1) x - in - () - - prop "byte above 255 rejects in Plutus V3" fail = - let - x via Proof.bytes - in - do - let - _ = operation 256 x - in - () - - prop "all negative byte values reject" fail = - let - n via Proof.int - x via Proof.bytes - in - do - let - _ = - operation - ( - if n < 0 then - n - else - -1 - n - ) - x - in - () - - prop "all byte values above 255 reject" fail = - let - n via Proof.int - x via Proof.bytes - in - do - let - _ = - operation - ( - if n > 255 then - n - else - 256 - ) - x - in - () diff --git a/proofs/builtins/ConstrData.nash b/proofs/builtins/ConstrData.nash deleted file mode 100644 index 771500be..00000000 --- a/proofs/builtins/ConstrData.nash +++ /dev/null @@ -1,33 +0,0 @@ -module ConstrData exposing (..) - - -import Builtin - - -operation p0 p1 = Builtin.constrData p0 p1 - - -proof - import Proof - - prop "constructor tag and fields survive roundtrip" = - let - tag via Proof.int - x via Proof.data - in - do - assert - ( - Builtin.fstPair - (Builtin.unConstrData (operation tag [ x ])) == - tag && - Builtin.headList - ( - Builtin.sndPair - ( - Builtin.unConstrData - (operation tag [ x ]) - ) - ) == - x - ) diff --git a/proofs/builtins/CountSetBits.nash b/proofs/builtins/CountSetBits.nash deleted file mode 100644 index 4aca83c7..00000000 --- a/proofs/builtins/CountSetBits.nash +++ /dev/null @@ -1,38 +0,0 @@ -module CountSetBits exposing (..) - - -import Builtin - - -operation p0 = Builtin.countSetBits p0 - - -proof - import Proof - - prop "count is bounded by the bit width" = - let - x via Proof.bytes - in - do - assert - ( - operation x >= - 0 && - operation x <= - Builtin.multiplyInteger - 8 - (Builtin.lengthOfByteString x) - ) - - test "known counts" = - do - assert - ( - operation #"00" == - 0 && - operation #"ff" == - 8 && - operation #"0103" == - 3 - ) diff --git a/proofs/builtins/DecodeUtf8.nash b/proofs/builtins/DecodeUtf8.nash deleted file mode 100644 index fba9ee61..00000000 --- a/proofs/builtins/DecodeUtf8.nash +++ /dev/null @@ -1,57 +0,0 @@ -module DecodeUtf8 exposing (..) - - -import Builtin - - -operation p0 = Builtin.decodeUtf8 p0 - - -proof - import Proof - - prop "inverse on encoded strings" = - let - text via Proof.string - in - do - assert (operation (Builtin.encodeUtf8 text) == text) - - test "non ASCII decoding" = - do - assert (operation #"c3a9cebb" == "éλ") - - test "invalid UTF8 rejects" fail = - do - let - _ = operation #"ff" - in - () - - test "truncated UTF8 rejects" fail = - do - let - _ = operation #"c3" - in - () - - test "overlong UTF8 is rejected" fail = - do - let - _ = operation #"c080" - in - () - - test "UTF8 surrogate codepoints are rejected" fail = - do - let - _ = operation #"eda080" - in - () - - test "codepoint above Unicode maximum is rejected" fail = - do - let - _ = operation #"f4908080" - in - () diff --git a/proofs/builtins/DivideInteger.nash b/proofs/builtins/DivideInteger.nash deleted file mode 100644 index 3b7e816a..00000000 --- a/proofs/builtins/DivideInteger.nash +++ /dev/null @@ -1,54 +0,0 @@ -module DivideInteger exposing (..) - - -import Builtin - - -operation p0 p1 = Builtin.divideInteger p0 p1 - - -proof - import Proof - - prop "division by one" = - let - x via Proof.int - in - do - assert (operation x 1 == x) - - prop "quotient and remainder reconstruct the dividend" = - let - x via Proof.int - y via Proof.int - in - do - assert - ( - if y == 0 then - True - else - Builtin.addInteger - (Builtin.multiplyInteger (operation x y) y) - (Builtin.modInteger x y) == - x - ) - - test "negative rounding convention" = - do - assert (operation (-7) 3 == -3) - - prop "zero divisor rejects every dividend" fail = - let - x via Proof.int - in - do - let - _ = operation x 0 - in - () - - test "detects wrong opcode argument order or signedness" = - do - assert - (operation (-9223372036854775808) (-1) == 9223372036854775808) diff --git a/proofs/builtins/DropList.nash b/proofs/builtins/DropList.nash deleted file mode 100644 index 926eaaf0..00000000 --- a/proofs/builtins/DropList.nash +++ /dev/null @@ -1,48 +0,0 @@ -module DropList exposing (..) - - -import Builtin - - -operation p0 p1 = Builtin.dropList p0 p1 - - -proof - import Proof - - prop "dropping zero preserves the head" = - let - x via Proof.data - in - do - assert (Builtin.headList (operation 0 [ x ]) == x) - - prop "dropping one preserves the next element" = - let - x via Proof.data - y via Proof.data - in - do - assert (Builtin.headList (operation 1 [ x, y ]) == y) - - prop "dropping the full list gives empty" = - let - x via Proof.data - y via Proof.data - in - do - assert (Builtin.nullList (operation 2 [ x, y ])) - - prop "negative count preserves the list" = - let - x via Proof.data - in - do - assert (Builtin.headList (operation (-1) [ x ]) == x) - - prop "count beyond length gives empty" = - let - x via Proof.data - in - do - assert (Builtin.nullList (operation 1000000 [ x ])) diff --git a/proofs/builtins/EncodeUtf8.nash b/proofs/builtins/EncodeUtf8.nash deleted file mode 100644 index b7b72523..00000000 --- a/proofs/builtins/EncodeUtf8.nash +++ /dev/null @@ -1,38 +0,0 @@ -module EncodeUtf8 exposing (..) - - -import Builtin - - -operation p0 = Builtin.encodeUtf8 p0 - - -proof - import Proof - - prop "decode after encoding" = - let - text via Proof.string - in - do - assert (Builtin.decodeUtf8 (operation text) == text) - - prop "append is preserved" = - let - x via Proof.string - y via Proof.string - in - do - assert - ( - operation (Builtin.appendString x y) == - Builtin.appendByteString (operation x) (operation y) - ) - - test "non ASCII encoding" = - do - assert (operation "éλ" == #"c3a9cebb") - - test "embedded zero is preserved" = - do - assert (operation "a\u{0000}b" == #"610062") diff --git a/proofs/builtins/EqualsByteString.nash b/proofs/builtins/EqualsByteString.nash deleted file mode 100644 index 7e74704c..00000000 --- a/proofs/builtins/EqualsByteString.nash +++ /dev/null @@ -1,46 +0,0 @@ -module EqualsByteString exposing (..) - - -import Builtin - - -operation p0 p1 = Builtin.equalsByteString p0 p1 - - -proof - import Proof - - prop "reflexive" = - let - x via Proof.bytes - in - do - assert (operation x x) - - prop "symmetric" = - let - x via Proof.bytes - y via Proof.bytes - in - do - assert (operation x y == operation y x) - - prop "transitive" = - let - x via Proof.bytes - y via Proof.bytes - z via Proof.bytes - in - do - assert - ( - if operation x y && operation y z then - operation x z - else - True - ) - - test "detects wrong opcode argument order or signedness" = - do - assert - (not (operation #"00" #"0000") && not (operation #"00" #"01")) diff --git a/proofs/builtins/EqualsData.nash b/proofs/builtins/EqualsData.nash deleted file mode 100644 index ad42c4fd..00000000 --- a/proofs/builtins/EqualsData.nash +++ /dev/null @@ -1,53 +0,0 @@ -module EqualsData exposing (..) - - -import Builtin - - -operation p0 p1 = Builtin.equalsData p0 p1 - - -proof - import Proof - - prop "reflexive" = - let - x via Proof.data - in - do - assert (operation x x) - - prop "symmetric" = - let - x via Proof.data - y via Proof.data - in - do - assert (operation x y == operation y x) - - prop "transitive" = - let - x via Proof.data - y via Proof.data - z via Proof.data - in - do - assert - ( - if operation x y && operation y z then - operation x z - else - True - ) - - test "detects wrong opcode argument order or signedness" = - do - assert - ( - not - ( - operation - (Primitive.coerce (Builtin.iData 0)) - (Primitive.coerce (Builtin.bData #"00")) - ) - ) diff --git a/proofs/builtins/EqualsInteger.nash b/proofs/builtins/EqualsInteger.nash deleted file mode 100644 index c355cb19..00000000 --- a/proofs/builtins/EqualsInteger.nash +++ /dev/null @@ -1,51 +0,0 @@ -module EqualsInteger exposing (..) - - -import Builtin - - -operation p0 p1 = Builtin.equalsInteger p0 p1 - - -proof - import Proof - - prop "reflexive" = - let - x via Proof.int - in - do - assert (operation x x) - - prop "symmetric" = - let - x via Proof.int - y via Proof.int - in - do - assert (operation x y == operation y x) - - prop "equivalent to zero difference" = - let - x via Proof.int - y via Proof.int - in - do - assert - ( - operation x y == - Builtin.equalsInteger - (Builtin.subtractInteger x y) - 0 - ) - - prop "successive integers are distinct at every width" = - let - x via Proof.int - in - do - assert (not (operation x (Builtin.addInteger x 1))) - - test "detects wrong opcode argument order or signedness" = - do - assert (not (operation (-1) 1)) diff --git a/proofs/builtins/EqualsString.nash b/proofs/builtins/EqualsString.nash deleted file mode 100644 index 92472856..00000000 --- a/proofs/builtins/EqualsString.nash +++ /dev/null @@ -1,45 +0,0 @@ -module EqualsString exposing (..) - - -import Builtin - - -operation p0 p1 = Builtin.equalsString p0 p1 - - -proof - import Proof - - prop "reflexive" = - let - x via Proof.string - in - do - assert (operation x x) - - prop "symmetric" = - let - x via Proof.string - y via Proof.string - in - do - assert (operation x y == operation y x) - - prop "transitive" = - let - x via Proof.string - y via Proof.string - z via Proof.string - in - do - assert - ( - if operation x y && operation y z then - operation x z - else - True - ) - - test "detects wrong opcode argument order or signedness" = - do - assert (not (operation "é" "e") && not (operation "a" "ab")) diff --git a/proofs/builtins/ExpModInteger.nash b/proofs/builtins/ExpModInteger.nash deleted file mode 100644 index 36d01537..00000000 --- a/proofs/builtins/ExpModInteger.nash +++ /dev/null @@ -1,47 +0,0 @@ -module ExpModInteger exposing (..) - - -import Builtin - - -operation p0 p1 p2 = Builtin.expModInteger p0 p1 p2 - - -proof - import Proof - - prop "exponent zero gives the modular identity" = - let - x via Proof.int - in - do - assert (operation x 0 7 == 1) - - test "known modular power" = - do - assert (operation 2 10 7 == 2) - - test "zero modulus rejects" fail = - do - let - _ = operation 2 10 0 - in - () - - test "negative modulus rejects" fail = - do - let - _ = operation 2 3 (-7) - in - () - - test "missing modular inverse rejects" fail = - do - let - _ = operation 2 (-1) 4 - in - () - - test "negative exponent uses modular inverse" = - do - assert (operation 2 (-1) 7 == 4) diff --git a/proofs/builtins/FindFirstSetBit.nash b/proofs/builtins/FindFirstSetBit.nash deleted file mode 100644 index d5fe0b34..00000000 --- a/proofs/builtins/FindFirstSetBit.nash +++ /dev/null @@ -1,30 +0,0 @@ -module FindFirstSetBit exposing (..) - - -import Builtin - - -operation p0 = Builtin.findFirstSetBit p0 - - -proof - import Proof - - prop "minus one exactly for all zero bits" = - let - x via Proof.bytes - in - do - assert ((operation x == -1) == (Builtin.countSetBits x == 0)) - - test "least significant indexing" = - do - assert - ( - operation #"80" == - 7 && - operation #"01" == - 0 && - operation #"00" == - -1 - ) diff --git a/proofs/builtins/FstPair.nash b/proofs/builtins/FstPair.nash deleted file mode 100644 index ca279a5f..00000000 --- a/proofs/builtins/FstPair.nash +++ /dev/null @@ -1,19 +0,0 @@ -module FstPair exposing (..) - - -import Builtin - - -operation p0 = Builtin.fstPair p0 - - -proof - import Proof - - prop "projects the correct component" = - let - x via Proof.data - y via Proof.data - in - do - assert (operation (Builtin.mkPairData x y) == x) diff --git a/proofs/builtins/HeadList.nash b/proofs/builtins/HeadList.nash deleted file mode 100644 index e66197fd..00000000 --- a/proofs/builtins/HeadList.nash +++ /dev/null @@ -1,27 +0,0 @@ -module HeadList exposing (..) - - -import Builtin - - -operation : list Data -> Data -operation p0 = Builtin.headList p0 - - -proof - import Proof - - prop "head of a nonempty list" = - let - x via Proof.data - y via Proof.data - in - do - assert (operation [ x, y ] == x) - - test "empty list rejects" fail = - do - let - _ = operation [] - in - () diff --git a/proofs/builtins/IData.nash b/proofs/builtins/IData.nash deleted file mode 100644 index 3329d0a7..00000000 --- a/proofs/builtins/IData.nash +++ /dev/null @@ -1,18 +0,0 @@ -module IData exposing (..) - - -import Builtin - - -operation p0 = Builtin.iData p0 - - -proof - import Proof - - prop "integer roundtrip has no width bound" = - let - x via Proof.int - in - do - assert (Builtin.unIData (operation x) == x) diff --git a/proofs/builtins/IfThenElse.nash b/proofs/builtins/IfThenElse.nash deleted file mode 100644 index bf86d835..00000000 --- a/proofs/builtins/IfThenElse.nash +++ /dev/null @@ -1,62 +0,0 @@ -module IfThenElse exposing (..) - - -import Builtin - - -operation p0 p1 p2 = Builtin.ifThenElse p0 p1 p2 - - -badArgument : int -badArgument = fail - - -proof - import Proof - - prop "selects the branch indicated by the condition" = - let - condition via Proof.bool - x via Proof.int - y via Proof.int - in - do - assert - ( - operation condition x y == - ( - if condition then - x - else - y - ) - ) - - prop "selects arbitrary Data values" = - let - condition via Proof.bool - x via Proof.data - y via Proof.data - in - do - assert - ( - operation condition x y == - ( - if condition then - x - else - y - ) - ) - - test "unselected arguments are still evaluated" fail = - do - let - _ = operation True 1 badArgument - in - () - - test "detects wrong opcode argument order or signedness" = - do - assert (operation True 17 5 == 17 && operation False 17 5 == 5) diff --git a/proofs/builtins/IndexArray.nash b/proofs/builtins/IndexArray.nash deleted file mode 100644 index 3e6a003f..00000000 --- a/proofs/builtins/IndexArray.nash +++ /dev/null @@ -1,66 +0,0 @@ -module IndexArray exposing (..) - - -import Builtin - - -{-| The pinned PlutusCoreBlaster revision has no array/value model. These are -executable source obligations; Lean verification is pending model support. -} -operation p0 p1 = Builtin.indexArray p0 p1 - - -one : array int -one = Builtin.listToArray [ 1 ] - - -proof - import Proof - - prop "selects each position" = - let - x via Proof.data - y via Proof.data - in - do - assert - ( - operation (Builtin.listToArray [ x, y ]) 0 == - x && - operation (Builtin.listToArray [ x, y ]) 1 == - y - ) - - test "negative index rejects" fail = - do - let - _ = operation one (-1) - in - () - - test "index equal to length rejects" fail = - do - let - _ = operation one 1 - in - () - - test "index must not truncate to a machine word" fail = - do - let - _ = operation one 18446744073709551616 - in - () - - test "index above 128 bits rejects without host overflow" fail = - do - let - _ = - operation - one - ( - Builtin.byteStringToInteger - True - #"0100000000000000000000000000000000" - ) - in - () diff --git a/proofs/builtins/IndexByteString.nash b/proofs/builtins/IndexByteString.nash deleted file mode 100644 index 25799ce3..00000000 --- a/proofs/builtins/IndexByteString.nash +++ /dev/null @@ -1,73 +0,0 @@ -module IndexByteString exposing (..) - - -import Builtin - - -operation p0 p1 = Builtin.indexByteString p0 p1 - - -proof - import Proof - - prop "valid indices return a byte" = - let - x via Proof.bytes - index via Proof.int - in - do - assert - ( - if index >= 0 && index < Builtin.lengthOfByteString x then - operation x index >= 0 && operation x index <= 255 - else - True - ) - - prop "cons supplies index zero" = - let - x via Proof.bytes - in - do - assert (operation (Builtin.consByteString 255 x) 0 == 255) - - prop "negative index rejects" fail = - let - x via Proof.bytes - in - do - let - _ = operation x (-1) - in - () - - prop "index equal to length rejects" fail = - let - x via Proof.bytes - in - do - let - _ = operation x (Builtin.lengthOfByteString x) - in - () - - test "index must not truncate to a machine word" fail = - do - let - _ = operation #"ff" 18446744073709551616 - in - () - - test "index above 128 bits rejects without host overflow" fail = - do - let - _ = - operation - #"ff" - ( - Builtin.byteStringToInteger - True - #"0100000000000000000000000000000000" - ) - in - () diff --git a/proofs/builtins/InsertCoin.nash b/proofs/builtins/InsertCoin.nash deleted file mode 100644 index da0302aa..00000000 --- a/proofs/builtins/InsertCoin.nash +++ /dev/null @@ -1,106 +0,0 @@ -module InsertCoin exposing (..) - - -import Builtin -import Cardano.Value - - -{-| The pinned PlutusCoreBlaster revision has no array/value model. These are -executable source obligations; Lean verification is pending model support. -} -operation p0 p1 p2 p3 = Builtin.insertCoin p0 p1 p2 p3 - - -empty : value -empty = Cardano.Value.empty - - -coin : int -> value -coin amount = Builtin.insertCoin #"" #"" amount empty - - -proof - import Proof - - prop "insert then lookup" = - let - amount via Proof.int - in - do - assert - ( - if amount >= 0 && amount <= 1000000 then - Builtin.lookupCoin - #"" - #"" - (operation #"" #"" amount empty) == - amount - else - True - ) - - test "zero quantity removes the coin" = - do - assert - (Builtin.lookupCoin #"" #"" (operation #"" #"" 0 (coin 5)) == 0) - - test "nonzero quantity rejects currency ID above 32 bytes" fail = - do - let - _ = - operation - #"000000000000000000000000000000000000000000000000000000000000000000" - #"" - 1 - empty - in - () - - test "nonzero quantity rejects token name above 32 bytes" fail = - do - let - _ = - operation - #"" - #"000000000000000000000000000000000000000000000000000000000000000000" - 1 - empty - in - () - - test "positive quantity above signed 128 bits rejects" fail = - do - let - _ = - operation - #"" - #"" - ( - Builtin.byteStringToInteger - True - #"80000000000000000000000000000000" - ) - empty - in - () - - test "zero quantity accepts long identifiers" = - do - assert - ( - Builtin.lookupCoin - #"" - #"" - ( - operation - #"000000000000000000000000000000000000000000000000000000000000000000" - #"000000000000000000000000000000000000000000000000000000000000000000" - 0 - (coin 5) - ) == - 5 - ) - - test "reinsertion replaces rather than adds" = - do - assert - (Builtin.lookupCoin #"" #"" (operation #"" #"" 7 (coin 5)) == 7) diff --git a/proofs/builtins/IntegerToByteString.nash b/proofs/builtins/IntegerToByteString.nash deleted file mode 100644 index b671e43e..00000000 --- a/proofs/builtins/IntegerToByteString.nash +++ /dev/null @@ -1,73 +0,0 @@ -module IntegerToByteString exposing (..) - - -import Builtin - - -operation p0 p1 p2 = Builtin.integerToByteString p0 p1 p2 - - -proof - import Proof - - test "zero with minimal width is empty" = - do - assert (operation True 0 0 == #"" && operation False 0 0 == #"") - - test "endianness and leading padding" = - do - assert - ( - operation True 3 258 == - #"000102" && - operation False 3 258 == - #"020100" - ) - - prop "unsigned byte roundtrip" = - let - n via Proof.int - in - do - assert - ( - if n >= 0 && n < 256 then - Builtin.byteStringToInteger - True - (operation True 1 n) == - n - else - True - ) - - test "negative integer rejects" fail = - do - let - _ = operation True 0 (-1) - in - () - - test "insufficient width rejects" fail = - do - let - _ = operation True 1 256 - in - () - - test "negative width rejects" fail = - do - let - _ = operation True (-1) 1 - in - () - - test "width above 8192 rejects" fail = - do - let - _ = operation True 8193 1 - in - () - - test "largest supported width is accepted" = - do - assert (Builtin.lengthOfByteString (operation True 8192 1) == 8192) diff --git a/proofs/builtins/Keccak256.nash b/proofs/builtins/Keccak256.nash deleted file mode 100644 index 75b2d756..00000000 --- a/proofs/builtins/Keccak256.nash +++ /dev/null @@ -1,19 +0,0 @@ -module Keccak256 exposing (..) - - -import Builtin - - -{-| Digest length is the semantic obligation here. This does not claim collision resistance or correctness of the underlying cryptographic implementation. The pinned Lean model exposes an opaque hash without length lemmas; this obligation is pending there. -} -operation p0 = Builtin.keccak_256 p0 - - -proof - import Proof - - prop "digest has the specified byte length" = - let - message via Proof.bytes - in - do - assert (Builtin.lengthOfByteString (operation message) == 32) diff --git a/proofs/builtins/LengthOfArray.nash b/proofs/builtins/LengthOfArray.nash deleted file mode 100644 index 068301a5..00000000 --- a/proofs/builtins/LengthOfArray.nash +++ /dev/null @@ -1,26 +0,0 @@ -module LengthOfArray exposing (..) - - -import Builtin - - -{-| The pinned PlutusCoreBlaster revision has no array/value model. These are -executable source obligations; Lean verification is pending model support. -} -operation : array Data -> int -operation p0 = Builtin.lengthOfArray p0 - - -proof - import Proof - - test "empty array length" = - do - assert (operation (Builtin.listToArray []) == 0) - - prop "length after list conversion" = - let - x via Proof.data - y via Proof.data - in - do - assert (operation (Builtin.listToArray [ x, y ]) == 2) diff --git a/proofs/builtins/LengthOfByteString.nash b/proofs/builtins/LengthOfByteString.nash deleted file mode 100644 index c378a9d5..00000000 --- a/proofs/builtins/LengthOfByteString.nash +++ /dev/null @@ -1,38 +0,0 @@ -module LengthOfByteString exposing (..) - - -import Builtin - - -operation p0 = Builtin.lengthOfByteString p0 - - -proof - import Proof - - prop "nonnegative" = - let - x via Proof.bytes - in - do - assert (operation x >= 0) - - test "empty length is zero" = - do - assert (operation #"" == 0) - - prop "append adds lengths" = - let - x via Proof.bytes - y via Proof.bytes - in - do - assert - ( - operation (Builtin.appendByteString x y) == - Builtin.addInteger (operation x) (operation y) - ) - - test "bytes are counted rather than UTF8 codepoints" = - do - assert (operation #"00c3a9ff" == 4) diff --git a/proofs/builtins/LessThanByteString.nash b/proofs/builtins/LessThanByteString.nash deleted file mode 100644 index f901770f..00000000 --- a/proofs/builtins/LessThanByteString.nash +++ /dev/null @@ -1,30 +0,0 @@ -module LessThanByteString exposing (..) - - -import Builtin - - -operation p0 p1 = Builtin.lessThanByteString p0 p1 - - -proof - import Proof - - prop "irreflexive" = - let - x via Proof.bytes - in - do - assert (not (operation x x)) - - prop "asymmetric" = - let - x via Proof.bytes - y via Proof.bytes - in - do - assert (not (operation x y && operation y x)) - - test "unsigned lexicographic ordering" = - do - assert (operation #"00ff" #"0100" && not (operation #"ff" #"00")) diff --git a/proofs/builtins/LessThanEqualsByteString.nash b/proofs/builtins/LessThanEqualsByteString.nash deleted file mode 100644 index 3c6fbc00..00000000 --- a/proofs/builtins/LessThanEqualsByteString.nash +++ /dev/null @@ -1,33 +0,0 @@ -module LessThanEqualsByteString exposing (..) - - -import Builtin - - -operation p0 p1 = Builtin.lessThanEqualsByteString p0 p1 - - -proof - import Proof - - prop "reflexive" = - let - x via Proof.bytes - in - do - assert (operation x x) - - prop "agrees with strict order or equality" = - let - x via Proof.bytes - y via Proof.bytes - in - do - assert - ( - operation x y == - ( - Builtin.lessThanByteString x y || - Builtin.equalsByteString x y - ) - ) diff --git a/proofs/builtins/LessThanEqualsInteger.nash b/proofs/builtins/LessThanEqualsInteger.nash deleted file mode 100644 index ddacbeac..00000000 --- a/proofs/builtins/LessThanEqualsInteger.nash +++ /dev/null @@ -1,51 +0,0 @@ -module LessThanEqualsInteger exposing (..) - - -import Builtin - - -operation p0 p1 = Builtin.lessThanEqualsInteger p0 p1 - - -proof - import Proof - - prop "reflexive" = - let - x via Proof.int - in - do - assert (operation x x) - - prop "antisymmetric" = - let - x via Proof.int - y via Proof.int - in - do - assert - ( - if operation x y && operation y x then - Builtin.equalsInteger x y - else - True - ) - - prop "agrees with strict order or equality" = - let - x via Proof.int - y via Proof.int - in - do - assert - ( - operation x y == - ( - Builtin.lessThanInteger x y || - Builtin.equalsInteger x y - ) - ) - - test "detects wrong opcode argument order or signedness" = - do - assert (operation (-1) 0 && not (operation 0 (-1))) diff --git a/proofs/builtins/LessThanInteger.nash b/proofs/builtins/LessThanInteger.nash deleted file mode 100644 index 6c12aef7..00000000 --- a/proofs/builtins/LessThanInteger.nash +++ /dev/null @@ -1,52 +0,0 @@ -module LessThanInteger exposing (..) - - -import Builtin - - -operation p0 p1 = Builtin.lessThanInteger p0 p1 - - -proof - import Proof - - prop "irreflexive" = - let - x via Proof.int - in - do - assert (not (operation x x)) - - prop "strict successor order at every width" = - let - x via Proof.int - in - do - assert (operation x (Builtin.addInteger x 1)) - - prop "asymmetric" = - let - x via Proof.int - y via Proof.int - in - do - assert (not (operation x y && operation y x)) - - prop "transitive" = - let - x via Proof.int - y via Proof.int - z via Proof.int - in - do - assert - ( - if operation x y && operation y z then - operation x z - else - True - ) - - test "detects wrong opcode argument order or signedness" = - do - assert (operation (-1) 0 && not (operation 0 (-1))) diff --git a/proofs/builtins/ListData.nash b/proofs/builtins/ListData.nash deleted file mode 100644 index 873f7e39..00000000 --- a/proofs/builtins/ListData.nash +++ /dev/null @@ -1,33 +0,0 @@ -module ListData exposing (..) - - -import Builtin - - -operation p0 = Builtin.listData p0 - - -proof - import Proof - - prop "list elements survive roundtrip" = - let - x via Proof.data - y via Proof.data - in - do - assert - ( - Builtin.headList - (Builtin.unListData (operation [ x, y ])) == - x && - Builtin.headList - ( - Builtin.tailList - ( - Builtin.unListData - (operation [ x, y ]) - ) - ) == - y - ) diff --git a/proofs/builtins/ListToArray.nash b/proofs/builtins/ListToArray.nash deleted file mode 100644 index 7ec90be8..00000000 --- a/proofs/builtins/ListToArray.nash +++ /dev/null @@ -1,27 +0,0 @@ -module ListToArray exposing (..) - - -import Builtin - - -{-| The pinned PlutusCoreBlaster revision has no array/value model. These are -executable source obligations; Lean verification is pending model support. -} -operation p0 = Builtin.listToArray p0 - - -proof - import Proof - - prop "preserves order" = - let - x via Proof.data - y via Proof.data - in - do - assert - ( - Builtin.indexArray (operation [ x, y ]) 0 == - x && - Builtin.indexArray (operation [ x, y ]) 1 == - y - ) diff --git a/proofs/builtins/LookupCoin.nash b/proofs/builtins/LookupCoin.nash deleted file mode 100644 index a55e10e8..00000000 --- a/proofs/builtins/LookupCoin.nash +++ /dev/null @@ -1,39 +0,0 @@ -module LookupCoin exposing (..) - - -import Builtin -import Cardano.Value - - -{-| The pinned PlutusCoreBlaster revision has no array/value model. These are -executable source obligations; Lean verification is pending model support. -} -operation p0 p1 p2 = Builtin.lookupCoin p0 p1 p2 - - -empty : value -empty = Cardano.Value.empty - - -coin : int -> value -coin amount = Builtin.insertCoin #"" #"" amount empty - - -proof - test "absent asset has zero quantity" = - do - assert (operation #"" #"" empty == 0) - - test "returns the inserted quantity" = - do - assert (operation #"" #"" (coin 5) == 5) - - test "long nonexistent identifier gives zero" = - do - assert - ( - operation - #"000000000000000000000000000000000000000000000000000000000000000000" - #"" - (coin 5) == - 0 - ) diff --git a/proofs/builtins/MapData.nash b/proofs/builtins/MapData.nash deleted file mode 100644 index f882a027..00000000 --- a/proofs/builtins/MapData.nash +++ /dev/null @@ -1,46 +0,0 @@ -module MapData exposing (..) - - -import Builtin - - -operation p0 = Builtin.mapData p0 - - -proof - import Proof - - prop "map entry survives roundtrip" = - let - x via Proof.data - y via Proof.data - in - do - assert - ( - Builtin.fstPair - ( - Builtin.headList - ( - Builtin.unMapData - ( - operation - [ Builtin.mkPairData x y ] - ) - ) - ) == - x && - Builtin.sndPair - ( - Builtin.headList - ( - Builtin.unMapData - ( - operation - [ Builtin.mkPairData x y - ] - ) - ) - ) == - y - ) diff --git a/proofs/builtins/MkCons.nash b/proofs/builtins/MkCons.nash deleted file mode 100644 index 1bb137d8..00000000 --- a/proofs/builtins/MkCons.nash +++ /dev/null @@ -1,43 +0,0 @@ -module MkCons exposing (..) - - -import Builtin - - -operation p0 p1 = Builtin.mkCons p0 p1 - - -proof - import Proof - - prop "head of cons is its element" = - let - x via Proof.data - y via Proof.data - in - do - assert (Builtin.headList (operation x [ y ]) == x) - - prop "tail of cons is its original list" = - let - x via Proof.data - y via Proof.data - in - do - assert - ( - Builtin.headList (Builtin.tailList (operation x [ y ])) == - y && - Builtin.nullList - ( - Builtin.tailList - (Builtin.tailList (operation x [ y ])) - ) - ) - - prop "cons is never empty" = - let - x via Proof.data - in - do - assert (not (Builtin.nullList (operation x []))) diff --git a/proofs/builtins/MkNilData.nash b/proofs/builtins/MkNilData.nash deleted file mode 100644 index 74d74250..00000000 --- a/proofs/builtins/MkNilData.nash +++ /dev/null @@ -1,20 +0,0 @@ -module MkNilData exposing (..) - - -import Builtin - - -operation p0 = Builtin.mkNilData p0 - - -proof - test "produces the empty list" = - do - assert (Builtin.nullList (operation ())) - - test "empty result has no head" fail = - do - let - _ = Builtin.headList (operation ()) - in - () diff --git a/proofs/builtins/MkNilPairData.nash b/proofs/builtins/MkNilPairData.nash deleted file mode 100644 index 44d3d9c1..00000000 --- a/proofs/builtins/MkNilPairData.nash +++ /dev/null @@ -1,20 +0,0 @@ -module MkNilPairData exposing (..) - - -import Builtin - - -operation p0 = Builtin.mkNilPairData p0 - - -proof - test "produces the empty list" = - do - assert (Builtin.nullList (operation ())) - - test "empty result has no head" fail = - do - let - _ = Builtin.headList (operation ()) - in - () diff --git a/proofs/builtins/MkPairData.nash b/proofs/builtins/MkPairData.nash deleted file mode 100644 index ebe964c9..00000000 --- a/proofs/builtins/MkPairData.nash +++ /dev/null @@ -1,25 +0,0 @@ -module MkPairData exposing (..) - - -import Builtin - - -operation p0 p1 = Builtin.mkPairData p0 p1 - - -proof - import Proof - - prop "both projections recover their arguments" = - let - x via Proof.data - y via Proof.data - in - do - assert - ( - Builtin.fstPair (operation x y) == - x && - Builtin.sndPair (operation x y) == - y - ) diff --git a/proofs/builtins/ModInteger.nash b/proofs/builtins/ModInteger.nash deleted file mode 100644 index 32ee3d86..00000000 --- a/proofs/builtins/ModInteger.nash +++ /dev/null @@ -1,71 +0,0 @@ -module ModInteger exposing (..) - - -import Builtin - - -operation p0 p1 = Builtin.modInteger p0 p1 - - -proof - import Proof - - prop "remainder modulo one is zero" = - let - x via Proof.int - in - do - assert (operation x 1 == 0) - - prop "division reconstruction" = - let - x via Proof.int - y via Proof.int - in - do - assert - ( - if y == 0 then - True - else - Builtin.addInteger - ( - Builtin.multiplyInteger - (Builtin.divideInteger x y) - y - ) - (operation x y) == - x - ) - - prop "positive modulus bounds the remainder" = - let - x via Proof.int - y via Proof.int - in - do - assert - ( - if y > 0 then - operation x y >= 0 && operation x y < y - else - True - ) - - test "negative rounding convention" = - do - assert (operation (-7) 3 == 2) - - prop "zero divisor rejects every dividend" fail = - let - x via Proof.int - in - do - let - _ = operation x 0 - in - () - - test "detects wrong opcode argument order or signedness" = - do - assert (operation 7 (-3) == -2) diff --git a/proofs/builtins/MultiplyInteger.nash b/proofs/builtins/MultiplyInteger.nash deleted file mode 100644 index 623f1257..00000000 --- a/proofs/builtins/MultiplyInteger.nash +++ /dev/null @@ -1,71 +0,0 @@ -module MultiplyInteger exposing (..) - - -import Builtin - - -operation p0 p1 = Builtin.multiplyInteger p0 p1 - - -proof - import Proof - - prop "commutative" = - let - x via Proof.int - y via Proof.int - in - do - assert (operation x y == operation y x) - - prop "associative" = - let - x via Proof.int - y via Proof.int - z via Proof.int - in - do - assert - (operation (operation x y) z == operation x (operation y z)) - - prop "distributive over addition" = - let - x via Proof.int - y via Proof.int - z via Proof.int - in - do - assert - ( - operation x (Builtin.addInteger y z) == - Builtin.addInteger (operation x y) (operation x z) - ) - - prop "multiplicative identity" = - let - x via Proof.int - in - do - assert (operation x 1 == x && operation 1 x == x) - - prop "zero annihilates" = - let - x via Proof.int - in - do - assert (operation x 0 == 0) - - prop "square is nonnegative without machine overflow" = - let - x via Proof.int - in - do - assert (operation x x >= 0) - - test "crosses signed 64-bit boundary" = - do - assert (operation 4294967296 4294967296 == 18446744073709551616) - - test "detects wrong opcode argument order or signedness" = - do - assert (operation (-4294967296) 4294967296 == -18446744073709551616) diff --git a/proofs/builtins/NullList.nash b/proofs/builtins/NullList.nash deleted file mode 100644 index 4742326a..00000000 --- a/proofs/builtins/NullList.nash +++ /dev/null @@ -1,23 +0,0 @@ -module NullList exposing (..) - - -import Builtin - - -operation : list Data -> bool -operation p0 = Builtin.nullList p0 - - -proof - import Proof - - test "empty list is null" = - do - assert (operation []) - - prop "singleton list is not null" = - let - x via Proof.data - in - do - assert (not (operation [ x ])) diff --git a/proofs/builtins/OrByteString.nash b/proofs/builtins/OrByteString.nash deleted file mode 100644 index d0b309ad..00000000 --- a/proofs/builtins/OrByteString.nash +++ /dev/null @@ -1,35 +0,0 @@ -module OrByteString exposing (..) - - -import Builtin - - -operation p0 p1 p2 = Builtin.orByteString p0 p1 p2 - - -proof - import Proof - - prop "commutative for both padding modes" = - let - pad via Proof.bool - x via Proof.bytes - y via Proof.bytes - in - do - assert (operation pad x y == operation pad y x) - - prop "self law" = - let - pad via Proof.bool - x via Proof.bytes - in - do - assert (operation pad x x == x) - - prop "truncating mode with empty input" = - let - x via Proof.bytes - in - do - assert (operation False x #"" == #"") diff --git a/proofs/builtins/QuotientInteger.nash b/proofs/builtins/QuotientInteger.nash deleted file mode 100644 index 35bc9bd8..00000000 --- a/proofs/builtins/QuotientInteger.nash +++ /dev/null @@ -1,54 +0,0 @@ -module QuotientInteger exposing (..) - - -import Builtin - - -operation p0 p1 = Builtin.quotientInteger p0 p1 - - -proof - import Proof - - prop "division by one" = - let - x via Proof.int - in - do - assert (operation x 1 == x) - - prop "quotient and remainder reconstruct the dividend" = - let - x via Proof.int - y via Proof.int - in - do - assert - ( - if y == 0 then - True - else - Builtin.addInteger - (Builtin.multiplyInteger (operation x y) y) - (Builtin.remainderInteger x y) == - x - ) - - test "negative rounding convention" = - do - assert (operation (-7) 3 == -2) - - prop "zero divisor rejects every dividend" fail = - let - x via Proof.int - in - do - let - _ = operation x 0 - in - () - - test "detects wrong opcode argument order or signedness" = - do - assert - (operation (-9223372036854775808) (-1) == 9223372036854775808) diff --git a/proofs/builtins/ReadBit.nash b/proofs/builtins/ReadBit.nash deleted file mode 100644 index c394f79b..00000000 --- a/proofs/builtins/ReadBit.nash +++ /dev/null @@ -1,56 +0,0 @@ -module ReadBit exposing (..) - - -import Builtin - - -operation p0 p1 = Builtin.readBit p0 p1 - - -proof - import Proof - - test "least significant bit indexing" = - do - assert (operation #"01" 0 && not (operation #"01" 7)) - - prop "negative bit index rejects" fail = - let - x via Proof.bytes - in - do - let - _ = operation x (-1) - in - () - - prop "index equal to bit count rejects" fail = - let - x via Proof.bytes - in - do - let - _ = - operation - x - ( - Builtin.multiplyInteger - 8 - (Builtin.lengthOfByteString x) - ) - in - () - - test "index above 128 bits rejects without host overflow" fail = - do - let - _ = - operation - #"ff" - ( - Builtin.byteStringToInteger - True - #"0100000000000000000000000000000000" - ) - in - () diff --git a/proofs/builtins/RemainderInteger.nash b/proofs/builtins/RemainderInteger.nash deleted file mode 100644 index 2196af89..00000000 --- a/proofs/builtins/RemainderInteger.nash +++ /dev/null @@ -1,73 +0,0 @@ -module RemainderInteger exposing (..) - - -import Builtin - - -operation p0 p1 = Builtin.remainderInteger p0 p1 - - -proof - import Proof - - prop "remainder modulo one is zero" = - let - x via Proof.int - in - do - assert (operation x 1 == 0) - - prop "division reconstruction" = - let - x via Proof.int - y via Proof.int - in - do - assert - ( - if y == 0 then - True - else - Builtin.addInteger - ( - Builtin.multiplyInteger - (Builtin.quotientInteger x y) - y - ) - (operation x y) == - x - ) - - prop "remainder has the dividend sign" = - let - x via Proof.int - y via Proof.int - in - do - assert - ( - if y == 0 then - True - else if x >= 0 then - operation x y >= 0 - else - operation x y <= 0 - ) - - test "negative rounding convention" = - do - assert (operation (-7) 3 == -1) - - prop "zero divisor rejects every dividend" fail = - let - x via Proof.int - in - do - let - _ = operation x 0 - in - () - - test "detects wrong opcode argument order or signedness" = - do - assert (operation 7 (-3) == 1) diff --git a/proofs/builtins/ReplicateByte.nash b/proofs/builtins/ReplicateByte.nash deleted file mode 100644 index 344750b7..00000000 --- a/proofs/builtins/ReplicateByte.nash +++ /dev/null @@ -1,49 +0,0 @@ -module ReplicateByte exposing (..) - - -import Builtin - - -operation p0 p1 = Builtin.replicateByte p0 p1 - - -proof - test "zero count produces empty bytes" = - do - assert (operation 0 255 == #"") - - test "repeats exactly the requested byte" = - do - assert (operation 3 128 == #"808080") - - test "invalid byte rejects" fail = - do - let - _ = operation 1 256 - in - () - - test "negative count rejects" fail = - do - let - _ = operation (-1) 0 - in - () - - test "negative byte rejects" fail = - do - let - _ = operation 1 (-1) - in - () - - test "count above 8192 rejects" fail = - do - let - _ = operation 8193 0 - in - () - - test "largest supported count is accepted" = - do - assert (Builtin.lengthOfByteString (operation 8192 0) == 8192) diff --git a/proofs/builtins/Ripemd160.nash b/proofs/builtins/Ripemd160.nash deleted file mode 100644 index 9e1ac834..00000000 --- a/proofs/builtins/Ripemd160.nash +++ /dev/null @@ -1,24 +0,0 @@ -module Ripemd160 exposing (..) - - -import Builtin - - -{-| Digest length is the semantic obligation here. This does not claim collision resistance or correctness of the underlying cryptographic implementation. The pinned Lean model exposes an opaque hash without length lemmas; this obligation is pending there. -} -operation p0 = Builtin.ripemd_160 p0 - - -proof - import Proof - - prop "digest has the specified byte length" = - let - message via Proof.bytes - in - do - assert (Builtin.lengthOfByteString (operation message) == 20) - - test "empty message known answer detects the wrong hash primitive" = - do - assert - (operation #"" == #"9c1185a5c5e9fc54612808977ee8f548b2258d31") diff --git a/proofs/builtins/RotateByteString.nash b/proofs/builtins/RotateByteString.nash deleted file mode 100644 index ab4c3a34..00000000 --- a/proofs/builtins/RotateByteString.nash +++ /dev/null @@ -1,61 +0,0 @@ -module RotateByteString exposing (..) - - -import Builtin - - -operation p0 p1 = Builtin.rotateByteString p0 p1 - - -proof - import Proof - - prop "zero rotation is identity" = - let - x via Proof.bytes - in - do - assert (operation x 0 == x) - - prop "rotation by width is identity" = - let - x via Proof.bytes - in - do - assert - ( - operation - x - ( - Builtin.multiplyInteger - 8 - (Builtin.lengthOfByteString x) - ) == - x - ) - - prop "inverse rotation" = - let - x via Proof.bytes - n via Proof.int - in - do - assert - ( - operation (operation x n) (Builtin.subtractInteger 0 n) == - x - ) - - test "large rotation is reduced by bit width" = - do - assert - ( - operation - #"01" - ( - Builtin.byteStringToInteger - True - #"0100000000000000000000000000000000" - ) == - #"01" - ) diff --git a/proofs/builtins/ScaleValue.nash b/proofs/builtins/ScaleValue.nash deleted file mode 100644 index 77eb9505..00000000 --- a/proofs/builtins/ScaleValue.nash +++ /dev/null @@ -1,53 +0,0 @@ -module ScaleValue exposing (..) - - -import Builtin -import Cardano.Value - - -{-| The pinned PlutusCoreBlaster revision has no array/value model. These are -executable source obligations; Lean verification is pending model support. -} -operation p0 p1 = Builtin.scaleValue p0 p1 - - -empty : value -empty = Cardano.Value.empty - - -coin : int -> value -coin amount = Builtin.insertCoin #"" #"" amount empty - - -proof - test "zero annihilates quantities" = - do - assert (Builtin.lookupCoin #"" #"" (operation 0 (coin 5)) == 0) - - test "multiplicative identity" = - do - assert (Builtin.lookupCoin #"" #"" (operation 1 (coin 5)) == 5) - - test "scales quantities" = - do - assert (Builtin.lookupCoin #"" #"" (operation 3 (coin 5)) == 15) - - test "quantity overflow rejects" fail = - do - let - _ = operation 2 (coin 170141183460469231731687303715884105727) - in - () - - test "out of bounds multiplier rejects" fail = - do - let - _ = - operation - ( - Builtin.byteStringToInteger - True - #"80000000000000000000000000000000" - ) - empty - in - () diff --git a/proofs/builtins/SerialiseData.nash b/proofs/builtins/SerialiseData.nash deleted file mode 100644 index 632d9ec7..00000000 --- a/proofs/builtins/SerialiseData.nash +++ /dev/null @@ -1,20 +0,0 @@ -module SerialiseData exposing (..) - - -import Builtin - - -{-| These are explicit CBOR examples, not a completeness theorem for serialization. -} -operation p0 = Builtin.serialiseData p0 - - -proof - test "canonical CBOR integer encoding" = - do - assert - ( - operation (Primitive.coerce (Builtin.iData 0)) == - #"00" && - operation (Primitive.coerce (Builtin.iData (-1))) == - #"20" - ) diff --git a/proofs/builtins/Sha2256.nash b/proofs/builtins/Sha2256.nash deleted file mode 100644 index 6d5b4328..00000000 --- a/proofs/builtins/Sha2256.nash +++ /dev/null @@ -1,27 +0,0 @@ -module Sha2256 exposing (..) - - -import Builtin - - -{-| Digest length is the semantic obligation here. This does not claim collision resistance or correctness of the underlying cryptographic implementation. The pinned Lean model exposes an opaque hash without length lemmas; this obligation is pending there. -} -operation p0 = Builtin.sha2_256 p0 - - -proof - import Proof - - prop "digest has the specified byte length" = - let - message via Proof.bytes - in - do - assert (Builtin.lengthOfByteString (operation message) == 32) - - test "empty message known answer detects the wrong hash primitive" = - do - assert - ( - operation #"" == - #"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" - ) diff --git a/proofs/builtins/Sha3256.nash b/proofs/builtins/Sha3256.nash deleted file mode 100644 index 3f2c12a6..00000000 --- a/proofs/builtins/Sha3256.nash +++ /dev/null @@ -1,27 +0,0 @@ -module Sha3256 exposing (..) - - -import Builtin - - -{-| Digest length is the semantic obligation here. This does not claim collision resistance or correctness of the underlying cryptographic implementation. The pinned Lean model exposes an opaque hash without length lemmas; this obligation is pending there. -} -operation p0 = Builtin.sha3_256 p0 - - -proof - import Proof - - prop "digest has the specified byte length" = - let - message via Proof.bytes - in - do - assert (Builtin.lengthOfByteString (operation message) == 32) - - test "empty message known answer detects the wrong hash primitive" = - do - assert - ( - operation #"" == - #"a7ffc6f8bf1ed76651c14756a061d662f580ff4de43b49fa82d80a4b80f8434a" - ) diff --git a/proofs/builtins/ShiftByteString.nash b/proofs/builtins/ShiftByteString.nash deleted file mode 100644 index e93f85a7..00000000 --- a/proofs/builtins/ShiftByteString.nash +++ /dev/null @@ -1,69 +0,0 @@ -module ShiftByteString exposing (..) - - -import Builtin - - -operation p0 p1 = Builtin.shiftByteString p0 p1 - - -proof - import Proof - - prop "zero shift is identity" = - let - x via Proof.bytes - in - do - assert (operation x 0 == x) - - test "direction and zero fill" = - do - assert (operation #"01" 1 == #"02" && operation #"80" (-1) == #"40") - - prop "shifting out every bit produces zero" = - let - x via Proof.bytes - in - do - assert - ( - Builtin.countSetBits - ( - operation - x - ( - Builtin.multiplyInteger - 8 - (Builtin.lengthOfByteString x) - ) - ) == - 0 - ) - - test "large shift does not truncate to a host word" = - do - assert - ( - Builtin.countSetBits - ( - operation - #"ff" - ( - Builtin.byteStringToInteger - True - #"0100000000000000000000000000000000" - ) - ) == - 0 - ) - - test "whole byte shifts preserve the remaining bytes" = - do - assert - ( - operation #"001122" 8 == - #"112200" && - operation #"001122" (-8) == - #"000011" - ) diff --git a/proofs/builtins/SliceByteString.nash b/proofs/builtins/SliceByteString.nash deleted file mode 100644 index 9b0648e2..00000000 --- a/proofs/builtins/SliceByteString.nash +++ /dev/null @@ -1,48 +0,0 @@ -module SliceByteString exposing (..) - - -import Builtin - - -operation p0 p1 p2 = Builtin.sliceByteString p0 p1 p2 - - -proof - import Proof - - prop "zero count produces empty bytes" = - let - x via Proof.bytes - offset via Proof.int - in - do - assert (operation offset 0 x == #"") - - prop "negative count produces empty bytes" = - let - x via Proof.bytes - in - do - assert (operation 0 (-1) x == #"") - - prop "whole slice preserves the bytes" = - let - x via Proof.bytes - in - do - assert (operation 0 (Builtin.lengthOfByteString x) x == x) - - test "negative offset is clamped independently of count" = - do - assert (operation (-2) 2 #"010203" == #"0102") - - prop "offset at the end gives empty bytes" = - let - x via Proof.bytes - in - do - assert (operation (Builtin.lengthOfByteString x) 1 x == #"") - - test "detects wrong opcode argument order or signedness" = - do - assert (operation 1 2 #"00010203" == #"0102") diff --git a/proofs/builtins/SndPair.nash b/proofs/builtins/SndPair.nash deleted file mode 100644 index 253b0c31..00000000 --- a/proofs/builtins/SndPair.nash +++ /dev/null @@ -1,19 +0,0 @@ -module SndPair exposing (..) - - -import Builtin - - -operation p0 = Builtin.sndPair p0 - - -proof - import Proof - - prop "projects the correct component" = - let - x via Proof.data - y via Proof.data - in - do - assert (operation (Builtin.mkPairData x y) == y) diff --git a/proofs/builtins/SubtractInteger.nash b/proofs/builtins/SubtractInteger.nash deleted file mode 100644 index b05ffc5c..00000000 --- a/proofs/builtins/SubtractInteger.nash +++ /dev/null @@ -1,48 +0,0 @@ -module SubtractInteger exposing (..) - - -import Builtin - - -operation p0 p1 = Builtin.subtractInteger p0 p1 - - -proof - import Proof - - prop "right identity" = - let - x via Proof.int - in - do - assert (operation x 0 == x) - - prop "self subtraction is zero" = - let - x via Proof.int - in - do - assert (operation x x == 0) - - prop "inverse of addition" = - let - x via Proof.int - y via Proof.int - in - do - assert (Builtin.addInteger (operation x y) y == x) - - prop "predecessor never wraps at any integer width" = - let - x via Proof.int - in - do - assert (operation x 1 < x) - - test "argument order" = - do - assert (operation 7 2 == 5 && operation 2 7 == -5) - - test "detects wrong opcode argument order or signedness" = - do - assert (operation (-9223372036854775808) 1 == -9223372036854775809) diff --git a/proofs/builtins/TailList.nash b/proofs/builtins/TailList.nash deleted file mode 100644 index 2423f74b..00000000 --- a/proofs/builtins/TailList.nash +++ /dev/null @@ -1,39 +0,0 @@ -module TailList exposing (..) - - -import Builtin - - -operation : list Data -> list Data -operation p0 = Builtin.tailList p0 - - -proof - import Proof - - prop "tail preserves subsequent elements" = - let - x via Proof.data - y via Proof.data - in - do - assert - ( - Builtin.headList (operation [ x, y ]) == - y && - Builtin.nullList (operation (operation [ x, y ])) - ) - - prop "singleton tail is empty" = - let - x via Proof.data - in - do - assert (Builtin.nullList (operation [ x ])) - - test "empty list rejects" fail = - do - let - _ = operation [] - in - () diff --git a/proofs/builtins/Trace.nash b/proofs/builtins/Trace.nash deleted file mode 100644 index 17600b26..00000000 --- a/proofs/builtins/Trace.nash +++ /dev/null @@ -1,24 +0,0 @@ -module Trace exposing (..) - - -import Builtin - - -{-| The returned value is specified here. Log contents are not observed by the proof backend. -} -operation p0 p1 = Builtin.trace p0 p1 - - -proof - import Proof - - prop "preserves the value" = - let - message via Proof.string - x via Proof.data - in - do - assert (operation message x == x) - - test "detects wrong opcode argument order or signedness" = - do - assert (operation "log" 17 == 17) diff --git a/proofs/builtins/UnBData.nash b/proofs/builtins/UnBData.nash deleted file mode 100644 index fae488da..00000000 --- a/proofs/builtins/UnBData.nash +++ /dev/null @@ -1,35 +0,0 @@ -module UnBData exposing (..) - - -import Builtin - - -operation p0 = Builtin.unBData p0 - - -proof - import Proof - - prop "unboxes every bytestring" = - let - x via Proof.bytes - in - do - assert (operation (Builtin.bData x) == x) - - prop "integer Data is not bytes Data" fail = - let - x via Proof.int - in - do - let - _ = operation (Primitive.coerce (Builtin.iData x)) - in - () - - test "constructor Data cannot be used as bytes" fail = - do - let - _ = operation (Primitive.coerce (Builtin.constrData 0 [])) - in - () diff --git a/proofs/builtins/UnConstrData.nash b/proofs/builtins/UnConstrData.nash deleted file mode 100644 index ee3f756f..00000000 --- a/proofs/builtins/UnConstrData.nash +++ /dev/null @@ -1,53 +0,0 @@ -module UnConstrData exposing (..) - - -import Builtin - - -operation p0 = Builtin.unConstrData p0 - - -proof - import Proof - - prop "recovers tag and first field" = - let - tag via Proof.int - x via Proof.data - in - do - assert - ( - Builtin.fstPair - (operation (Builtin.constrData tag [ x ])) == - tag && - Builtin.headList - ( - Builtin.sndPair - ( - operation - (Builtin.constrData tag [ x ]) - ) - ) == - x - ) - - prop "integer Data is not constructor Data" fail = - let - x via Proof.int - in - do - let - _ = operation (Primitive.coerce (Builtin.iData x)) - in - () - - prop "bytes Data cannot be used as a constructor" fail = - let - x via Proof.bytes - in - do - let - _ = operation (Primitive.coerce (Builtin.bData x)) - in - () diff --git a/proofs/builtins/UnIData.nash b/proofs/builtins/UnIData.nash deleted file mode 100644 index aa8c2614..00000000 --- a/proofs/builtins/UnIData.nash +++ /dev/null @@ -1,35 +0,0 @@ -module UnIData exposing (..) - - -import Builtin - - -operation p0 = Builtin.unIData p0 - - -proof - import Proof - - prop "unboxes every integer" = - let - x via Proof.int - in - do - assert (operation (Builtin.iData x) == x) - - prop "bytes Data is not integer Data" fail = - let - x via Proof.bytes - in - do - let - _ = operation (Primitive.coerce (Builtin.bData x)) - in - () - - test "constructor Data cannot be used as an integer" fail = - do - let - _ = operation (Primitive.coerce (Builtin.constrData 0 [])) - in - () diff --git a/proofs/builtins/UnListData.nash b/proofs/builtins/UnListData.nash deleted file mode 100644 index 0b474548..00000000 --- a/proofs/builtins/UnListData.nash +++ /dev/null @@ -1,47 +0,0 @@ -module UnListData exposing (..) - - -import Builtin - - -operation p0 = Builtin.unListData p0 - - -wrongShape : int -> List Data -wrongShape x = Primitive.coerce (Builtin.iData x) - - -wrongBytes : bytes -> List Data -wrongBytes x = Primitive.coerce (Builtin.bData x) - - -proof - import Proof - - prop "recovers the first element" = - let - x via Proof.data - in - do - assert - (Builtin.headList (operation (Builtin.listData [ x ])) == x) - - prop "integer Data is not list Data" fail = - let - x via Proof.int - in - do - let - _ = operation (wrongShape x) - in - () - - prop "bytes Data cannot be used as a list" fail = - let - x via Proof.bytes - in - do - let - _ = operation (wrongBytes x) - in - () diff --git a/proofs/builtins/UnMapData.nash b/proofs/builtins/UnMapData.nash deleted file mode 100644 index 25f5ced2..00000000 --- a/proofs/builtins/UnMapData.nash +++ /dev/null @@ -1,61 +0,0 @@ -module UnMapData exposing (..) - - -import Builtin - - -operation p0 = Builtin.unMapData p0 - - -wrongShape : int -> Map Data Data -wrongShape x = Primitive.coerce (Builtin.iData x) - - -wrongBytes : bytes -> Map Data Data -wrongBytes x = Primitive.coerce (Builtin.bData x) - - -proof - import Proof - - prop "recovers a map entry" = - let - x via Proof.data - y via Proof.data - in - do - assert - ( - Builtin.sndPair - ( - Builtin.headList - ( - operation - ( - Builtin.mapData - [ Builtin.mkPairData x y ] - ) - ) - ) == - y - ) - - prop "integer Data is not map Data" fail = - let - x via Proof.int - in - do - let - _ = operation (wrongShape x) - in - () - - prop "bytes Data cannot be used as a map" fail = - let - x via Proof.bytes - in - do - let - _ = operation (wrongBytes x) - in - () diff --git a/proofs/builtins/UnValueData.nash b/proofs/builtins/UnValueData.nash deleted file mode 100644 index ef29f0ee..00000000 --- a/proofs/builtins/UnValueData.nash +++ /dev/null @@ -1,139 +0,0 @@ -module UnValueData exposing (..) - - -import Builtin -import Cardano.Value - - -{-| The pinned PlutusCoreBlaster revision has no array/value model. These are -executable source obligations; Lean verification is pending model support. -} -operation p0 = Builtin.unValueData p0 - - -empty : value -empty = Cardano.Value.empty - - -coin : int -> value -coin amount = Builtin.insertCoin #"" #"" amount empty - - -emptyAssetMap : Map Bytes Int -emptyAssetMap = Builtin.mapData [] - - -emptyInner : Data -emptyInner = - Primitive.coerce - ( - Builtin.mapData - [ Builtin.mkPairData (Builtin.bData #"") emptyAssetMap ] - ) - - -zeroQuantity : Data -zeroQuantity = - Primitive.coerce - ( - Builtin.mapData - [ Builtin.mkPairData - (Builtin.bData #"") - ( - Builtin.mapData - [ Builtin.mkPairData - (Builtin.bData #"") - (Builtin.iData 0) - ] - ) - ] - ) - - -duplicateToken : Data -duplicateToken = - Primitive.coerce - ( - Builtin.mapData - [ Builtin.mkPairData - (Builtin.bData #"") - ( - Builtin.mapData - [ Builtin.mkPairData - (Builtin.bData #"") - (Builtin.iData 1) - , Builtin.mkPairData - (Builtin.bData #"") - (Builtin.iData 2) - ] - ) - ] - ) - - -unsortedTokens : Data -unsortedTokens = - Primitive.coerce - ( - Builtin.mapData - [ Builtin.mkPairData - (Builtin.bData #"") - ( - Builtin.mapData - [ Builtin.mkPairData - (Builtin.bData #"01") - (Builtin.iData 1) - , Builtin.mkPairData - (Builtin.bData #"00") - (Builtin.iData 2) - ] - ) - ] - ) - - -proof - test "decoding and encoding recover quantities" = - do - assert - ( - Builtin.lookupCoin - #"" - #"" - (operation (Builtin.valueData (coin 5))) == - 5 - ) - - test "integer Data is not a value" fail = - do - let - _ = operation (Primitive.coerce (Builtin.iData 5)) - in - () - - test "empty inner asset map rejects" fail = - do - let - _ = operation emptyInner - in - () - - test "zero encoded quantity rejects" fail = - do - let - _ = operation zeroQuantity - in - () - - test "duplicate asset names reject" fail = - do - let - _ = operation duplicateToken - in - () - - test "out of order asset names reject" fail = - do - let - _ = operation unsortedTokens - in - () diff --git a/proofs/builtins/UnionValue.nash b/proofs/builtins/UnionValue.nash deleted file mode 100644 index 6dc6383c..00000000 --- a/proofs/builtins/UnionValue.nash +++ /dev/null @@ -1,39 +0,0 @@ -module UnionValue exposing (..) - - -import Builtin -import Cardano.Value - - -{-| The pinned PlutusCoreBlaster revision has no array/value model. These are -executable source obligations; Lean verification is pending model support. -} -operation p0 p1 = Builtin.unionValue p0 p1 - - -empty : value -empty = Cardano.Value.empty - - -coin : int -> value -coin amount = Builtin.insertCoin #"" #"" amount empty - - -proof - test "empty value is the identity" = - do - assert (Builtin.lookupCoin #"" #"" (operation empty (coin 5)) == 5) - - test "adds the same asset quantities" = - do - assert - (Builtin.lookupCoin #"" #"" (operation (coin 5) (coin 7)) == 12) - - test "quantity overflow rejects" fail = - do - let - _ = - operation - (coin 170141183460469231731687303715884105727) - (coin 1) - in - () diff --git a/proofs/builtins/ValueContains.nash b/proofs/builtins/ValueContains.nash deleted file mode 100644 index 2420df9e..00000000 --- a/proofs/builtins/ValueContains.nash +++ /dev/null @@ -1,46 +0,0 @@ -module ValueContains exposing (..) - - -import Builtin -import Cardano.Value - - -{-| The pinned PlutusCoreBlaster revision has no array/value model. These are -executable source obligations; Lean verification is pending model support. -} -operation p0 p1 = Builtin.valueContains p0 p1 - - -empty : value -empty = Cardano.Value.empty - - -coin : int -> value -coin amount = Builtin.insertCoin #"" #"" amount empty - - -proof - test "nonnegative value contains itself" = - do - assert (operation (coin 5) (coin 5)) - - test "quantity containment" = - do - assert - ( - operation (coin 5) (coin 3) && - not (operation (coin 3) (coin 5)) - ) - - test "negative first quantity rejects" fail = - do - let - _ = operation (coin (-1)) (coin 1) - in - () - - test "negative second quantity rejects" fail = - do - let - _ = operation (coin 1) (coin (-1)) - in - () diff --git a/proofs/builtins/ValueData.nash b/proofs/builtins/ValueData.nash deleted file mode 100644 index 5df3f857..00000000 --- a/proofs/builtins/ValueData.nash +++ /dev/null @@ -1,31 +0,0 @@ -module ValueData exposing (..) - - -import Builtin -import Cardano.Value - - -{-| The pinned PlutusCoreBlaster revision has no array/value model. These are -executable source obligations; Lean verification is pending model support. -} -operation p0 = Builtin.valueData p0 - - -empty : value -empty = Cardano.Value.empty - - -coin : int -> value -coin amount = Builtin.insertCoin #"" #"" amount empty - - -proof - test "encoding and decoding recover quantities" = - do - assert - ( - Builtin.lookupCoin - #"" - #"" - (Builtin.unValueData (operation (coin 5))) == - 5 - ) diff --git a/proofs/builtins/VerifyEcdsaSecp256k1Signature.nash b/proofs/builtins/VerifyEcdsaSecp256k1Signature.nash deleted file mode 100644 index bde498ff..00000000 --- a/proofs/builtins/VerifyEcdsaSecp256k1Signature.nash +++ /dev/null @@ -1,33 +0,0 @@ -module VerifyEcdsaSecp256k1Signature exposing (..) - - -import Builtin - - -{-| Malformed-key rejection is specified explicitly. Signature soundness and completeness require cryptographic contracts absent from the pinned model. -} -operation p0 p1 p2 = Builtin.verifyEcdsaSecp256k1Signature p0 p1 p2 - - -proof - import Proof - - prop "empty public key rejects" fail = - let - message via Proof.bytes - in - do - let - _ = operation #"" message #"" - in - () - - test "valid key with truncated signature rejects" fail = - do - let - _ = - operation - #"0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" - #"0000000000000000000000000000000000000000000000000000000000000000" - #"00" - in - () diff --git a/proofs/builtins/VerifyEd25519Signature.nash b/proofs/builtins/VerifyEd25519Signature.nash deleted file mode 100644 index 0db615ba..00000000 --- a/proofs/builtins/VerifyEd25519Signature.nash +++ /dev/null @@ -1,56 +0,0 @@ -module VerifyEd25519Signature exposing (..) - - -import Builtin - - -{-| Malformed-key rejection is specified explicitly. Signature soundness and completeness require cryptographic contracts absent from the pinned model. -} -operation p0 p1 p2 = Builtin.verifyEd25519Signature p0 p1 p2 - - -proof - import Proof - - prop "empty public key rejects" fail = - let - message via Proof.bytes - in - do - let - _ = operation #"" message #"" - in - () - - test "valid key with truncated signature rejects" fail = - do - let - _ = - operation - #"d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a" - #"" - #"00" - in - () - - test "RFC8032 empty message signature verifies" = - do - assert - ( - operation - #"d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a" - #"" - #"e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b" - ) - - test "changed message returns False instead of rejecting" = - do - assert - ( - not - ( - operation - #"d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a" - #"00" - #"e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b" - ) - ) diff --git a/proofs/builtins/VerifySchnorrSecp256k1Signature.nash b/proofs/builtins/VerifySchnorrSecp256k1Signature.nash deleted file mode 100644 index c63b806c..00000000 --- a/proofs/builtins/VerifySchnorrSecp256k1Signature.nash +++ /dev/null @@ -1,33 +0,0 @@ -module VerifySchnorrSecp256k1Signature exposing (..) - - -import Builtin - - -{-| Malformed-key rejection is specified explicitly. Signature soundness and completeness require cryptographic contracts absent from the pinned model. -} -operation p0 p1 p2 = Builtin.verifySchnorrSecp256k1Signature p0 p1 p2 - - -proof - import Proof - - prop "empty public key rejects" fail = - let - message via Proof.bytes - in - do - let - _ = operation #"" message #"" - in - () - - test "valid key with truncated signature rejects" fail = - do - let - _ = - operation - #"79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" - #"" - #"00" - in - () diff --git a/proofs/builtins/WriteBits.nash b/proofs/builtins/WriteBits.nash deleted file mode 100644 index ed58761c..00000000 --- a/proofs/builtins/WriteBits.nash +++ /dev/null @@ -1,61 +0,0 @@ -module WriteBits exposing (..) - - -import Builtin - - -operation p0 p1 p2 = Builtin.writeBits p0 p1 p2 - - -proof - import Proof - - prop "write then read" = - let - bit via Proof.bool - in - do - assert (Builtin.readBit (operation #"00" [ 0 ] bit) 0 == bit) - - prop "empty index list is identity" = - let - x via Proof.bytes - bit via Proof.bool - in - do - assert (operation x [] bit == x) - - test "invalid index rejects" fail = - do - let - _ = operation #"00" [ 8 ] True - in - () - - test "index above 128 bits rejects without host overflow" fail = - do - let - _ = - operation - #"00" - [ Builtin.byteStringToInteger - True - #"0100000000000000000000000000000000" - ] - True - in - () - - test "negative index rejects" fail = - do - let - _ = operation #"00" [ -1 ] True - in - () - - test "all indices must be valid" fail = - do - let - _ = operation #"00" [ 0, 8 ] True - in - () diff --git a/proofs/builtins/XorByteString.nash b/proofs/builtins/XorByteString.nash deleted file mode 100644 index 37014c13..00000000 --- a/proofs/builtins/XorByteString.nash +++ /dev/null @@ -1,35 +0,0 @@ -module XorByteString exposing (..) - - -import Builtin - - -operation p0 p1 p2 = Builtin.xorByteString p0 p1 p2 - - -proof - import Proof - - prop "commutative for both padding modes" = - let - pad via Proof.bool - x via Proof.bytes - y via Proof.bytes - in - do - assert (operation pad x y == operation pad y x) - - prop "self law" = - let - pad via Proof.bool - x via Proof.bytes - in - do - assert (Builtin.countSetBits (operation pad x x) == 0) - - prop "truncating mode with empty input" = - let - x via Proof.bytes - in - do - assert (operation False x #"" == #"") diff --git a/proofs/nash.jsonc b/proofs/nash.jsonc deleted file mode 100644 index 60d26262..00000000 --- a/proofs/nash.jsonc +++ /dev/null @@ -1 +0,0 @@ -{"type":"application","sourceDirectories":["builtins"],"plutusVersion":"v3"} diff --git a/proofs/pending/builtins/Bls12381G1MultiScalarMul.nash b/proofs/pending/builtins/Bls12381G1MultiScalarMul.nash deleted file mode 100644 index f8ef26f7..00000000 --- a/proofs/pending/builtins/Bls12381G1MultiScalarMul.nash +++ /dev/null @@ -1,63 +0,0 @@ -module Bls12381G1MultiScalarMul exposing (..) - - -import Builtin - - -{-| These group laws are partial correctness over successfully decoded points. They do not assert that arbitrary bytes decode to a valid point. The pinned Lean model has opaque group operations without algebraic lemmas; these obligations are pending there. -} -operation p0 p1 = Builtin.bls12_381_g1_multiScalarMul p0 p1 - - -point : bytes -> bls_g1 -point encoded = Builtin.bls12_381_g1_uncompress encoded - - -proof - import Proof - - prop "singleton agrees with scalar multiplication" = - let - x via Proof.bytes - n via Proof.int - in - do - Proof.returns - ( - Builtin.bls12_381_g1_equal - (operation [ n ] [ point x ]) - (Builtin.bls12_381_g1_scalarMul n (point x)) - ) - (\holds -> holds) - - test "different scalar and point counts reject" fail = - do - let - _ = - operation - [ 1, 2 ] - [ point - #"c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" - ] - in - () - - test "scalar above the signed 4096 bit range rejects" fail = - do - let - scalar = - Builtin.byteStringToInteger - True - ( - Builtin.consByteString - 128 - (Builtin.replicateByte 511 0) - ) - - _ = - operation - [ scalar ] - [ point - #"c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" - ] - in - () diff --git a/proofs/pending/builtins/Bls12381G2MultiScalarMul.nash b/proofs/pending/builtins/Bls12381G2MultiScalarMul.nash deleted file mode 100644 index a0879694..00000000 --- a/proofs/pending/builtins/Bls12381G2MultiScalarMul.nash +++ /dev/null @@ -1,63 +0,0 @@ -module Bls12381G2MultiScalarMul exposing (..) - - -import Builtin - - -{-| These group laws are partial correctness over successfully decoded points. They do not assert that arbitrary bytes decode to a valid point. The pinned Lean model has opaque group operations without algebraic lemmas; these obligations are pending there. -} -operation p0 p1 = Builtin.bls12_381_g2_multiScalarMul p0 p1 - - -point : bytes -> bls_g2 -point encoded = Builtin.bls12_381_g2_uncompress encoded - - -proof - import Proof - - prop "singleton agrees with scalar multiplication" = - let - x via Proof.bytes - n via Proof.int - in - do - Proof.returns - ( - Builtin.bls12_381_g2_equal - (operation [ n ] [ point x ]) - (Builtin.bls12_381_g2_scalarMul n (point x)) - ) - (\holds -> holds) - - test "different scalar and point counts reject" fail = - do - let - _ = - operation - [ 1, 2 ] - [ point - #"c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" - ] - in - () - - test "scalar above the signed 4096 bit range rejects" fail = - do - let - scalar = - Builtin.byteStringToInteger - True - ( - Builtin.consByteString - 128 - (Builtin.replicateByte 511 0) - ) - - _ = - operation - [ scalar ] - [ point - #"c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" - ] - in - () diff --git a/proofs/pending/nash.jsonc b/proofs/pending/nash.jsonc deleted file mode 100644 index 60d26262..00000000 --- a/proofs/pending/nash.jsonc +++ /dev/null @@ -1 +0,0 @@ -{"type":"application","sourceDirectories":["builtins"],"plutusVersion":"v3"} diff --git a/proofs/verification.json b/proofs/verification.json deleted file mode 100644 index 67ed5f44..00000000 --- a/proofs/verification.json +++ /dev/null @@ -1,2342 +0,0 @@ -{ - "fuel": 300, - "postcondition_fuel": 300, - "solver_timeout_seconds": 10, - "wall_timeout_seconds": 25, - "trust": "smt_verified", - "backend": { - "lean": "4.24.0", - "Blaster": "bafdd4f7976037cd7bd8e443df04af096dc5b96e", - "PlutusCore": "41fe7eadf460dc66bef22b85656bc36408638bdc", - "CardanoLedgerApi": "3f9f7c6b6ecf0012e1397bab0a814daa36e70ba2" - }, - "obligations": [ - { - "file": "builtins/AddInteger.nash", - "module": "AddInteger", - "name": "commutative", - "source_sha256": "3a8e3aa12a6c780234160c664ae3c4365d707fbb3f945ebd9bf7e1019acc6055", - "status": "verified" - }, - { - "file": "builtins/AddInteger.nash", - "module": "AddInteger", - "name": "associative", - "source_sha256": "3a8e3aa12a6c780234160c664ae3c4365d707fbb3f945ebd9bf7e1019acc6055", - "status": "timeout", - "reason": "Did not finish within the wall limit; no proof or rejection witness claimed." - }, - { - "file": "builtins/AddInteger.nash", - "module": "AddInteger", - "name": "left identity", - "source_sha256": "3a8e3aa12a6c780234160c664ae3c4365d707fbb3f945ebd9bf7e1019acc6055", - "status": "verified" - }, - { - "file": "builtins/AddInteger.nash", - "module": "AddInteger", - "name": "right identity", - "source_sha256": "3a8e3aa12a6c780234160c664ae3c4365d707fbb3f945ebd9bf7e1019acc6055", - "status": "verified" - }, - { - "file": "builtins/AddInteger.nash", - "module": "AddInteger", - "name": "additive inverse", - "source_sha256": "3a8e3aa12a6c780234160c664ae3c4365d707fbb3f945ebd9bf7e1019acc6055", - "status": "verified" - }, - { - "file": "builtins/AddInteger.nash", - "module": "AddInteger", - "name": "successor never wraps at any integer width", - "source_sha256": "3a8e3aa12a6c780234160c664ae3c4365d707fbb3f945ebd9bf7e1019acc6055", - "status": "verified" - }, - { - "file": "builtins/AddInteger.nash", - "module": "AddInteger", - "name": "boxed successor never wraps at any integer width", - "source_sha256": "3a8e3aa12a6c780234160c664ae3c4365d707fbb3f945ebd9bf7e1019acc6055", - "status": "verified" - }, - { - "file": "builtins/AddInteger.nash", - "module": "AddInteger", - "name": "crosses signed 64-bit boundary", - "source_sha256": "3a8e3aa12a6c780234160c664ae3c4365d707fbb3f945ebd9bf7e1019acc6055", - "status": "verified" - }, - { - "file": "builtins/AddInteger.nash", - "module": "AddInteger", - "name": "crosses unsigned 128-bit boundary", - "source_sha256": "3a8e3aa12a6c780234160c664ae3c4365d707fbb3f945ebd9bf7e1019acc6055", - "status": "verified" - }, - { - "file": "builtins/AddInteger.nash", - "module": "AddInteger", - "name": "crosses negative signed 64-bit boundary", - "source_sha256": "3a8e3aa12a6c780234160c664ae3c4365d707fbb3f945ebd9bf7e1019acc6055", - "status": "verified" - }, - { - "file": "builtins/AddInteger.nash", - "module": "AddInteger", - "name": "detects wrong opcode argument order or signedness", - "source_sha256": "3a8e3aa12a6c780234160c664ae3c4365d707fbb3f945ebd9bf7e1019acc6055", - "status": "verified" - }, - { - "file": "builtins/AndByteString.nash", - "module": "AndByteString", - "name": "commutative for both padding modes", - "source_sha256": "efd4079cba62107e8aef8811d504c9706243469ed49cc2d9ed9435a30284ccac", - "status": "not_checked" - }, - { - "file": "builtins/AndByteString.nash", - "module": "AndByteString", - "name": "self law", - "source_sha256": "efd4079cba62107e8aef8811d504c9706243469ed49cc2d9ed9435a30284ccac", - "status": "not_checked" - }, - { - "file": "builtins/AndByteString.nash", - "module": "AndByteString", - "name": "truncating mode with empty input", - "source_sha256": "efd4079cba62107e8aef8811d504c9706243469ed49cc2d9ed9435a30284ccac", - "status": "not_checked" - }, - { - "file": "builtins/AppendByteString.nash", - "module": "AppendByteString", - "name": "left and right identity", - "source_sha256": "dcbd160b48c1ca7c81c01c936646b0e6ed41c74e2bc874bc3b3e47dd22c2b90c", - "status": "not_checked" - }, - { - "file": "builtins/AppendByteString.nash", - "module": "AppendByteString", - "name": "associative", - "source_sha256": "dcbd160b48c1ca7c81c01c936646b0e6ed41c74e2bc874bc3b3e47dd22c2b90c", - "status": "not_checked" - }, - { - "file": "builtins/AppendByteString.nash", - "module": "AppendByteString", - "name": "length is additive", - "source_sha256": "dcbd160b48c1ca7c81c01c936646b0e6ed41c74e2bc874bc3b3e47dd22c2b90c", - "status": "not_checked" - }, - { - "file": "builtins/AppendByteString.nash", - "module": "AppendByteString", - "name": "preserves byte order", - "source_sha256": "dcbd160b48c1ca7c81c01c936646b0e6ed41c74e2bc874bc3b3e47dd22c2b90c", - "status": "not_checked" - }, - { - "file": "builtins/AppendString.nash", - "module": "AppendString", - "name": "left and right identity", - "source_sha256": "add6ff09c26954fa30855ad4f902097165b61abf971125243748b7b40ca0769e", - "status": "not_checked" - }, - { - "file": "builtins/AppendString.nash", - "module": "AppendString", - "name": "associative", - "source_sha256": "add6ff09c26954fa30855ad4f902097165b61abf971125243748b7b40ca0769e", - "status": "not_checked" - }, - { - "file": "builtins/AppendString.nash", - "module": "AppendString", - "name": "preserves Unicode characters", - "source_sha256": "add6ff09c26954fa30855ad4f902097165b61abf971125243748b7b40ca0769e", - "status": "not_checked" - }, - { - "file": "builtins/BData.nash", - "module": "BData", - "name": "bytes roundtrip", - "source_sha256": "5a2845a995de08dd4f527c3cdeafbb18cf5433b64af407dfe55c81ec096cb386", - "status": "not_checked" - }, - { - "file": "builtins/Blake2b224.nash", - "module": "Blake2b224", - "name": "digest has the specified byte length", - "source_sha256": "ef21cb62ea26311156278cb4e84f5f6ecff493e9dac19cf95dcfff72924ce331", - "status": "not_checked" - }, - { - "file": "builtins/Blake2b224.nash", - "module": "Blake2b224", - "name": "empty message known answer detects the wrong hash primitive", - "source_sha256": "ef21cb62ea26311156278cb4e84f5f6ecff493e9dac19cf95dcfff72924ce331", - "status": "not_checked" - }, - { - "file": "builtins/Blake2b256.nash", - "module": "Blake2b256", - "name": "digest has the specified byte length", - "source_sha256": "ef2ccf54332c8873fcc8a4d34025b3c7e383c0c26dfbe3457c6d5b12f22ee194", - "status": "not_checked" - }, - { - "file": "builtins/Blake2b256.nash", - "module": "Blake2b256", - "name": "empty message known answer detects the wrong hash primitive", - "source_sha256": "ef2ccf54332c8873fcc8a4d34025b3c7e383c0c26dfbe3457c6d5b12f22ee194", - "status": "not_checked" - }, - { - "file": "builtins/Bls12381FinalVerify.nash", - "module": "Bls12381FinalVerify", - "name": "reflexive on valid Miller loop results", - "source_sha256": "f107a395e42fa64c1a8052bfc1457ef9ea042a9423c6ae4a0b8add8bbee4fd64", - "status": "not_checked" - }, - { - "file": "builtins/Bls12381G1Add.nash", - "module": "Bls12381G1Add", - "name": "commutative on valid decoded points", - "source_sha256": "dc1eaa2bc2e486734c79b72c9ad017711d429a0ad4e122735d320cc91815d329", - "status": "not_checked" - }, - { - "file": "builtins/Bls12381G1Compress.nash", - "module": "Bls12381G1Compress", - "name": "fixed compressed width on valid points", - "source_sha256": "e819f19f9d112c3343f635ffeea1571bf97eebf777ba22463867513964e3e94a", - "status": "not_checked" - }, - { - "file": "builtins/Bls12381G1Equal.nash", - "module": "Bls12381G1Equal", - "name": "reflexive on valid points", - "source_sha256": "e5cfe4e38f147f8cac205bdbf0f9c45371cdf15622740280d21d9aa5bee1750b", - "status": "not_checked" - }, - { - "file": "builtins/Bls12381G1HashToGroup.nash", - "module": "Bls12381G1HashToGroup", - "name": "hash to group yields a compressed point of fixed width", - "source_sha256": "38a59566e74c78fca4de560b56ec4e86db6352256fec9178b4843f984582fc62", - "status": "not_checked" - }, - { - "file": "builtins/Bls12381G1HashToGroup.nash", - "module": "Bls12381G1HashToGroup", - "name": "oversized domain separator rejects", - "source_sha256": "38a59566e74c78fca4de560b56ec4e86db6352256fec9178b4843f984582fc62", - "status": "backend_error", - "reason": "The SMT translator does not support an encountered Fin/BitVec datatype." - }, - { - "file": "builtins/Bls12381G1Neg.nash", - "module": "Bls12381G1Neg", - "name": "negation is an involution on valid points", - "source_sha256": "12c2b4e0583cee36c3c316cda4482fb742a7d1782adb2ecef5cd2270a6454598", - "status": "not_checked" - }, - { - "file": "builtins/Bls12381G1ScalarMul.nash", - "module": "Bls12381G1ScalarMul", - "name": "scalar one is the identity on valid points", - "source_sha256": "84fe446b0e5cf29823e19d62e8c4ffaf1c0340bf09a7422f4c0a96f05e05d4d6", - "status": "not_checked" - }, - { - "file": "builtins/Bls12381G1Uncompress.nash", - "module": "Bls12381G1Uncompress", - "name": "canonical compressed roundtrip on valid encodings", - "source_sha256": "f7ccc0ef2477b7cc6f2d4048d1be6dabc30dbec96b24362e3b483d90aeb161ee", - "status": "not_checked" - }, - { - "file": "builtins/Bls12381G1Uncompress.nash", - "module": "Bls12381G1Uncompress", - "name": "empty encoding rejects", - "source_sha256": "f7ccc0ef2477b7cc6f2d4048d1be6dabc30dbec96b24362e3b483d90aeb161ee", - "status": "backend_error", - "reason": "The SMT translator does not support an encountered Fin/BitVec datatype." - }, - { - "file": "builtins/Bls12381G1Uncompress.nash", - "module": "Bls12381G1Uncompress", - "name": "wrong compressed width rejects", - "source_sha256": "f7ccc0ef2477b7cc6f2d4048d1be6dabc30dbec96b24362e3b483d90aeb161ee", - "status": "backend_error", - "reason": "The SMT translator does not support an encountered Fin/BitVec datatype." - }, - { - "file": "builtins/Bls12381G1Uncompress.nash", - "module": "Bls12381G1Uncompress", - "name": "canonical infinity is accepted", - "source_sha256": "f7ccc0ef2477b7cc6f2d4048d1be6dabc30dbec96b24362e3b483d90aeb161ee", - "status": "not_checked" - }, - { - "file": "builtins/Bls12381G2Add.nash", - "module": "Bls12381G2Add", - "name": "commutative on valid decoded points", - "source_sha256": "15798ba1820b5ba559e845b348673484f7b4d2c122b5e0f56a4d364b191acb6b", - "status": "not_checked" - }, - { - "file": "builtins/Bls12381G2Compress.nash", - "module": "Bls12381G2Compress", - "name": "fixed compressed width on valid points", - "source_sha256": "8eccd44502c9bc82c31efcffa247a9157d5a2c1162cdfe2569db72ee8b470a67", - "status": "not_checked" - }, - { - "file": "builtins/Bls12381G2Equal.nash", - "module": "Bls12381G2Equal", - "name": "reflexive on valid points", - "source_sha256": "d068c460205c020a029ba083e7b3226ad6a6cb405e27b556969e21a601470df7", - "status": "not_checked" - }, - { - "file": "builtins/Bls12381G2HashToGroup.nash", - "module": "Bls12381G2HashToGroup", - "name": "hash to group yields a compressed point of fixed width", - "source_sha256": "d8397e7e897801964b2738c79c0bf1304619894038015753d055d2fc0b48a3ce", - "status": "not_checked" - }, - { - "file": "builtins/Bls12381G2HashToGroup.nash", - "module": "Bls12381G2HashToGroup", - "name": "oversized domain separator rejects", - "source_sha256": "d8397e7e897801964b2738c79c0bf1304619894038015753d055d2fc0b48a3ce", - "status": "backend_error", - "reason": "The SMT translator does not support an encountered Fin/BitVec datatype." - }, - { - "file": "builtins/Bls12381G2Neg.nash", - "module": "Bls12381G2Neg", - "name": "negation is an involution on valid points", - "source_sha256": "9dfef88a94225db2b4460da3bc97f1def176238813f4fcf5fdf562ae9a34532a", - "status": "not_checked" - }, - { - "file": "builtins/Bls12381G2ScalarMul.nash", - "module": "Bls12381G2ScalarMul", - "name": "scalar one is the identity on valid points", - "source_sha256": "052ca2e508cb66bc31d904cb20133ecc0dd71e4c445966cef7dc5eddfe46221e", - "status": "not_checked" - }, - { - "file": "builtins/Bls12381G2Uncompress.nash", - "module": "Bls12381G2Uncompress", - "name": "canonical compressed roundtrip on valid encodings", - "source_sha256": "fd9927b451e08242761632df367dfaa067ed588f4b45ff7b5edb28cf4ea0905a", - "status": "not_checked" - }, - { - "file": "builtins/Bls12381G2Uncompress.nash", - "module": "Bls12381G2Uncompress", - "name": "empty encoding rejects", - "source_sha256": "fd9927b451e08242761632df367dfaa067ed588f4b45ff7b5edb28cf4ea0905a", - "status": "backend_error", - "reason": "The SMT translator does not support an encountered Fin/BitVec datatype." - }, - { - "file": "builtins/Bls12381G2Uncompress.nash", - "module": "Bls12381G2Uncompress", - "name": "wrong compressed width rejects", - "source_sha256": "fd9927b451e08242761632df367dfaa067ed588f4b45ff7b5edb28cf4ea0905a", - "status": "backend_error", - "reason": "The SMT translator does not support an encountered Fin/BitVec datatype." - }, - { - "file": "builtins/Bls12381G2Uncompress.nash", - "module": "Bls12381G2Uncompress", - "name": "canonical infinity is accepted", - "source_sha256": "fd9927b451e08242761632df367dfaa067ed588f4b45ff7b5edb28cf4ea0905a", - "status": "not_checked" - }, - { - "file": "builtins/Bls12381MillerLoop.nash", - "module": "Bls12381MillerLoop", - "name": "pairing the same points gives equivalent results", - "source_sha256": "4d3da5021b0bf48103eb9a0ab4c4ace51ef81a7865f37a9693b9c0140c840cec", - "status": "not_checked" - }, - { - "file": "builtins/Bls12381MulMlResult.nash", - "module": "Bls12381MulMlResult", - "name": "commutative under final verification", - "source_sha256": "fcf1e621713613f2884b873c102f896850ebccefc7700f2c24d78908f228bb0c", - "status": "not_checked" - }, - { - "file": "builtins/ByteStringToInteger.nash", - "module": "ByteStringToInteger", - "name": "empty bytes mean zero", - "source_sha256": "c40f41663cf37c0899c2d59aed9b1492e35b392ffb5983970dbedab421afe79f", - "status": "not_checked" - }, - { - "file": "builtins/ByteStringToInteger.nash", - "module": "ByteStringToInteger", - "name": "endianness", - "source_sha256": "c40f41663cf37c0899c2d59aed9b1492e35b392ffb5983970dbedab421afe79f", - "status": "not_checked" - }, - { - "file": "builtins/ByteStringToInteger.nash", - "module": "ByteStringToInteger", - "name": "arbitrary width conversion stays nonnegative", - "source_sha256": "c40f41663cf37c0899c2d59aed9b1492e35b392ffb5983970dbedab421afe79f", - "status": "not_checked" - }, - { - "file": "builtins/ByteStringToInteger.nash", - "module": "ByteStringToInteger", - "name": "leading zero does not alter big endian value", - "source_sha256": "c40f41663cf37c0899c2d59aed9b1492e35b392ffb5983970dbedab421afe79f", - "status": "not_checked" - }, - { - "file": "builtins/ChooseData.nash", - "module": "ChooseData", - "name": "selects the constructor branch", - "source_sha256": "3720d8f49a22b3127811717964b98c31a5d46e614d62cc8407aeb2138e2c02f4", - "status": "not_checked" - }, - { - "file": "builtins/ChooseData.nash", - "module": "ChooseData", - "name": "selects the map branch", - "source_sha256": "3720d8f49a22b3127811717964b98c31a5d46e614d62cc8407aeb2138e2c02f4", - "status": "not_checked" - }, - { - "file": "builtins/ChooseData.nash", - "module": "ChooseData", - "name": "selects the list branch", - "source_sha256": "3720d8f49a22b3127811717964b98c31a5d46e614d62cc8407aeb2138e2c02f4", - "status": "not_checked" - }, - { - "file": "builtins/ChooseData.nash", - "module": "ChooseData", - "name": "selects the integer branch", - "source_sha256": "3720d8f49a22b3127811717964b98c31a5d46e614d62cc8407aeb2138e2c02f4", - "status": "not_checked" - }, - { - "file": "builtins/ChooseData.nash", - "module": "ChooseData", - "name": "selects the bytes branch", - "source_sha256": "3720d8f49a22b3127811717964b98c31a5d46e614d62cc8407aeb2138e2c02f4", - "status": "not_checked" - }, - { - "file": "builtins/ChooseList.nash", - "module": "ChooseList", - "name": "empty list chooses the empty branch", - "source_sha256": "0164fd136bbe25b2f0be56c84d6223d7e6e8ece44db1c1d42189b9432f2e4f3f", - "status": "not_checked" - }, - { - "file": "builtins/ChooseList.nash", - "module": "ChooseList", - "name": "nonempty list chooses the nonempty branch", - "source_sha256": "0164fd136bbe25b2f0be56c84d6223d7e6e8ece44db1c1d42189b9432f2e4f3f", - "status": "not_checked" - }, - { - "file": "builtins/ChooseList.nash", - "module": "ChooseList", - "name": "detects wrong opcode argument order or signedness", - "source_sha256": "0164fd136bbe25b2f0be56c84d6223d7e6e8ece44db1c1d42189b9432f2e4f3f", - "status": "not_checked" - }, - { - "file": "builtins/ChooseUnit.nash", - "module": "ChooseUnit", - "name": "returns the second argument", - "source_sha256": "2d3042cb4cfbacbc0680f60d11fc6aed8cf12c13fa802683871b36929e0a3eff", - "status": "not_checked" - }, - { - "file": "builtins/ChooseUnit.nash", - "module": "ChooseUnit", - "name": "detects wrong opcode argument order or signedness", - "source_sha256": "2d3042cb4cfbacbc0680f60d11fc6aed8cf12c13fa802683871b36929e0a3eff", - "status": "not_checked" - }, - { - "file": "builtins/ComplementByteString.nash", - "module": "ComplementByteString", - "name": "involution", - "source_sha256": "63a14d511e64af8c599c2bde12d1af7a8e19f42bce07667506bd895f91316ccd", - "status": "not_checked" - }, - { - "file": "builtins/ComplementByteString.nash", - "module": "ComplementByteString", - "name": "preserves length", - "source_sha256": "63a14d511e64af8c599c2bde12d1af7a8e19f42bce07667506bd895f91316ccd", - "status": "not_checked" - }, - { - "file": "builtins/ComplementByteString.nash", - "module": "ComplementByteString", - "name": "complements every bit", - "source_sha256": "63a14d511e64af8c599c2bde12d1af7a8e19f42bce07667506bd895f91316ccd", - "status": "not_checked" - }, - { - "file": "builtins/ConsByteString.nash", - "module": "ConsByteString", - "name": "prepends zero and preserves length", - "source_sha256": "aca6a7be3d9836fd97cbee1f5889ab24b70a2583ed51be9a416387656249707f", - "status": "not_checked" - }, - { - "file": "builtins/ConsByteString.nash", - "module": "ConsByteString", - "name": "valid byte survives at the head", - "source_sha256": "aca6a7be3d9836fd97cbee1f5889ab24b70a2583ed51be9a416387656249707f", - "status": "not_checked" - }, - { - "file": "builtins/ConsByteString.nash", - "module": "ConsByteString", - "name": "negative byte rejects in Plutus V3", - "source_sha256": "aca6a7be3d9836fd97cbee1f5889ab24b70a2583ed51be9a416387656249707f", - "status": "verified" - }, - { - "file": "builtins/ConsByteString.nash", - "module": "ConsByteString", - "name": "byte above 255 rejects in Plutus V3", - "source_sha256": "aca6a7be3d9836fd97cbee1f5889ab24b70a2583ed51be9a416387656249707f", - "status": "verified" - }, - { - "file": "builtins/ConsByteString.nash", - "module": "ConsByteString", - "name": "all negative byte values reject", - "source_sha256": "aca6a7be3d9836fd97cbee1f5889ab24b70a2583ed51be9a416387656249707f", - "status": "timeout", - "reason": "Did not finish within the wall limit; no proof or rejection witness claimed." - }, - { - "file": "builtins/ConsByteString.nash", - "module": "ConsByteString", - "name": "all byte values above 255 reject", - "source_sha256": "aca6a7be3d9836fd97cbee1f5889ab24b70a2583ed51be9a416387656249707f", - "status": "timeout", - "reason": "Did not finish within the wall limit; no proof or rejection witness claimed." - }, - { - "file": "builtins/ConstrData.nash", - "module": "ConstrData", - "name": "constructor tag and fields survive roundtrip", - "source_sha256": "fcba123af0e6a1765a70b81ab2a04d3110e2311f66d2ab0d1a34cae1c1885cbd", - "status": "not_checked" - }, - { - "file": "builtins/CountSetBits.nash", - "module": "CountSetBits", - "name": "count is bounded by the bit width", - "source_sha256": "8d617db7d1ccbec70297d29137de18782847c03da97c9c708d59ddaf24c2ba67", - "status": "not_checked" - }, - { - "file": "builtins/CountSetBits.nash", - "module": "CountSetBits", - "name": "known counts", - "source_sha256": "8d617db7d1ccbec70297d29137de18782847c03da97c9c708d59ddaf24c2ba67", - "status": "not_checked" - }, - { - "file": "builtins/DecodeUtf8.nash", - "module": "DecodeUtf8", - "name": "inverse on encoded strings", - "source_sha256": "991556c25a18fe32d2b8e1ac4d165b2be8460508b09c10b9170c8048593aa087", - "status": "not_checked" - }, - { - "file": "builtins/DecodeUtf8.nash", - "module": "DecodeUtf8", - "name": "non ASCII decoding", - "source_sha256": "991556c25a18fe32d2b8e1ac4d165b2be8460508b09c10b9170c8048593aa087", - "status": "not_checked" - }, - { - "file": "builtins/DecodeUtf8.nash", - "module": "DecodeUtf8", - "name": "invalid UTF8 rejects", - "source_sha256": "991556c25a18fe32d2b8e1ac4d165b2be8460508b09c10b9170c8048593aa087", - "status": "verified" - }, - { - "file": "builtins/DecodeUtf8.nash", - "module": "DecodeUtf8", - "name": "truncated UTF8 rejects", - "source_sha256": "991556c25a18fe32d2b8e1ac4d165b2be8460508b09c10b9170c8048593aa087", - "status": "timeout", - "reason": "Did not finish within the wall limit; no proof or rejection witness claimed." - }, - { - "file": "builtins/DecodeUtf8.nash", - "module": "DecodeUtf8", - "name": "overlong UTF8 is rejected", - "source_sha256": "991556c25a18fe32d2b8e1ac4d165b2be8460508b09c10b9170c8048593aa087", - "status": "timeout", - "reason": "Did not finish within the wall limit; no proof or rejection witness claimed." - }, - { - "file": "builtins/DecodeUtf8.nash", - "module": "DecodeUtf8", - "name": "UTF8 surrogate codepoints are rejected", - "source_sha256": "991556c25a18fe32d2b8e1ac4d165b2be8460508b09c10b9170c8048593aa087", - "status": "timeout", - "reason": "Did not finish within the wall limit; no proof or rejection witness claimed." - }, - { - "file": "builtins/DecodeUtf8.nash", - "module": "DecodeUtf8", - "name": "codepoint above Unicode maximum is rejected", - "source_sha256": "991556c25a18fe32d2b8e1ac4d165b2be8460508b09c10b9170c8048593aa087", - "status": "timeout", - "reason": "Did not finish within the wall limit; no proof or rejection witness claimed." - }, - { - "file": "builtins/DivideInteger.nash", - "module": "DivideInteger", - "name": "division by one", - "source_sha256": "7e1241661337239cf0a5bf1b840566d486efaca0209822688f0575a94dcca83c", - "status": "verified" - }, - { - "file": "builtins/DivideInteger.nash", - "module": "DivideInteger", - "name": "quotient and remainder reconstruct the dividend", - "source_sha256": "7e1241661337239cf0a5bf1b840566d486efaca0209822688f0575a94dcca83c", - "status": "timeout", - "reason": "Did not finish within the wall limit; no proof or rejection witness claimed." - }, - { - "file": "builtins/DivideInteger.nash", - "module": "DivideInteger", - "name": "negative rounding convention", - "source_sha256": "7e1241661337239cf0a5bf1b840566d486efaca0209822688f0575a94dcca83c", - "status": "verified" - }, - { - "file": "builtins/DivideInteger.nash", - "module": "DivideInteger", - "name": "zero divisor rejects every dividend", - "source_sha256": "7e1241661337239cf0a5bf1b840566d486efaca0209822688f0575a94dcca83c", - "status": "verified" - }, - { - "file": "builtins/DivideInteger.nash", - "module": "DivideInteger", - "name": "detects wrong opcode argument order or signedness", - "source_sha256": "7e1241661337239cf0a5bf1b840566d486efaca0209822688f0575a94dcca83c", - "status": "verified" - }, - { - "file": "builtins/DropList.nash", - "module": "DropList", - "name": "dropping zero preserves the head", - "source_sha256": "229f63138341cf7a42d5a229475bfbf78e44e945c7301f936ea1389d902c89fb", - "status": "not_checked" - }, - { - "file": "builtins/DropList.nash", - "module": "DropList", - "name": "dropping one preserves the next element", - "source_sha256": "229f63138341cf7a42d5a229475bfbf78e44e945c7301f936ea1389d902c89fb", - "status": "not_checked" - }, - { - "file": "builtins/DropList.nash", - "module": "DropList", - "name": "dropping the full list gives empty", - "source_sha256": "229f63138341cf7a42d5a229475bfbf78e44e945c7301f936ea1389d902c89fb", - "status": "not_checked" - }, - { - "file": "builtins/DropList.nash", - "module": "DropList", - "name": "negative count preserves the list", - "source_sha256": "229f63138341cf7a42d5a229475bfbf78e44e945c7301f936ea1389d902c89fb", - "status": "not_checked" - }, - { - "file": "builtins/DropList.nash", - "module": "DropList", - "name": "count beyond length gives empty", - "source_sha256": "229f63138341cf7a42d5a229475bfbf78e44e945c7301f936ea1389d902c89fb", - "status": "not_checked" - }, - { - "file": "builtins/EncodeUtf8.nash", - "module": "EncodeUtf8", - "name": "decode after encoding", - "source_sha256": "4959bcf9829ea6613f9f365f324f8118ca48a96ce4f3a06e91ccbb16714138e7", - "status": "not_checked" - }, - { - "file": "builtins/EncodeUtf8.nash", - "module": "EncodeUtf8", - "name": "append is preserved", - "source_sha256": "4959bcf9829ea6613f9f365f324f8118ca48a96ce4f3a06e91ccbb16714138e7", - "status": "not_checked" - }, - { - "file": "builtins/EncodeUtf8.nash", - "module": "EncodeUtf8", - "name": "non ASCII encoding", - "source_sha256": "4959bcf9829ea6613f9f365f324f8118ca48a96ce4f3a06e91ccbb16714138e7", - "status": "not_checked" - }, - { - "file": "builtins/EncodeUtf8.nash", - "module": "EncodeUtf8", - "name": "embedded zero is preserved", - "source_sha256": "4959bcf9829ea6613f9f365f324f8118ca48a96ce4f3a06e91ccbb16714138e7", - "status": "not_checked" - }, - { - "file": "builtins/EqualsByteString.nash", - "module": "EqualsByteString", - "name": "reflexive", - "source_sha256": "263083b016df90e5cfb513d04b19341ad6f9e6f917af7294de24554a7b34c8c3", - "status": "not_checked" - }, - { - "file": "builtins/EqualsByteString.nash", - "module": "EqualsByteString", - "name": "symmetric", - "source_sha256": "263083b016df90e5cfb513d04b19341ad6f9e6f917af7294de24554a7b34c8c3", - "status": "not_checked" - }, - { - "file": "builtins/EqualsByteString.nash", - "module": "EqualsByteString", - "name": "transitive", - "source_sha256": "263083b016df90e5cfb513d04b19341ad6f9e6f917af7294de24554a7b34c8c3", - "status": "not_checked" - }, - { - "file": "builtins/EqualsByteString.nash", - "module": "EqualsByteString", - "name": "detects wrong opcode argument order or signedness", - "source_sha256": "263083b016df90e5cfb513d04b19341ad6f9e6f917af7294de24554a7b34c8c3", - "status": "not_checked" - }, - { - "file": "builtins/EqualsData.nash", - "module": "EqualsData", - "name": "reflexive", - "source_sha256": "cb5a90908258c4d48d2b4bc04b1b0c02a34b159bdea9f7975a140b2e8ef0408f", - "status": "not_checked" - }, - { - "file": "builtins/EqualsData.nash", - "module": "EqualsData", - "name": "symmetric", - "source_sha256": "cb5a90908258c4d48d2b4bc04b1b0c02a34b159bdea9f7975a140b2e8ef0408f", - "status": "not_checked" - }, - { - "file": "builtins/EqualsData.nash", - "module": "EqualsData", - "name": "transitive", - "source_sha256": "cb5a90908258c4d48d2b4bc04b1b0c02a34b159bdea9f7975a140b2e8ef0408f", - "status": "not_checked" - }, - { - "file": "builtins/EqualsData.nash", - "module": "EqualsData", - "name": "detects wrong opcode argument order or signedness", - "source_sha256": "cb5a90908258c4d48d2b4bc04b1b0c02a34b159bdea9f7975a140b2e8ef0408f", - "status": "not_checked" - }, - { - "file": "builtins/EqualsInteger.nash", - "module": "EqualsInteger", - "name": "reflexive", - "source_sha256": "a68aecf09ad5a8173bdbf662adbeeb04b7cf59029857c1a5e2f76e850b7f3816", - "status": "verified" - }, - { - "file": "builtins/EqualsInteger.nash", - "module": "EqualsInteger", - "name": "symmetric", - "source_sha256": "a68aecf09ad5a8173bdbf662adbeeb04b7cf59029857c1a5e2f76e850b7f3816", - "status": "timeout", - "reason": "Did not finish within the wall limit; no proof or rejection witness claimed." - }, - { - "file": "builtins/EqualsInteger.nash", - "module": "EqualsInteger", - "name": "equivalent to zero difference", - "source_sha256": "a68aecf09ad5a8173bdbf662adbeeb04b7cf59029857c1a5e2f76e850b7f3816", - "status": "timeout", - "reason": "Did not finish within the wall limit; no proof or rejection witness claimed." - }, - { - "file": "builtins/EqualsInteger.nash", - "module": "EqualsInteger", - "name": "successive integers are distinct at every width", - "source_sha256": "a68aecf09ad5a8173bdbf662adbeeb04b7cf59029857c1a5e2f76e850b7f3816", - "status": "timeout", - "reason": "Did not finish within the wall limit; no proof or rejection witness claimed." - }, - { - "file": "builtins/EqualsInteger.nash", - "module": "EqualsInteger", - "name": "detects wrong opcode argument order or signedness", - "source_sha256": "a68aecf09ad5a8173bdbf662adbeeb04b7cf59029857c1a5e2f76e850b7f3816", - "status": "verified" - }, - { - "file": "builtins/EqualsString.nash", - "module": "EqualsString", - "name": "reflexive", - "source_sha256": "f5300d7fbf6468ff29a2d6d79e27275366cabe8bcefc03929098d9005f67ba91", - "status": "not_checked" - }, - { - "file": "builtins/EqualsString.nash", - "module": "EqualsString", - "name": "symmetric", - "source_sha256": "f5300d7fbf6468ff29a2d6d79e27275366cabe8bcefc03929098d9005f67ba91", - "status": "not_checked" - }, - { - "file": "builtins/EqualsString.nash", - "module": "EqualsString", - "name": "transitive", - "source_sha256": "f5300d7fbf6468ff29a2d6d79e27275366cabe8bcefc03929098d9005f67ba91", - "status": "not_checked" - }, - { - "file": "builtins/EqualsString.nash", - "module": "EqualsString", - "name": "detects wrong opcode argument order or signedness", - "source_sha256": "f5300d7fbf6468ff29a2d6d79e27275366cabe8bcefc03929098d9005f67ba91", - "status": "not_checked" - }, - { - "file": "builtins/ExpModInteger.nash", - "module": "ExpModInteger", - "name": "exponent zero gives the modular identity", - "source_sha256": "23193aad0cd956725b2a816fbea6a37caa2fecb7c1b9427e008bace7aa6df468", - "status": "not_checked" - }, - { - "file": "builtins/ExpModInteger.nash", - "module": "ExpModInteger", - "name": "known modular power", - "source_sha256": "23193aad0cd956725b2a816fbea6a37caa2fecb7c1b9427e008bace7aa6df468", - "status": "not_checked" - }, - { - "file": "builtins/ExpModInteger.nash", - "module": "ExpModInteger", - "name": "zero modulus rejects", - "source_sha256": "23193aad0cd956725b2a816fbea6a37caa2fecb7c1b9427e008bace7aa6df468", - "status": "counterexample_in_opaque_model", - "reason": "The SMT interpretation assigns unconstrained behavior to the opaque cryptographic primitive; this is not a concrete Nash counterexample." - }, - { - "file": "builtins/ExpModInteger.nash", - "module": "ExpModInteger", - "name": "negative modulus rejects", - "source_sha256": "23193aad0cd956725b2a816fbea6a37caa2fecb7c1b9427e008bace7aa6df468", - "status": "counterexample_in_opaque_model", - "reason": "The SMT interpretation assigns unconstrained behavior to the opaque cryptographic primitive; this is not a concrete Nash counterexample." - }, - { - "file": "builtins/ExpModInteger.nash", - "module": "ExpModInteger", - "name": "missing modular inverse rejects", - "source_sha256": "23193aad0cd956725b2a816fbea6a37caa2fecb7c1b9427e008bace7aa6df468", - "status": "counterexample_in_opaque_model", - "reason": "The SMT interpretation assigns unconstrained behavior to the opaque cryptographic primitive; this is not a concrete Nash counterexample." - }, - { - "file": "builtins/ExpModInteger.nash", - "module": "ExpModInteger", - "name": "negative exponent uses modular inverse", - "source_sha256": "23193aad0cd956725b2a816fbea6a37caa2fecb7c1b9427e008bace7aa6df468", - "status": "not_checked" - }, - { - "file": "builtins/FindFirstSetBit.nash", - "module": "FindFirstSetBit", - "name": "minus one exactly for all zero bits", - "source_sha256": "b914a1e7e9b08349d74bb1646f46eae186c06cab6738c3b38937f6144ad0704d", - "status": "not_checked" - }, - { - "file": "builtins/FindFirstSetBit.nash", - "module": "FindFirstSetBit", - "name": "least significant indexing", - "source_sha256": "b914a1e7e9b08349d74bb1646f46eae186c06cab6738c3b38937f6144ad0704d", - "status": "not_checked" - }, - { - "file": "builtins/FstPair.nash", - "module": "FstPair", - "name": "projects the correct component", - "source_sha256": "c5276f530af614f07ac935e3f128a69fa97a23967423b3dcfb7d8dc80a1984ce", - "status": "not_checked" - }, - { - "file": "builtins/HeadList.nash", - "module": "HeadList", - "name": "head of a nonempty list", - "source_sha256": "c67e1b3fe1aeb093895803b406b9e3ab07aa13ce3c8e817ee7800bc8cf7e5385", - "status": "not_checked" - }, - { - "file": "builtins/HeadList.nash", - "module": "HeadList", - "name": "empty list rejects", - "source_sha256": "c67e1b3fe1aeb093895803b406b9e3ab07aa13ce3c8e817ee7800bc8cf7e5385", - "status": "verified" - }, - { - "file": "builtins/IData.nash", - "module": "IData", - "name": "integer roundtrip has no width bound", - "source_sha256": "46e7ff3cd58fb4caf6ed3a64e1213e946d22c5168f8ce5fd96de09106ef650a2", - "status": "not_checked" - }, - { - "file": "builtins/IfThenElse.nash", - "module": "IfThenElse", - "name": "selects the branch indicated by the condition", - "source_sha256": "dfd1580aecf46f091410b87bc64023946ccd4a2b8f7fc88a196e13d635a1ea8b", - "status": "not_checked" - }, - { - "file": "builtins/IfThenElse.nash", - "module": "IfThenElse", - "name": "selects arbitrary Data values", - "source_sha256": "dfd1580aecf46f091410b87bc64023946ccd4a2b8f7fc88a196e13d635a1ea8b", - "status": "not_checked" - }, - { - "file": "builtins/IfThenElse.nash", - "module": "IfThenElse", - "name": "unselected arguments are still evaluated", - "source_sha256": "dfd1580aecf46f091410b87bc64023946ccd4a2b8f7fc88a196e13d635a1ea8b", - "status": "not_checked" - }, - { - "file": "builtins/IfThenElse.nash", - "module": "IfThenElse", - "name": "detects wrong opcode argument order or signedness", - "source_sha256": "dfd1580aecf46f091410b87bc64023946ccd4a2b8f7fc88a196e13d635a1ea8b", - "status": "not_checked" - }, - { - "file": "builtins/IndexArray.nash", - "module": "IndexArray", - "name": "selects each position", - "source_sha256": "f7e6bac94e0624ab2d287977b22610a445dac3004d91cb5cac5702a85b91aaac", - "status": "not_checked" - }, - { - "file": "builtins/IndexArray.nash", - "module": "IndexArray", - "name": "negative index rejects", - "source_sha256": "f7e6bac94e0624ab2d287977b22610a445dac3004d91cb5cac5702a85b91aaac", - "status": "timeout", - "reason": "Did not finish within the wall limit; no proof or rejection witness claimed." - }, - { - "file": "builtins/IndexArray.nash", - "module": "IndexArray", - "name": "index equal to length rejects", - "source_sha256": "f7e6bac94e0624ab2d287977b22610a445dac3004d91cb5cac5702a85b91aaac", - "status": "timeout", - "reason": "Did not finish within the wall limit; no proof or rejection witness claimed." - }, - { - "file": "builtins/IndexArray.nash", - "module": "IndexArray", - "name": "index must not truncate to a machine word", - "source_sha256": "f7e6bac94e0624ab2d287977b22610a445dac3004d91cb5cac5702a85b91aaac", - "status": "not_checked" - }, - { - "file": "builtins/IndexArray.nash", - "module": "IndexArray", - "name": "index above 128 bits rejects without host overflow", - "source_sha256": "f7e6bac94e0624ab2d287977b22610a445dac3004d91cb5cac5702a85b91aaac", - "status": "timeout", - "reason": "Did not finish within the wall limit; no proof or rejection witness claimed." - }, - { - "file": "builtins/IndexByteString.nash", - "module": "IndexByteString", - "name": "valid indices return a byte", - "source_sha256": "5d62bea74e6a730ab523b5f5d4920663422497b2e0dced15b619c9ab03ac263f", - "status": "not_checked" - }, - { - "file": "builtins/IndexByteString.nash", - "module": "IndexByteString", - "name": "cons supplies index zero", - "source_sha256": "5d62bea74e6a730ab523b5f5d4920663422497b2e0dced15b619c9ab03ac263f", - "status": "not_checked" - }, - { - "file": "builtins/IndexByteString.nash", - "module": "IndexByteString", - "name": "negative index rejects", - "source_sha256": "5d62bea74e6a730ab523b5f5d4920663422497b2e0dced15b619c9ab03ac263f", - "status": "verified" - }, - { - "file": "builtins/IndexByteString.nash", - "module": "IndexByteString", - "name": "index equal to length rejects", - "source_sha256": "5d62bea74e6a730ab523b5f5d4920663422497b2e0dced15b619c9ab03ac263f", - "status": "backend_error", - "reason": "The SMT translator does not support an encountered Fin/BitVec datatype." - }, - { - "file": "builtins/IndexByteString.nash", - "module": "IndexByteString", - "name": "index must not truncate to a machine word", - "source_sha256": "5d62bea74e6a730ab523b5f5d4920663422497b2e0dced15b619c9ab03ac263f", - "status": "not_checked" - }, - { - "file": "builtins/IndexByteString.nash", - "module": "IndexByteString", - "name": "index above 128 bits rejects without host overflow", - "source_sha256": "5d62bea74e6a730ab523b5f5d4920663422497b2e0dced15b619c9ab03ac263f", - "status": "verified" - }, - { - "file": "builtins/InsertCoin.nash", - "module": "InsertCoin", - "name": "insert then lookup", - "source_sha256": "f8cb439f708e30b61df149c9b27b9b363b771c7735f4ea50e23f2205103fcf8e", - "status": "not_checked" - }, - { - "file": "builtins/InsertCoin.nash", - "module": "InsertCoin", - "name": "zero quantity removes the coin", - "source_sha256": "f8cb439f708e30b61df149c9b27b9b363b771c7735f4ea50e23f2205103fcf8e", - "status": "not_checked" - }, - { - "file": "builtins/InsertCoin.nash", - "module": "InsertCoin", - "name": "nonzero quantity rejects currency ID above 32 bytes", - "source_sha256": "f8cb439f708e30b61df149c9b27b9b363b771c7735f4ea50e23f2205103fcf8e", - "status": "timeout", - "reason": "Did not finish within the wall limit; no proof or rejection witness claimed." - }, - { - "file": "builtins/InsertCoin.nash", - "module": "InsertCoin", - "name": "nonzero quantity rejects token name above 32 bytes", - "source_sha256": "f8cb439f708e30b61df149c9b27b9b363b771c7735f4ea50e23f2205103fcf8e", - "status": "timeout", - "reason": "Did not finish within the wall limit; no proof or rejection witness claimed." - }, - { - "file": "builtins/InsertCoin.nash", - "module": "InsertCoin", - "name": "positive quantity above signed 128 bits rejects", - "source_sha256": "f8cb439f708e30b61df149c9b27b9b363b771c7735f4ea50e23f2205103fcf8e", - "status": "timeout", - "reason": "Did not finish within the wall limit; no proof or rejection witness claimed." - }, - { - "file": "builtins/InsertCoin.nash", - "module": "InsertCoin", - "name": "zero quantity accepts long identifiers", - "source_sha256": "f8cb439f708e30b61df149c9b27b9b363b771c7735f4ea50e23f2205103fcf8e", - "status": "not_checked" - }, - { - "file": "builtins/InsertCoin.nash", - "module": "InsertCoin", - "name": "reinsertion replaces rather than adds", - "source_sha256": "f8cb439f708e30b61df149c9b27b9b363b771c7735f4ea50e23f2205103fcf8e", - "status": "not_checked" - }, - { - "file": "builtins/IntegerToByteString.nash", - "module": "IntegerToByteString", - "name": "zero with minimal width is empty", - "source_sha256": "84e35ce21ff4b21a11cf0fe4724669c58edd027a1e81f81624f8ca781a0e6795", - "status": "not_checked" - }, - { - "file": "builtins/IntegerToByteString.nash", - "module": "IntegerToByteString", - "name": "endianness and leading padding", - "source_sha256": "84e35ce21ff4b21a11cf0fe4724669c58edd027a1e81f81624f8ca781a0e6795", - "status": "not_checked" - }, - { - "file": "builtins/IntegerToByteString.nash", - "module": "IntegerToByteString", - "name": "unsigned byte roundtrip", - "source_sha256": "84e35ce21ff4b21a11cf0fe4724669c58edd027a1e81f81624f8ca781a0e6795", - "status": "not_checked" - }, - { - "file": "builtins/IntegerToByteString.nash", - "module": "IntegerToByteString", - "name": "negative integer rejects", - "source_sha256": "84e35ce21ff4b21a11cf0fe4724669c58edd027a1e81f81624f8ca781a0e6795", - "status": "verified" - }, - { - "file": "builtins/IntegerToByteString.nash", - "module": "IntegerToByteString", - "name": "insufficient width rejects", - "source_sha256": "84e35ce21ff4b21a11cf0fe4724669c58edd027a1e81f81624f8ca781a0e6795", - "status": "verified" - }, - { - "file": "builtins/IntegerToByteString.nash", - "module": "IntegerToByteString", - "name": "negative width rejects", - "source_sha256": "84e35ce21ff4b21a11cf0fe4724669c58edd027a1e81f81624f8ca781a0e6795", - "status": "verified" - }, - { - "file": "builtins/IntegerToByteString.nash", - "module": "IntegerToByteString", - "name": "width above 8192 rejects", - "source_sha256": "84e35ce21ff4b21a11cf0fe4724669c58edd027a1e81f81624f8ca781a0e6795", - "status": "verified" - }, - { - "file": "builtins/IntegerToByteString.nash", - "module": "IntegerToByteString", - "name": "largest supported width is accepted", - "source_sha256": "84e35ce21ff4b21a11cf0fe4724669c58edd027a1e81f81624f8ca781a0e6795", - "status": "not_checked" - }, - { - "file": "builtins/Keccak256.nash", - "module": "Keccak256", - "name": "digest has the specified byte length", - "source_sha256": "0e802e224b2873a001a0743d088a2208a718ac933618ac38c0cc90d247738fb0", - "status": "not_checked" - }, - { - "file": "builtins/LengthOfArray.nash", - "module": "LengthOfArray", - "name": "empty array length", - "source_sha256": "17aec38c6ee47865ea254427800b50a96d7aa3676eae0c00ddb24c94b12f2203", - "status": "not_checked" - }, - { - "file": "builtins/LengthOfArray.nash", - "module": "LengthOfArray", - "name": "length after list conversion", - "source_sha256": "17aec38c6ee47865ea254427800b50a96d7aa3676eae0c00ddb24c94b12f2203", - "status": "not_checked" - }, - { - "file": "builtins/LengthOfByteString.nash", - "module": "LengthOfByteString", - "name": "nonnegative", - "source_sha256": "866001394f3eeebabce6eb33254a6af3eb1d5dd189d2115a7b0f983cc8a3fa43", - "status": "not_checked" - }, - { - "file": "builtins/LengthOfByteString.nash", - "module": "LengthOfByteString", - "name": "empty length is zero", - "source_sha256": "866001394f3eeebabce6eb33254a6af3eb1d5dd189d2115a7b0f983cc8a3fa43", - "status": "not_checked" - }, - { - "file": "builtins/LengthOfByteString.nash", - "module": "LengthOfByteString", - "name": "append adds lengths", - "source_sha256": "866001394f3eeebabce6eb33254a6af3eb1d5dd189d2115a7b0f983cc8a3fa43", - "status": "not_checked" - }, - { - "file": "builtins/LengthOfByteString.nash", - "module": "LengthOfByteString", - "name": "bytes are counted rather than UTF8 codepoints", - "source_sha256": "866001394f3eeebabce6eb33254a6af3eb1d5dd189d2115a7b0f983cc8a3fa43", - "status": "not_checked" - }, - { - "file": "builtins/LessThanByteString.nash", - "module": "LessThanByteString", - "name": "irreflexive", - "source_sha256": "02fd1873ff7285f9fd3b2f4e192404ea05f06ea9c91443853208aaa51923172a", - "status": "not_checked" - }, - { - "file": "builtins/LessThanByteString.nash", - "module": "LessThanByteString", - "name": "asymmetric", - "source_sha256": "02fd1873ff7285f9fd3b2f4e192404ea05f06ea9c91443853208aaa51923172a", - "status": "not_checked" - }, - { - "file": "builtins/LessThanByteString.nash", - "module": "LessThanByteString", - "name": "unsigned lexicographic ordering", - "source_sha256": "02fd1873ff7285f9fd3b2f4e192404ea05f06ea9c91443853208aaa51923172a", - "status": "not_checked" - }, - { - "file": "builtins/LessThanEqualsByteString.nash", - "module": "LessThanEqualsByteString", - "name": "reflexive", - "source_sha256": "6b00fdee94ad2b0d4cb58dcb0fdeea5bfede44e02f94ac572cca90ea7e074387", - "status": "not_checked" - }, - { - "file": "builtins/LessThanEqualsByteString.nash", - "module": "LessThanEqualsByteString", - "name": "agrees with strict order or equality", - "source_sha256": "6b00fdee94ad2b0d4cb58dcb0fdeea5bfede44e02f94ac572cca90ea7e074387", - "status": "not_checked" - }, - { - "file": "builtins/LessThanEqualsInteger.nash", - "module": "LessThanEqualsInteger", - "name": "reflexive", - "source_sha256": "dc2e5d35ca06921fb417e189e81e2bbabf18b350dfc334a25756d19848f944ec", - "status": "verified" - }, - { - "file": "builtins/LessThanEqualsInteger.nash", - "module": "LessThanEqualsInteger", - "name": "antisymmetric", - "source_sha256": "dc2e5d35ca06921fb417e189e81e2bbabf18b350dfc334a25756d19848f944ec", - "status": "timeout", - "reason": "Did not finish within the wall limit; no proof or rejection witness claimed." - }, - { - "file": "builtins/LessThanEqualsInteger.nash", - "module": "LessThanEqualsInteger", - "name": "agrees with strict order or equality", - "source_sha256": "dc2e5d35ca06921fb417e189e81e2bbabf18b350dfc334a25756d19848f944ec", - "status": "timeout", - "reason": "Did not finish within the wall limit; no proof or rejection witness claimed." - }, - { - "file": "builtins/LessThanEqualsInteger.nash", - "module": "LessThanEqualsInteger", - "name": "detects wrong opcode argument order or signedness", - "source_sha256": "dc2e5d35ca06921fb417e189e81e2bbabf18b350dfc334a25756d19848f944ec", - "status": "verified" - }, - { - "file": "builtins/LessThanInteger.nash", - "module": "LessThanInteger", - "name": "irreflexive", - "source_sha256": "7e297299a3ac48729855b3cfe216f7e9df5e0f34fb2584b5147d2a6768bfa147", - "status": "verified" - }, - { - "file": "builtins/LessThanInteger.nash", - "module": "LessThanInteger", - "name": "strict successor order at every width", - "source_sha256": "7e297299a3ac48729855b3cfe216f7e9df5e0f34fb2584b5147d2a6768bfa147", - "status": "verified" - }, - { - "file": "builtins/LessThanInteger.nash", - "module": "LessThanInteger", - "name": "asymmetric", - "source_sha256": "7e297299a3ac48729855b3cfe216f7e9df5e0f34fb2584b5147d2a6768bfa147", - "status": "timeout", - "reason": "Did not finish within the wall limit; no proof or rejection witness claimed." - }, - { - "file": "builtins/LessThanInteger.nash", - "module": "LessThanInteger", - "name": "transitive", - "source_sha256": "7e297299a3ac48729855b3cfe216f7e9df5e0f34fb2584b5147d2a6768bfa147", - "status": "timeout", - "reason": "Did not finish within the wall limit; no proof or rejection witness claimed." - }, - { - "file": "builtins/LessThanInteger.nash", - "module": "LessThanInteger", - "name": "detects wrong opcode argument order or signedness", - "source_sha256": "7e297299a3ac48729855b3cfe216f7e9df5e0f34fb2584b5147d2a6768bfa147", - "status": "verified" - }, - { - "file": "builtins/ListData.nash", - "module": "ListData", - "name": "list elements survive roundtrip", - "source_sha256": "034a0b9cdf82e37cd7fd1d26ca72dcb5a2692319b6da7f76ff79efcabc1631ad", - "status": "not_checked" - }, - { - "file": "builtins/ListToArray.nash", - "module": "ListToArray", - "name": "preserves order", - "source_sha256": "befc87b01312836833a4784f979c14ae9e3caf12af8a0a760be49fede1731526", - "status": "not_checked" - }, - { - "file": "builtins/LookupCoin.nash", - "module": "LookupCoin", - "name": "absent asset has zero quantity", - "source_sha256": "4eedfe77733162b690777a4a61bdeebeedbc40a345dbb61daebd749d874c3457", - "status": "not_checked" - }, - { - "file": "builtins/LookupCoin.nash", - "module": "LookupCoin", - "name": "returns the inserted quantity", - "source_sha256": "4eedfe77733162b690777a4a61bdeebeedbc40a345dbb61daebd749d874c3457", - "status": "not_checked" - }, - { - "file": "builtins/LookupCoin.nash", - "module": "LookupCoin", - "name": "long nonexistent identifier gives zero", - "source_sha256": "4eedfe77733162b690777a4a61bdeebeedbc40a345dbb61daebd749d874c3457", - "status": "not_checked" - }, - { - "file": "builtins/MapData.nash", - "module": "MapData", - "name": "map entry survives roundtrip", - "source_sha256": "c6934265ccc1808db2c48a083bce631b1908834aff87e91a7d74c95ff1c59f3f", - "status": "not_checked" - }, - { - "file": "builtins/MkCons.nash", - "module": "MkCons", - "name": "head of cons is its element", - "source_sha256": "bb21e43e2315d53ab0841682f90a054dbb48d9748579e1b6ae29dc401d1ed510", - "status": "not_checked" - }, - { - "file": "builtins/MkCons.nash", - "module": "MkCons", - "name": "tail of cons is its original list", - "source_sha256": "bb21e43e2315d53ab0841682f90a054dbb48d9748579e1b6ae29dc401d1ed510", - "status": "not_checked" - }, - { - "file": "builtins/MkCons.nash", - "module": "MkCons", - "name": "cons is never empty", - "source_sha256": "bb21e43e2315d53ab0841682f90a054dbb48d9748579e1b6ae29dc401d1ed510", - "status": "not_checked" - }, - { - "file": "builtins/MkNilData.nash", - "module": "MkNilData", - "name": "produces the empty list", - "source_sha256": "d5e708f1d140b27b5c9e111e6878a6670a32090ce9ca140a10ae2ab77b59588d", - "status": "not_checked" - }, - { - "file": "builtins/MkNilData.nash", - "module": "MkNilData", - "name": "empty result has no head", - "source_sha256": "d5e708f1d140b27b5c9e111e6878a6670a32090ce9ca140a10ae2ab77b59588d", - "status": "not_checked" - }, - { - "file": "builtins/MkNilPairData.nash", - "module": "MkNilPairData", - "name": "produces the empty list", - "source_sha256": "123eb956be08903b6a031fbf4dd27f1da1583f01a085d305e48ec53c803113d5", - "status": "not_checked" - }, - { - "file": "builtins/MkNilPairData.nash", - "module": "MkNilPairData", - "name": "empty result has no head", - "source_sha256": "123eb956be08903b6a031fbf4dd27f1da1583f01a085d305e48ec53c803113d5", - "status": "not_checked" - }, - { - "file": "builtins/MkPairData.nash", - "module": "MkPairData", - "name": "both projections recover their arguments", - "source_sha256": "8e4b9a314085bb303ae25b9eccb8571e983a8af4f50433fbf77feac220578d94", - "status": "not_checked" - }, - { - "file": "builtins/ModInteger.nash", - "module": "ModInteger", - "name": "remainder modulo one is zero", - "source_sha256": "c1c66b2f1ace739a8ffa2309bc9d14592cc1e3fbc5e60f4d664dc6ea3729cba6", - "status": "verified" - }, - { - "file": "builtins/ModInteger.nash", - "module": "ModInteger", - "name": "division reconstruction", - "source_sha256": "c1c66b2f1ace739a8ffa2309bc9d14592cc1e3fbc5e60f4d664dc6ea3729cba6", - "status": "timeout", - "reason": "Did not finish within the wall limit; no proof or rejection witness claimed." - }, - { - "file": "builtins/ModInteger.nash", - "module": "ModInteger", - "name": "positive modulus bounds the remainder", - "source_sha256": "c1c66b2f1ace739a8ffa2309bc9d14592cc1e3fbc5e60f4d664dc6ea3729cba6", - "status": "timeout", - "reason": "Did not finish within the wall limit; no proof or rejection witness claimed." - }, - { - "file": "builtins/ModInteger.nash", - "module": "ModInteger", - "name": "negative rounding convention", - "source_sha256": "c1c66b2f1ace739a8ffa2309bc9d14592cc1e3fbc5e60f4d664dc6ea3729cba6", - "status": "verified" - }, - { - "file": "builtins/ModInteger.nash", - "module": "ModInteger", - "name": "zero divisor rejects every dividend", - "source_sha256": "c1c66b2f1ace739a8ffa2309bc9d14592cc1e3fbc5e60f4d664dc6ea3729cba6", - "status": "verified" - }, - { - "file": "builtins/ModInteger.nash", - "module": "ModInteger", - "name": "detects wrong opcode argument order or signedness", - "source_sha256": "c1c66b2f1ace739a8ffa2309bc9d14592cc1e3fbc5e60f4d664dc6ea3729cba6", - "status": "verified" - }, - { - "file": "builtins/MultiplyInteger.nash", - "module": "MultiplyInteger", - "name": "commutative", - "source_sha256": "d2a91324f674ef643bf8cac3b0d68907147c9734eb1d55425d4a3df9fb919d23", - "status": "verified" - }, - { - "file": "builtins/MultiplyInteger.nash", - "module": "MultiplyInteger", - "name": "associative", - "source_sha256": "d2a91324f674ef643bf8cac3b0d68907147c9734eb1d55425d4a3df9fb919d23", - "status": "timeout", - "reason": "Did not finish within the wall limit; no proof or rejection witness claimed." - }, - { - "file": "builtins/MultiplyInteger.nash", - "module": "MultiplyInteger", - "name": "distributive over addition", - "source_sha256": "d2a91324f674ef643bf8cac3b0d68907147c9734eb1d55425d4a3df9fb919d23", - "status": "timeout", - "reason": "Did not finish within the wall limit; no proof or rejection witness claimed." - }, - { - "file": "builtins/MultiplyInteger.nash", - "module": "MultiplyInteger", - "name": "multiplicative identity", - "source_sha256": "d2a91324f674ef643bf8cac3b0d68907147c9734eb1d55425d4a3df9fb919d23", - "status": "verified" - }, - { - "file": "builtins/MultiplyInteger.nash", - "module": "MultiplyInteger", - "name": "zero annihilates", - "source_sha256": "d2a91324f674ef643bf8cac3b0d68907147c9734eb1d55425d4a3df9fb919d23", - "status": "verified" - }, - { - "file": "builtins/MultiplyInteger.nash", - "module": "MultiplyInteger", - "name": "square is nonnegative without machine overflow", - "source_sha256": "d2a91324f674ef643bf8cac3b0d68907147c9734eb1d55425d4a3df9fb919d23", - "status": "timeout", - "reason": "Did not finish within the wall limit; no proof or rejection witness claimed." - }, - { - "file": "builtins/MultiplyInteger.nash", - "module": "MultiplyInteger", - "name": "crosses signed 64-bit boundary", - "source_sha256": "d2a91324f674ef643bf8cac3b0d68907147c9734eb1d55425d4a3df9fb919d23", - "status": "verified" - }, - { - "file": "builtins/MultiplyInteger.nash", - "module": "MultiplyInteger", - "name": "detects wrong opcode argument order or signedness", - "source_sha256": "d2a91324f674ef643bf8cac3b0d68907147c9734eb1d55425d4a3df9fb919d23", - "status": "verified" - }, - { - "file": "builtins/NullList.nash", - "module": "NullList", - "name": "empty list is null", - "source_sha256": "d90c8266f2c6e2316bd72a7e47243fca0dc3cebc26c056b72d8022fcc000831d", - "status": "not_checked" - }, - { - "file": "builtins/NullList.nash", - "module": "NullList", - "name": "singleton list is not null", - "source_sha256": "d90c8266f2c6e2316bd72a7e47243fca0dc3cebc26c056b72d8022fcc000831d", - "status": "not_checked" - }, - { - "file": "builtins/OrByteString.nash", - "module": "OrByteString", - "name": "commutative for both padding modes", - "source_sha256": "c76c5d0c7f8b5cd5c300d8994b1e9f4113dc8096c8f530bad26a692401371230", - "status": "not_checked" - }, - { - "file": "builtins/OrByteString.nash", - "module": "OrByteString", - "name": "self law", - "source_sha256": "c76c5d0c7f8b5cd5c300d8994b1e9f4113dc8096c8f530bad26a692401371230", - "status": "not_checked" - }, - { - "file": "builtins/OrByteString.nash", - "module": "OrByteString", - "name": "truncating mode with empty input", - "source_sha256": "c76c5d0c7f8b5cd5c300d8994b1e9f4113dc8096c8f530bad26a692401371230", - "status": "not_checked" - }, - { - "file": "builtins/QuotientInteger.nash", - "module": "QuotientInteger", - "name": "division by one", - "source_sha256": "8f92a13ef16e47fffb82ef378c046d22c3493135e03884a09840dbe875b37c93", - "status": "verified" - }, - { - "file": "builtins/QuotientInteger.nash", - "module": "QuotientInteger", - "name": "quotient and remainder reconstruct the dividend", - "source_sha256": "8f92a13ef16e47fffb82ef378c046d22c3493135e03884a09840dbe875b37c93", - "status": "timeout", - "reason": "Did not finish within the wall limit; no proof or rejection witness claimed." - }, - { - "file": "builtins/QuotientInteger.nash", - "module": "QuotientInteger", - "name": "negative rounding convention", - "source_sha256": "8f92a13ef16e47fffb82ef378c046d22c3493135e03884a09840dbe875b37c93", - "status": "verified" - }, - { - "file": "builtins/QuotientInteger.nash", - "module": "QuotientInteger", - "name": "zero divisor rejects every dividend", - "source_sha256": "8f92a13ef16e47fffb82ef378c046d22c3493135e03884a09840dbe875b37c93", - "status": "verified" - }, - { - "file": "builtins/QuotientInteger.nash", - "module": "QuotientInteger", - "name": "detects wrong opcode argument order or signedness", - "source_sha256": "8f92a13ef16e47fffb82ef378c046d22c3493135e03884a09840dbe875b37c93", - "status": "verified" - }, - { - "file": "builtins/ReadBit.nash", - "module": "ReadBit", - "name": "least significant bit indexing", - "source_sha256": "c2032885e10e60ade90b7d2ec5718565cd2dac02619c419bb43d9e77e6204f9e", - "status": "not_checked" - }, - { - "file": "builtins/ReadBit.nash", - "module": "ReadBit", - "name": "negative bit index rejects", - "source_sha256": "c2032885e10e60ade90b7d2ec5718565cd2dac02619c419bb43d9e77e6204f9e", - "status": "verified" - }, - { - "file": "builtins/ReadBit.nash", - "module": "ReadBit", - "name": "index equal to bit count rejects", - "source_sha256": "c2032885e10e60ade90b7d2ec5718565cd2dac02619c419bb43d9e77e6204f9e", - "status": "verified" - }, - { - "file": "builtins/ReadBit.nash", - "module": "ReadBit", - "name": "index above 128 bits rejects without host overflow", - "source_sha256": "c2032885e10e60ade90b7d2ec5718565cd2dac02619c419bb43d9e77e6204f9e", - "status": "verified" - }, - { - "file": "builtins/RemainderInteger.nash", - "module": "RemainderInteger", - "name": "remainder modulo one is zero", - "source_sha256": "3c480cdceb54125e8f87ed5667745fc45fb2f001c932463d85d7ec6f498d0947", - "status": "verified" - }, - { - "file": "builtins/RemainderInteger.nash", - "module": "RemainderInteger", - "name": "division reconstruction", - "source_sha256": "3c480cdceb54125e8f87ed5667745fc45fb2f001c932463d85d7ec6f498d0947", - "status": "timeout", - "reason": "Did not finish within the wall limit; no proof or rejection witness claimed." - }, - { - "file": "builtins/RemainderInteger.nash", - "module": "RemainderInteger", - "name": "remainder has the dividend sign", - "source_sha256": "3c480cdceb54125e8f87ed5667745fc45fb2f001c932463d85d7ec6f498d0947", - "status": "timeout", - "reason": "Did not finish within the wall limit; no proof or rejection witness claimed." - }, - { - "file": "builtins/RemainderInteger.nash", - "module": "RemainderInteger", - "name": "negative rounding convention", - "source_sha256": "3c480cdceb54125e8f87ed5667745fc45fb2f001c932463d85d7ec6f498d0947", - "status": "verified" - }, - { - "file": "builtins/RemainderInteger.nash", - "module": "RemainderInteger", - "name": "zero divisor rejects every dividend", - "source_sha256": "3c480cdceb54125e8f87ed5667745fc45fb2f001c932463d85d7ec6f498d0947", - "status": "verified" - }, - { - "file": "builtins/RemainderInteger.nash", - "module": "RemainderInteger", - "name": "detects wrong opcode argument order or signedness", - "source_sha256": "3c480cdceb54125e8f87ed5667745fc45fb2f001c932463d85d7ec6f498d0947", - "status": "verified" - }, - { - "file": "builtins/ReplicateByte.nash", - "module": "ReplicateByte", - "name": "zero count produces empty bytes", - "source_sha256": "bfc02d6daff02ef5066e00f8f863a941655784696939b53d32923ea5d0f851d3", - "status": "not_checked" - }, - { - "file": "builtins/ReplicateByte.nash", - "module": "ReplicateByte", - "name": "repeats exactly the requested byte", - "source_sha256": "bfc02d6daff02ef5066e00f8f863a941655784696939b53d32923ea5d0f851d3", - "status": "not_checked" - }, - { - "file": "builtins/ReplicateByte.nash", - "module": "ReplicateByte", - "name": "invalid byte rejects", - "source_sha256": "bfc02d6daff02ef5066e00f8f863a941655784696939b53d32923ea5d0f851d3", - "status": "verified" - }, - { - "file": "builtins/ReplicateByte.nash", - "module": "ReplicateByte", - "name": "negative count rejects", - "source_sha256": "bfc02d6daff02ef5066e00f8f863a941655784696939b53d32923ea5d0f851d3", - "status": "verified" - }, - { - "file": "builtins/ReplicateByte.nash", - "module": "ReplicateByte", - "name": "negative byte rejects", - "source_sha256": "bfc02d6daff02ef5066e00f8f863a941655784696939b53d32923ea5d0f851d3", - "status": "verified" - }, - { - "file": "builtins/ReplicateByte.nash", - "module": "ReplicateByte", - "name": "count above 8192 rejects", - "source_sha256": "bfc02d6daff02ef5066e00f8f863a941655784696939b53d32923ea5d0f851d3", - "status": "verified" - }, - { - "file": "builtins/ReplicateByte.nash", - "module": "ReplicateByte", - "name": "largest supported count is accepted", - "source_sha256": "bfc02d6daff02ef5066e00f8f863a941655784696939b53d32923ea5d0f851d3", - "status": "not_checked" - }, - { - "file": "builtins/Ripemd160.nash", - "module": "Ripemd160", - "name": "digest has the specified byte length", - "source_sha256": "54dcb28b5abda7f7def4d07c88719d299ea85ca84178df3a627414ef8329ea01", - "status": "not_checked" - }, - { - "file": "builtins/Ripemd160.nash", - "module": "Ripemd160", - "name": "empty message known answer detects the wrong hash primitive", - "source_sha256": "54dcb28b5abda7f7def4d07c88719d299ea85ca84178df3a627414ef8329ea01", - "status": "not_checked" - }, - { - "file": "builtins/RotateByteString.nash", - "module": "RotateByteString", - "name": "zero rotation is identity", - "source_sha256": "5c0afa3feee1dd5a7c422ea807b29729e70b738848c6f131dc3fb8f7166b2447", - "status": "not_checked" - }, - { - "file": "builtins/RotateByteString.nash", - "module": "RotateByteString", - "name": "rotation by width is identity", - "source_sha256": "5c0afa3feee1dd5a7c422ea807b29729e70b738848c6f131dc3fb8f7166b2447", - "status": "not_checked" - }, - { - "file": "builtins/RotateByteString.nash", - "module": "RotateByteString", - "name": "inverse rotation", - "source_sha256": "5c0afa3feee1dd5a7c422ea807b29729e70b738848c6f131dc3fb8f7166b2447", - "status": "not_checked" - }, - { - "file": "builtins/RotateByteString.nash", - "module": "RotateByteString", - "name": "large rotation is reduced by bit width", - "source_sha256": "5c0afa3feee1dd5a7c422ea807b29729e70b738848c6f131dc3fb8f7166b2447", - "status": "counterexample_in_opaque_model", - "reason": "The SMT interpretation assigns unconstrained behavior to the opaque cryptographic primitive; this is not a concrete Nash counterexample." - }, - { - "file": "builtins/ScaleValue.nash", - "module": "ScaleValue", - "name": "zero annihilates quantities", - "source_sha256": "48339d3ca100d527144709992e93ac284a742d59cf74ece393273c1ae1cb1589", - "status": "not_checked" - }, - { - "file": "builtins/ScaleValue.nash", - "module": "ScaleValue", - "name": "multiplicative identity", - "source_sha256": "48339d3ca100d527144709992e93ac284a742d59cf74ece393273c1ae1cb1589", - "status": "not_checked" - }, - { - "file": "builtins/ScaleValue.nash", - "module": "ScaleValue", - "name": "scales quantities", - "source_sha256": "48339d3ca100d527144709992e93ac284a742d59cf74ece393273c1ae1cb1589", - "status": "not_checked" - }, - { - "file": "builtins/ScaleValue.nash", - "module": "ScaleValue", - "name": "quantity overflow rejects", - "source_sha256": "48339d3ca100d527144709992e93ac284a742d59cf74ece393273c1ae1cb1589", - "status": "timeout", - "reason": "Did not finish within the wall limit; no proof or rejection witness claimed." - }, - { - "file": "builtins/ScaleValue.nash", - "module": "ScaleValue", - "name": "out of bounds multiplier rejects", - "source_sha256": "48339d3ca100d527144709992e93ac284a742d59cf74ece393273c1ae1cb1589", - "status": "timeout", - "reason": "Did not finish within the wall limit; no proof or rejection witness claimed." - }, - { - "file": "builtins/SerialiseData.nash", - "module": "SerialiseData", - "name": "canonical CBOR integer encoding", - "source_sha256": "64c3959b6325508a269fcbeaf09f25a33e91f95ce83fa639b6ded8d0baafa659", - "status": "not_checked" - }, - { - "file": "builtins/Sha2256.nash", - "module": "Sha2256", - "name": "digest has the specified byte length", - "source_sha256": "cdbaa425599b5bbef0707b916196a5ccaa1c068384ca93eddaed6e4dffa2a66c", - "status": "not_checked" - }, - { - "file": "builtins/Sha2256.nash", - "module": "Sha2256", - "name": "empty message known answer detects the wrong hash primitive", - "source_sha256": "cdbaa425599b5bbef0707b916196a5ccaa1c068384ca93eddaed6e4dffa2a66c", - "status": "not_checked" - }, - { - "file": "builtins/Sha3256.nash", - "module": "Sha3256", - "name": "digest has the specified byte length", - "source_sha256": "a09f08e6f2946d5aac17be4a2d9549f432aecc25964732a1e26e8bee3416d29b", - "status": "not_checked" - }, - { - "file": "builtins/Sha3256.nash", - "module": "Sha3256", - "name": "empty message known answer detects the wrong hash primitive", - "source_sha256": "a09f08e6f2946d5aac17be4a2d9549f432aecc25964732a1e26e8bee3416d29b", - "status": "not_checked" - }, - { - "file": "builtins/ShiftByteString.nash", - "module": "ShiftByteString", - "name": "zero shift is identity", - "source_sha256": "cf1c719bc17823d3c5bfcb1749e8a7f0fafbd55aca241d2c71b3fab09c72f2a5", - "status": "not_checked" - }, - { - "file": "builtins/ShiftByteString.nash", - "module": "ShiftByteString", - "name": "direction and zero fill", - "source_sha256": "cf1c719bc17823d3c5bfcb1749e8a7f0fafbd55aca241d2c71b3fab09c72f2a5", - "status": "not_checked" - }, - { - "file": "builtins/ShiftByteString.nash", - "module": "ShiftByteString", - "name": "shifting out every bit produces zero", - "source_sha256": "cf1c719bc17823d3c5bfcb1749e8a7f0fafbd55aca241d2c71b3fab09c72f2a5", - "status": "not_checked" - }, - { - "file": "builtins/ShiftByteString.nash", - "module": "ShiftByteString", - "name": "large shift does not truncate to a host word", - "source_sha256": "cf1c719bc17823d3c5bfcb1749e8a7f0fafbd55aca241d2c71b3fab09c72f2a5", - "status": "counterexample_in_opaque_model", - "reason": "The SMT interpretation assigns unconstrained behavior to the opaque cryptographic primitive; this is not a concrete Nash counterexample." - }, - { - "file": "builtins/ShiftByteString.nash", - "module": "ShiftByteString", - "name": "whole byte shifts preserve the remaining bytes", - "source_sha256": "cf1c719bc17823d3c5bfcb1749e8a7f0fafbd55aca241d2c71b3fab09c72f2a5", - "status": "counterexample_in_opaque_model", - "reason": "The SMT interpretation assigns unconstrained behavior to the opaque cryptographic primitive; this is not a concrete Nash counterexample." - }, - { - "file": "builtins/SliceByteString.nash", - "module": "SliceByteString", - "name": "zero count produces empty bytes", - "source_sha256": "c8693d7d6a8f72c68917995760caf764990a0d24f6c40f6a206959e32c718aff", - "status": "not_checked" - }, - { - "file": "builtins/SliceByteString.nash", - "module": "SliceByteString", - "name": "negative count produces empty bytes", - "source_sha256": "c8693d7d6a8f72c68917995760caf764990a0d24f6c40f6a206959e32c718aff", - "status": "not_checked" - }, - { - "file": "builtins/SliceByteString.nash", - "module": "SliceByteString", - "name": "whole slice preserves the bytes", - "source_sha256": "c8693d7d6a8f72c68917995760caf764990a0d24f6c40f6a206959e32c718aff", - "status": "not_checked" - }, - { - "file": "builtins/SliceByteString.nash", - "module": "SliceByteString", - "name": "negative offset is clamped independently of count", - "source_sha256": "c8693d7d6a8f72c68917995760caf764990a0d24f6c40f6a206959e32c718aff", - "status": "not_checked" - }, - { - "file": "builtins/SliceByteString.nash", - "module": "SliceByteString", - "name": "offset at the end gives empty bytes", - "source_sha256": "c8693d7d6a8f72c68917995760caf764990a0d24f6c40f6a206959e32c718aff", - "status": "not_checked" - }, - { - "file": "builtins/SliceByteString.nash", - "module": "SliceByteString", - "name": "detects wrong opcode argument order or signedness", - "source_sha256": "c8693d7d6a8f72c68917995760caf764990a0d24f6c40f6a206959e32c718aff", - "status": "not_checked" - }, - { - "file": "builtins/SndPair.nash", - "module": "SndPair", - "name": "projects the correct component", - "source_sha256": "4299259aabf7dbc5caf39f2944ade060632f8067c0313baec2a47f613522d723", - "status": "not_checked" - }, - { - "file": "builtins/SubtractInteger.nash", - "module": "SubtractInteger", - "name": "right identity", - "source_sha256": "beb39b6b1fb1cda54df42822c8eb963b870ac38fdee24755bea6bb80a4846664", - "status": "verified" - }, - { - "file": "builtins/SubtractInteger.nash", - "module": "SubtractInteger", - "name": "self subtraction is zero", - "source_sha256": "beb39b6b1fb1cda54df42822c8eb963b870ac38fdee24755bea6bb80a4846664", - "status": "verified" - }, - { - "file": "builtins/SubtractInteger.nash", - "module": "SubtractInteger", - "name": "inverse of addition", - "source_sha256": "beb39b6b1fb1cda54df42822c8eb963b870ac38fdee24755bea6bb80a4846664", - "status": "timeout", - "reason": "Did not finish within the wall limit; no proof or rejection witness claimed." - }, - { - "file": "builtins/SubtractInteger.nash", - "module": "SubtractInteger", - "name": "predecessor never wraps at any integer width", - "source_sha256": "beb39b6b1fb1cda54df42822c8eb963b870ac38fdee24755bea6bb80a4846664", - "status": "verified" - }, - { - "file": "builtins/SubtractInteger.nash", - "module": "SubtractInteger", - "name": "argument order", - "source_sha256": "beb39b6b1fb1cda54df42822c8eb963b870ac38fdee24755bea6bb80a4846664", - "status": "verified" - }, - { - "file": "builtins/SubtractInteger.nash", - "module": "SubtractInteger", - "name": "detects wrong opcode argument order or signedness", - "source_sha256": "beb39b6b1fb1cda54df42822c8eb963b870ac38fdee24755bea6bb80a4846664", - "status": "verified" - }, - { - "file": "builtins/TailList.nash", - "module": "TailList", - "name": "tail preserves subsequent elements", - "source_sha256": "f26fc640c4944dbb5d2900a8e151274f7c5a3cc20345305095a5f85d031fd4ac", - "status": "not_checked" - }, - { - "file": "builtins/TailList.nash", - "module": "TailList", - "name": "singleton tail is empty", - "source_sha256": "f26fc640c4944dbb5d2900a8e151274f7c5a3cc20345305095a5f85d031fd4ac", - "status": "not_checked" - }, - { - "file": "builtins/TailList.nash", - "module": "TailList", - "name": "empty list rejects", - "source_sha256": "f26fc640c4944dbb5d2900a8e151274f7c5a3cc20345305095a5f85d031fd4ac", - "status": "verified" - }, - { - "file": "builtins/Trace.nash", - "module": "Trace", - "name": "preserves the value", - "source_sha256": "23b38eb0f8518fc409262a485f41922b61a858e3aadd5589cec62f82a7e47646", - "status": "not_checked" - }, - { - "file": "builtins/Trace.nash", - "module": "Trace", - "name": "detects wrong opcode argument order or signedness", - "source_sha256": "23b38eb0f8518fc409262a485f41922b61a858e3aadd5589cec62f82a7e47646", - "status": "not_checked" - }, - { - "file": "builtins/UnBData.nash", - "module": "UnBData", - "name": "unboxes every bytestring", - "source_sha256": "7aa45a411f7453c906a2c9860933f4136f6d98e303b159c2b3e6a64108ec7448", - "status": "not_checked" - }, - { - "file": "builtins/UnBData.nash", - "module": "UnBData", - "name": "integer Data is not bytes Data", - "source_sha256": "7aa45a411f7453c906a2c9860933f4136f6d98e303b159c2b3e6a64108ec7448", - "status": "not_checked" - }, - { - "file": "builtins/UnBData.nash", - "module": "UnBData", - "name": "constructor Data cannot be used as bytes", - "source_sha256": "7aa45a411f7453c906a2c9860933f4136f6d98e303b159c2b3e6a64108ec7448", - "status": "not_checked" - }, - { - "file": "builtins/UnConstrData.nash", - "module": "UnConstrData", - "name": "recovers tag and first field", - "source_sha256": "4cd1dea95442f5064e5675af43b6ba7a90a693c473bf57f992d61ecea64f739b", - "status": "not_checked" - }, - { - "file": "builtins/UnConstrData.nash", - "module": "UnConstrData", - "name": "integer Data is not constructor Data", - "source_sha256": "4cd1dea95442f5064e5675af43b6ba7a90a693c473bf57f992d61ecea64f739b", - "status": "not_checked" - }, - { - "file": "builtins/UnConstrData.nash", - "module": "UnConstrData", - "name": "bytes Data cannot be used as a constructor", - "source_sha256": "4cd1dea95442f5064e5675af43b6ba7a90a693c473bf57f992d61ecea64f739b", - "status": "not_checked" - }, - { - "file": "builtins/UnIData.nash", - "module": "UnIData", - "name": "unboxes every integer", - "source_sha256": "864f4539a6c426a73e69af1826d37360047d346b87060f9399c720ae7824cfd5", - "status": "not_checked" - }, - { - "file": "builtins/UnIData.nash", - "module": "UnIData", - "name": "bytes Data is not integer Data", - "source_sha256": "864f4539a6c426a73e69af1826d37360047d346b87060f9399c720ae7824cfd5", - "status": "not_checked" - }, - { - "file": "builtins/UnIData.nash", - "module": "UnIData", - "name": "constructor Data cannot be used as an integer", - "source_sha256": "864f4539a6c426a73e69af1826d37360047d346b87060f9399c720ae7824cfd5", - "status": "not_checked" - }, - { - "file": "builtins/UnListData.nash", - "module": "UnListData", - "name": "recovers the first element", - "source_sha256": "a424030664ca78f042ae3aebabbcaa2396dad154dd4bb776b9299f43237006a7", - "status": "not_checked" - }, - { - "file": "builtins/UnListData.nash", - "module": "UnListData", - "name": "integer Data is not list Data", - "source_sha256": "a424030664ca78f042ae3aebabbcaa2396dad154dd4bb776b9299f43237006a7", - "status": "not_checked" - }, - { - "file": "builtins/UnListData.nash", - "module": "UnListData", - "name": "bytes Data cannot be used as a list", - "source_sha256": "a424030664ca78f042ae3aebabbcaa2396dad154dd4bb776b9299f43237006a7", - "status": "not_checked" - }, - { - "file": "builtins/UnMapData.nash", - "module": "UnMapData", - "name": "recovers a map entry", - "source_sha256": "cb21e0d32bf13ceeff37605908e8a24bc270af49490cdcccb486f9cbe6bdf44a", - "status": "not_checked" - }, - { - "file": "builtins/UnMapData.nash", - "module": "UnMapData", - "name": "integer Data is not map Data", - "source_sha256": "cb21e0d32bf13ceeff37605908e8a24bc270af49490cdcccb486f9cbe6bdf44a", - "status": "not_checked" - }, - { - "file": "builtins/UnMapData.nash", - "module": "UnMapData", - "name": "bytes Data cannot be used as a map", - "source_sha256": "cb21e0d32bf13ceeff37605908e8a24bc270af49490cdcccb486f9cbe6bdf44a", - "status": "not_checked" - }, - { - "file": "builtins/UnValueData.nash", - "module": "UnValueData", - "name": "decoding and encoding recover quantities", - "source_sha256": "6de5bc8f2ff995e4bf1ad529a2a07331fb2c13f8e7c0ee22e4d7a48d8dd07272", - "status": "not_checked" - }, - { - "file": "builtins/UnValueData.nash", - "module": "UnValueData", - "name": "integer Data is not a value", - "source_sha256": "6de5bc8f2ff995e4bf1ad529a2a07331fb2c13f8e7c0ee22e4d7a48d8dd07272", - "status": "not_checked" - }, - { - "file": "builtins/UnValueData.nash", - "module": "UnValueData", - "name": "empty inner asset map rejects", - "source_sha256": "6de5bc8f2ff995e4bf1ad529a2a07331fb2c13f8e7c0ee22e4d7a48d8dd07272", - "status": "timeout", - "reason": "Did not finish within the wall limit; no proof or rejection witness claimed." - }, - { - "file": "builtins/UnValueData.nash", - "module": "UnValueData", - "name": "zero encoded quantity rejects", - "source_sha256": "6de5bc8f2ff995e4bf1ad529a2a07331fb2c13f8e7c0ee22e4d7a48d8dd07272", - "status": "timeout", - "reason": "Did not finish within the wall limit; no proof or rejection witness claimed." - }, - { - "file": "builtins/UnValueData.nash", - "module": "UnValueData", - "name": "duplicate asset names reject", - "source_sha256": "6de5bc8f2ff995e4bf1ad529a2a07331fb2c13f8e7c0ee22e4d7a48d8dd07272", - "status": "timeout", - "reason": "Did not finish within the wall limit; no proof or rejection witness claimed." - }, - { - "file": "builtins/UnValueData.nash", - "module": "UnValueData", - "name": "out of order asset names reject", - "source_sha256": "6de5bc8f2ff995e4bf1ad529a2a07331fb2c13f8e7c0ee22e4d7a48d8dd07272", - "status": "timeout", - "reason": "Did not finish within the wall limit; no proof or rejection witness claimed." - }, - { - "file": "builtins/UnionValue.nash", - "module": "UnionValue", - "name": "empty value is the identity", - "source_sha256": "ad6428ffc4053362b0db1d15658123c80bbaee1465b3e7df5f28d378ccf6c83e", - "status": "not_checked" - }, - { - "file": "builtins/UnionValue.nash", - "module": "UnionValue", - "name": "adds the same asset quantities", - "source_sha256": "ad6428ffc4053362b0db1d15658123c80bbaee1465b3e7df5f28d378ccf6c83e", - "status": "not_checked" - }, - { - "file": "builtins/UnionValue.nash", - "module": "UnionValue", - "name": "quantity overflow rejects", - "source_sha256": "ad6428ffc4053362b0db1d15658123c80bbaee1465b3e7df5f28d378ccf6c83e", - "status": "timeout", - "reason": "Did not finish within the wall limit; no proof or rejection witness claimed." - }, - { - "file": "builtins/ValueContains.nash", - "module": "ValueContains", - "name": "nonnegative value contains itself", - "source_sha256": "647b4655d719739edac87da837f39fe19bb5cbb5a13b4a8f7659c4636c07d2cd", - "status": "not_checked" - }, - { - "file": "builtins/ValueContains.nash", - "module": "ValueContains", - "name": "quantity containment", - "source_sha256": "647b4655d719739edac87da837f39fe19bb5cbb5a13b4a8f7659c4636c07d2cd", - "status": "not_checked" - }, - { - "file": "builtins/ValueContains.nash", - "module": "ValueContains", - "name": "negative first quantity rejects", - "source_sha256": "647b4655d719739edac87da837f39fe19bb5cbb5a13b4a8f7659c4636c07d2cd", - "status": "timeout", - "reason": "Did not finish within the wall limit; no proof or rejection witness claimed." - }, - { - "file": "builtins/ValueContains.nash", - "module": "ValueContains", - "name": "negative second quantity rejects", - "source_sha256": "647b4655d719739edac87da837f39fe19bb5cbb5a13b4a8f7659c4636c07d2cd", - "status": "timeout", - "reason": "Did not finish within the wall limit; no proof or rejection witness claimed." - }, - { - "file": "builtins/ValueData.nash", - "module": "ValueData", - "name": "encoding and decoding recover quantities", - "source_sha256": "fbc2eef0a2db366d2c6094237004e212ffe75e802e16429e37fc4876143b1ca4", - "status": "not_checked" - }, - { - "file": "builtins/VerifyEcdsaSecp256k1Signature.nash", - "module": "VerifyEcdsaSecp256k1Signature", - "name": "empty public key rejects", - "source_sha256": "e0be60afd4e2fe6f10053ee76c578a0cf43f90739194c818e6c5f7d2350f389f", - "status": "counterexample_in_opaque_model", - "reason": "The SMT interpretation assigns unconstrained behavior to the opaque cryptographic primitive; this is not a concrete Nash counterexample." - }, - { - "file": "builtins/VerifyEcdsaSecp256k1Signature.nash", - "module": "VerifyEcdsaSecp256k1Signature", - "name": "valid key with truncated signature rejects", - "source_sha256": "e0be60afd4e2fe6f10053ee76c578a0cf43f90739194c818e6c5f7d2350f389f", - "status": "counterexample_in_opaque_model", - "reason": "The SMT interpretation assigns unconstrained behavior to the opaque cryptographic primitive; this is not a concrete Nash counterexample." - }, - { - "file": "builtins/VerifyEd25519Signature.nash", - "module": "VerifyEd25519Signature", - "name": "empty public key rejects", - "source_sha256": "4e94508fd10045bed2fbad31a12abd62d0176b0926f62d65a482014a51cb78a9", - "status": "counterexample_in_opaque_model", - "reason": "The SMT interpretation assigns unconstrained behavior to the opaque cryptographic primitive; this is not a concrete Nash counterexample." - }, - { - "file": "builtins/VerifyEd25519Signature.nash", - "module": "VerifyEd25519Signature", - "name": "valid key with truncated signature rejects", - "source_sha256": "4e94508fd10045bed2fbad31a12abd62d0176b0926f62d65a482014a51cb78a9", - "status": "counterexample_in_opaque_model", - "reason": "The SMT interpretation assigns unconstrained behavior to the opaque cryptographic primitive; this is not a concrete Nash counterexample." - }, - { - "file": "builtins/VerifyEd25519Signature.nash", - "module": "VerifyEd25519Signature", - "name": "RFC8032 empty message signature verifies", - "source_sha256": "4e94508fd10045bed2fbad31a12abd62d0176b0926f62d65a482014a51cb78a9", - "status": "not_checked" - }, - { - "file": "builtins/VerifyEd25519Signature.nash", - "module": "VerifyEd25519Signature", - "name": "changed message returns False instead of rejecting", - "source_sha256": "4e94508fd10045bed2fbad31a12abd62d0176b0926f62d65a482014a51cb78a9", - "status": "timeout", - "reason": "Did not finish within the wall limit; no proof or rejection witness claimed." - }, - { - "file": "builtins/VerifySchnorrSecp256k1Signature.nash", - "module": "VerifySchnorrSecp256k1Signature", - "name": "empty public key rejects", - "source_sha256": "69f8daa3b5c1950e974e685c6e2fab93358239e36f8ac03ffdf1dd1d9ed0ce01", - "status": "counterexample_in_opaque_model", - "reason": "The SMT interpretation assigns unconstrained behavior to the opaque cryptographic primitive; this is not a concrete Nash counterexample." - }, - { - "file": "builtins/VerifySchnorrSecp256k1Signature.nash", - "module": "VerifySchnorrSecp256k1Signature", - "name": "valid key with truncated signature rejects", - "source_sha256": "69f8daa3b5c1950e974e685c6e2fab93358239e36f8ac03ffdf1dd1d9ed0ce01", - "status": "counterexample_in_opaque_model", - "reason": "The SMT interpretation assigns unconstrained behavior to the opaque cryptographic primitive; this is not a concrete Nash counterexample." - }, - { - "file": "builtins/WriteBits.nash", - "module": "WriteBits", - "name": "write then read", - "source_sha256": "50a4ccf15143d85ca616e60b7587f98d0a11c47b6cf36307e74c0cceaf2c165a", - "status": "not_checked" - }, - { - "file": "builtins/WriteBits.nash", - "module": "WriteBits", - "name": "empty index list is identity", - "source_sha256": "50a4ccf15143d85ca616e60b7587f98d0a11c47b6cf36307e74c0cceaf2c165a", - "status": "not_checked" - }, - { - "file": "builtins/WriteBits.nash", - "module": "WriteBits", - "name": "invalid index rejects", - "source_sha256": "50a4ccf15143d85ca616e60b7587f98d0a11c47b6cf36307e74c0cceaf2c165a", - "status": "verified" - }, - { - "file": "builtins/WriteBits.nash", - "module": "WriteBits", - "name": "index above 128 bits rejects without host overflow", - "source_sha256": "50a4ccf15143d85ca616e60b7587f98d0a11c47b6cf36307e74c0cceaf2c165a", - "status": "verified" - }, - { - "file": "builtins/WriteBits.nash", - "module": "WriteBits", - "name": "negative index rejects", - "source_sha256": "50a4ccf15143d85ca616e60b7587f98d0a11c47b6cf36307e74c0cceaf2c165a", - "status": "verified" - }, - { - "file": "builtins/WriteBits.nash", - "module": "WriteBits", - "name": "all indices must be valid", - "source_sha256": "50a4ccf15143d85ca616e60b7587f98d0a11c47b6cf36307e74c0cceaf2c165a", - "status": "not_checked" - }, - { - "file": "builtins/XorByteString.nash", - "module": "XorByteString", - "name": "commutative for both padding modes", - "source_sha256": "b91410c7e60d3f970d1fae5319917cf7c282050d4ade87a43a65f9ac415dbc09", - "status": "not_checked" - }, - { - "file": "builtins/XorByteString.nash", - "module": "XorByteString", - "name": "self law", - "source_sha256": "b91410c7e60d3f970d1fae5319917cf7c282050d4ade87a43a65f9ac415dbc09", - "status": "not_checked" - }, - { - "file": "builtins/XorByteString.nash", - "module": "XorByteString", - "name": "truncating mode with empty input", - "source_sha256": "b91410c7e60d3f970d1fae5319917cf7c282050d4ade87a43a65f9ac415dbc09", - "status": "not_checked" - }, - { - "file": "pending/builtins/Bls12381G1MultiScalarMul.nash", - "module": "Bls12381G1MultiScalarMul", - "name": "singleton agrees with scalar multiplication", - "source_sha256": "c285ef425c387db5846b1572a8ccd6b1ee1b10bfb1ec5f5b997c0aafe39c9526", - "status": "compiler_blocked" - }, - { - "file": "pending/builtins/Bls12381G1MultiScalarMul.nash", - "module": "Bls12381G1MultiScalarMul", - "name": "different scalar and point counts reject", - "source_sha256": "c285ef425c387db5846b1572a8ccd6b1ee1b10bfb1ec5f5b997c0aafe39c9526", - "status": "compiler_blocked" - }, - { - "file": "pending/builtins/Bls12381G1MultiScalarMul.nash", - "module": "Bls12381G1MultiScalarMul", - "name": "scalar above the signed 4096 bit range rejects", - "source_sha256": "c285ef425c387db5846b1572a8ccd6b1ee1b10bfb1ec5f5b997c0aafe39c9526", - "status": "compiler_blocked" - }, - { - "file": "pending/builtins/Bls12381G2MultiScalarMul.nash", - "module": "Bls12381G2MultiScalarMul", - "name": "singleton agrees with scalar multiplication", - "source_sha256": "4790b57b9a70997e6c15d92b306fe49de5e3e5139a41573571886996d015a111", - "status": "compiler_blocked" - }, - { - "file": "pending/builtins/Bls12381G2MultiScalarMul.nash", - "module": "Bls12381G2MultiScalarMul", - "name": "different scalar and point counts reject", - "source_sha256": "4790b57b9a70997e6c15d92b306fe49de5e3e5139a41573571886996d015a111", - "status": "compiler_blocked" - }, - { - "file": "pending/builtins/Bls12381G2MultiScalarMul.nash", - "module": "Bls12381G2MultiScalarMul", - "name": "scalar above the signed 4096 bit range rejects", - "source_sha256": "4790b57b9a70997e6c15d92b306fe49de5e3e5139a41573571886996d015a111", - "status": "compiler_blocked" - } - ] -} diff --git a/proofs/verified.tsv b/proofs/verified.tsv deleted file mode 100644 index 998f0667..00000000 --- a/proofs/verified.tsv +++ /dev/null @@ -1,66 +0,0 @@ -# SMT-verified with the pinned models at 300 CEK steps; solver 10s, wall 25s. -# Moduleproof name. This is a reproducible subset, not all obligations. -AddInteger additive inverse -AddInteger boxed successor never wraps at any integer width -AddInteger commutative -AddInteger crosses negative signed 64-bit boundary -AddInteger crosses signed 64-bit boundary -AddInteger crosses unsigned 128-bit boundary -AddInteger detects wrong opcode argument order or signedness -AddInteger left identity -AddInteger right identity -AddInteger successor never wraps at any integer width -ConsByteString byte above 255 rejects in Plutus V3 -ConsByteString negative byte rejects in Plutus V3 -DecodeUtf8 invalid UTF8 rejects -DivideInteger detects wrong opcode argument order or signedness -DivideInteger division by one -DivideInteger negative rounding convention -DivideInteger zero divisor rejects every dividend -EqualsInteger detects wrong opcode argument order or signedness -EqualsInteger reflexive -HeadList empty list rejects -IndexByteString index above 128 bits rejects without host overflow -IndexByteString negative index rejects -IntegerToByteString insufficient width rejects -IntegerToByteString negative integer rejects -IntegerToByteString negative width rejects -IntegerToByteString width above 8192 rejects -LessThanEqualsInteger detects wrong opcode argument order or signedness -LessThanEqualsInteger reflexive -LessThanInteger detects wrong opcode argument order or signedness -LessThanInteger irreflexive -LessThanInteger strict successor order at every width -ModInteger detects wrong opcode argument order or signedness -ModInteger negative rounding convention -ModInteger remainder modulo one is zero -ModInteger zero divisor rejects every dividend -MultiplyInteger commutative -MultiplyInteger crosses signed 64-bit boundary -MultiplyInteger detects wrong opcode argument order or signedness -MultiplyInteger multiplicative identity -MultiplyInteger zero annihilates -QuotientInteger detects wrong opcode argument order or signedness -QuotientInteger division by one -QuotientInteger negative rounding convention -QuotientInteger zero divisor rejects every dividend -ReadBit index above 128 bits rejects without host overflow -ReadBit index equal to bit count rejects -ReadBit negative bit index rejects -RemainderInteger detects wrong opcode argument order or signedness -RemainderInteger negative rounding convention -RemainderInteger remainder modulo one is zero -RemainderInteger zero divisor rejects every dividend -ReplicateByte count above 8192 rejects -ReplicateByte invalid byte rejects -ReplicateByte negative byte rejects -ReplicateByte negative count rejects -SubtractInteger argument order -SubtractInteger detects wrong opcode argument order or signedness -SubtractInteger predecessor never wraps at any integer width -SubtractInteger right identity -SubtractInteger self subtraction is zero -TailList empty list rejects -WriteBits index above 128 bits rejects without host overflow -WriteBits invalid index rejects -WriteBits negative index rejects