From 41ac92869e6e4f4982d18d3c0c44f1cd0e4506d5 Mon Sep 17 00:00:00 2001 From: Jay Lorch Date: Wed, 22 Jul 2026 18:36:14 -0700 Subject: [PATCH 01/41] Start verifying Number --- src/lib.rs | 12 +- src/number.rs | 3 +- src/verify/bigint_assumptions.rs | 712 +++++++++++++++++++++++++++++++ src/verify/f64_assumptions.rs | 124 ++++++ src/verify/mod.rs | 5 + src/verify/number_specs.rs | 224 ++++++++++ src/verify/utils.rs | 65 +++ 7 files changed, 1140 insertions(+), 5 deletions(-) create mode 100644 src/verify/bigint_assumptions.rs create mode 100644 src/verify/f64_assumptions.rs create mode 100644 src/verify/mod.rs create mode 100644 src/verify/number_specs.rs create mode 100644 src/verify/utils.rs diff --git a/src/lib.rs b/src/lib.rs index 953d8bbe1..0f66140d4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,12 +1,16 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -// Unsafe code should not be used. +// Unsafe code should not be used, except during verification. // Hard to reason about correctness, and maintainability. -#![forbid(unsafe_code)] +// The `verus` feature is only used during verification, never in production +// builds, so the forbid remains in force for all shipped code. +#![cfg_attr(not(feature = "verus"), forbid(unsafe_code))] // Ensure that all lint names are valid. #![deny(unknown_lints)] // Fail-fast lints: correctness, safety, and API surface +#![cfg_attr(not(verus_keep_ghost), deny(dead_code))] // ban unused items +#![cfg_attr(not(verus_keep_ghost), deny(missing_debug_implementations))] // require Debug on public types #![deny( // Panic sources - catch all ways code can panic clippy::panic, // forbid explicit panic! macro @@ -21,7 +25,6 @@ clippy::panic_in_result_fn, // disallow panic inside functions returning Result // Rust warnings/upstream - dead_code, // ban unused items deprecated, // prevent use of deprecated APIs deprecated_in_future, // catch items scheduled for deprecation exported_private_dependencies, // avoid leaking private deps in public API @@ -29,7 +32,6 @@ invalid_doc_attributes, // ensure doc attributes are valid keyword_idents, // disallow identifiers that are keywords macro_use_extern_crate, // block legacy macro_use extern crate - missing_debug_implementations, // require Debug on public types // TODO: Address in future pass // missing_docs, // require docs on public items non_ascii_idents, // disallow non-ASCII identifiers @@ -125,6 +127,8 @@ mod compiler; mod engine; mod indexchecker; mod interpreter; +#[cfg(feature = "verus")] +mod verify; pub mod languages { #[cfg(feature = "azure_policy")] diff --git a/src/number.rs b/src/number.rs index 326fd1e4d..0c55bbeca 100644 --- a/src/number.rs +++ b/src/number.rs @@ -27,7 +27,6 @@ use num_traits::{One, Signed, ToPrimitive, Zero}; use serde::ser::Serializer; use serde::Serialize; -#[cfg(feature = "verus")] use vstd::prelude::*; use crate::*; @@ -37,6 +36,8 @@ pub type BigInt = NumBigInt; #[cfg_attr(feature = "verus", verus_verify)] const F64_SAFE_INTEGER: f64 = 9_007_199_254_740_992.0; // 2^53 +#[verus_verify] +#[verus_verify(external_derive)] #[derive(Clone)] pub enum Number { UInt(u64), diff --git a/src/verify/bigint_assumptions.rs b/src/verify/bigint_assumptions.rs new file mode 100644 index 000000000..0997f354b --- /dev/null +++ b/src/verify/bigint_assumptions.rs @@ -0,0 +1,712 @@ +// This file contains assumptions about the BigInt library, encoded +// in Verus. +// +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#![allow( + clippy::arithmetic_side_effects, + clippy::float_cmp, + clippy::unwrap_used, + clippy::unreachable, + clippy::option_if_let_else, + clippy::unseparated_literal_suffix, + clippy::as_conversions, + clippy::unused_trait_names, + clippy::pattern_type_mismatch +)] + +#[cfg(feature = "verus")] +use vstd::prelude::*; + +#[cfg(feature = "verus")] +verus! { + +use core::cmp::Ordering; +use num_bigint::BigInt; +use vstd::std_specs::cmp::OrdSpec; + +#[verifier::external_type_specification] +#[verifier::external_body] +pub struct ExNumBigInt(num_bigint::BigInt); + +pub assume_specification[ ::clone ](n: &BigInt) -> (res: BigInt) + ensures + res == n, +; + +pub trait BigIntAdditionalSpecFns { + spec fn view(&self) -> int; +} + +impl BigIntAdditionalSpecFns for BigInt { + uninterp spec fn view(&self) -> int; +} + +// Conditions + +pub assume_specification[ ::is_zero ](x: &BigInt) -> (res: bool) + ensures + res == (x@ == 0), +; + +pub assume_specification[ ::is_negative ](x: &BigInt) -> (res: bool) + ensures + res == (x@ < 0), +; + +// PartialEq + +pub axiom fn axiom_bigint_obeys_eq_spec() + ensures + ::obeys_eq_spec(), +; + +pub axiom fn axiom_bigint_obeys_partial_cmp_spec() + ensures + ::obeys_partial_cmp_spec(), +; + +pub assume_specification[ ::eq ](x: &BigInt, y: &BigInt) -> (res: bool) + ensures + res == (x@ == y@), +; + +// Ord + +pub axiom fn axiom_bigint_obeys_cmp_spec() + ensures + ::obeys_cmp_spec(), + forall|b1: &BigInt, b2: &BigInt| b1.cmp_spec(b2) == b1@.cmp_spec(&b2@), +; + +pub assume_specification[ ::cmp ](x: &BigInt, y: &BigInt) -> (res: Ordering) + ensures + res == x@.cmp_spec(&y@), +; + +// From + +pub assume_specification[ >::from ](i: i64) -> (res: BigInt) + ensures + res@ == i, +; + +pub assume_specification[ >::from ](i: i128) -> (res: BigInt) + ensures + res@ == i, +; + +pub assume_specification[ >::from ](u: u64) -> (res: BigInt) + ensures + res@ == u, +; + +pub assume_specification[ >::from ](u: u128) -> (res: BigInt) + ensures + res@ == u, +; + +// Negation + +pub assume_specification[ ::neg ](x: BigInt) -> (y: BigInt) + ensures + y@ == -x@, +; + +// Addition + +pub assume_specification[ ::add ](x: BigInt, y: BigInt) -> (o: BigInt) + ensures + o@ == x@ + y@, +; + +pub assume_specification<'a>[ >::add ](x: BigInt, y: &BigInt) -> (o: BigInt) + ensures + o@ == x@ + (*y)@, +; + +pub assume_specification<'a, 'b>[ <&BigInt as core::ops::Add<&BigInt>>::add ](x: &'b BigInt, y: &BigInt) -> (o: BigInt) + ensures + o@ == (*x)@ + (*y)@, +; + +pub assume_specification[ >::add ](x: BigInt, y: u8) -> (o: BigInt) + ensures + o@ == x@ + y, +; + +pub assume_specification[ >::add ](x: BigInt, y: u16) -> (o: BigInt) + ensures + o@ == x@ + y, +; + +pub assume_specification[ >::add ](x: BigInt, y: u32) -> (o: BigInt) + ensures + o@ == x@ + y, +; + +pub assume_specification[ >::add ](x: BigInt, y: u64) -> (o: BigInt) + ensures + o@ == x@ + y, +; + +pub assume_specification[ >::add ](x: BigInt, y: u128) -> (o: BigInt) + ensures + o@ == x@ + y, +; + +pub assume_specification[ >::add ](x: BigInt, y: i8) -> (o: BigInt) + ensures + o@ == x@ + y, +; + +pub assume_specification[ >::add ](x: BigInt, y: i16) -> (o: BigInt) + ensures + o@ == x@ + y, +; + +pub assume_specification[ >::add ](x: BigInt, y: i32) -> (o: BigInt) + ensures + o@ == x@ + y, +; + +pub assume_specification[ >::add ](x: BigInt, y: i64) -> (o: BigInt) + ensures + o@ == x@ + y, +; + +pub assume_specification[ >::add ](x: BigInt, y: i128) -> (o: BigInt) + ensures + o@ == x@ + y, +; + +pub assume_specification<'a>[ >::add ](x: BigInt, y: &u8) -> (o: BigInt) + ensures + o@ == x@ + *y, +; + +pub assume_specification<'a>[ >::add ](x: BigInt, y: &u16) -> (o: BigInt) + ensures + o@ == x@ + *y, +; + +pub assume_specification<'a>[ >::add ](x: BigInt, y: &u32) -> (o: BigInt) + ensures + o@ == x@ + *y, +; + +pub assume_specification<'a>[ >::add ](x: BigInt, y: &u64) -> (o: BigInt) + ensures + o@ == x@ + *y, +; + +pub assume_specification<'a>[ >::add ](x: BigInt, y: &u128) -> (o: BigInt) + ensures + o@ == x@ + *y, +; + +pub assume_specification<'a>[ >::add ](x: BigInt, y: &i8) -> (o: BigInt) + ensures + o@ == x@ + *y, +; + +pub assume_specification<'a>[ >::add ](x: BigInt, y: &i16) -> (o: BigInt) + ensures + o@ == x@ + *y, +; + +pub assume_specification<'a>[ >::add ](x: BigInt, y: &i32) -> (o: BigInt) + ensures + o@ == x@ + *y, +; + +pub assume_specification<'a>[ >::add ](x: BigInt, y: &i64) -> (o: BigInt) + ensures + o@ == x@ + *y, +; + +pub assume_specification<'a>[ >::add ](x: BigInt, y: &i128) -> (o: BigInt) + ensures + o@ == x@ + *y, +; + +// Subtraction + +pub assume_specification[ ::sub ](x: BigInt, y: BigInt) -> (o: BigInt) + ensures + o@ == x@ - y@, +; + +pub assume_specification<'a>[ >::sub ](x: BigInt, y: &BigInt) -> (o: BigInt) + ensures + o@ == x@ - (*y)@, +; + +pub assume_specification<'a, 'b>[ <&BigInt as core::ops::Sub<&BigInt>>::sub ](x: &'b BigInt, y: &BigInt) -> (o: BigInt) + ensures + o@ == (*x)@ - (*y)@, +; + +pub assume_specification[ >::sub ](x: BigInt, y: u8) -> (o: BigInt) + ensures + o@ == x@ - y, +; + +pub assume_specification[ >::sub ](x: BigInt, y: u16) -> (o: BigInt) + ensures + o@ == x@ - y, +; + +pub assume_specification[ >::sub ](x: BigInt, y: u32) -> (o: BigInt) + ensures + o@ == x@ - y, +; + +pub assume_specification[ >::sub ](x: BigInt, y: u64) -> (o: BigInt) + ensures + o@ == x@ - y, +; + +pub assume_specification[ >::sub ](x: BigInt, y: u128) -> (o: BigInt) + ensures + o@ == x@ - y, +; + +pub assume_specification[ >::sub ](x: BigInt, y: i8) -> (o: BigInt) + ensures + o@ == x@ - y, +; + +pub assume_specification[ >::sub ](x: BigInt, y: i16) -> (o: BigInt) + ensures + o@ == x@ - y, +; + +pub assume_specification[ >::sub ](x: BigInt, y: i32) -> (o: BigInt) + ensures + o@ == x@ - y, +; + +pub assume_specification[ >::sub ](x: BigInt, y: i64) -> (o: BigInt) + ensures + o@ == x@ - y, +; + +pub assume_specification[ >::sub ](x: BigInt, y: i128) -> (o: BigInt) + ensures + o@ == x@ - y, +; + +pub assume_specification<'a>[ >::sub ](x: BigInt, y: &u8) -> (o: BigInt) + ensures + o@ == x@ - *y, +; + +pub assume_specification<'a>[ >::sub ](x: BigInt, y: &u16) -> (o: BigInt) + ensures + o@ == x@ - *y, +; + +pub assume_specification<'a>[ >::sub ](x: BigInt, y: &u32) -> (o: BigInt) + ensures + o@ == x@ - *y, +; + +pub assume_specification<'a>[ >::sub ](x: BigInt, y: &u64) -> (o: BigInt) + ensures + o@ == x@ - *y, +; + +pub assume_specification<'a>[ >::sub ](x: BigInt, y: &u128) -> (o: BigInt) + ensures + o@ == x@ - *y, +; + +pub assume_specification<'a>[ >::sub ](x: BigInt, y: &i8) -> (o: BigInt) + ensures + o@ == x@ - *y, +; + +pub assume_specification<'a>[ >::sub ](x: BigInt, y: &i16) -> (o: BigInt) + ensures + o@ == x@ - *y, +; + +pub assume_specification<'a>[ >::sub ](x: BigInt, y: &i32) -> (o: BigInt) + ensures + o@ == x@ - *y, +; + +pub assume_specification<'a>[ >::sub ](x: BigInt, y: &i64) -> (o: BigInt) + ensures + o@ == x@ - *y, +; + +pub assume_specification<'a>[ >::sub ](x: BigInt, y: &i128) -> (o: BigInt) + ensures + o@ == x@ - *y, +; + +// Multiplication + +pub assume_specification[ ::mul ](x: BigInt, y: BigInt) -> (o: BigInt) + ensures + o@ == x@ * y@, +; + +pub assume_specification<'a>[ >::mul ](x: BigInt, y: &BigInt) -> (o: BigInt) + ensures + o@ == x@ * (*y)@, +; + +pub assume_specification<'a, 'b>[ <&BigInt as core::ops::Mul<&BigInt>>::mul ](x: &'b BigInt, y: &BigInt) -> (o: BigInt) + ensures + o@ == (*x)@ * (*y)@, +; + +pub assume_specification[ >::mul ](x: BigInt, y: u8) -> (o: BigInt) + ensures + o@ == x@ * y, +; + +pub assume_specification[ >::mul ](x: BigInt, y: u16) -> (o: BigInt) + ensures + o@ == x@ * y, +; + +pub assume_specification[ >::mul ](x: BigInt, y: u32) -> (o: BigInt) + ensures + o@ == x@ * y, +; + +pub assume_specification[ >::mul ](x: BigInt, y: u64) -> (o: BigInt) + ensures + o@ == x@ * y, +; + +pub assume_specification[ >::mul ](x: BigInt, y: u128) -> (o: BigInt) + ensures + o@ == x@ * y, +; + +pub assume_specification[ >::mul ](x: BigInt, y: i8) -> (o: BigInt) + ensures + o@ == x@ * y, +; + +pub assume_specification[ >::mul ](x: BigInt, y: i16) -> (o: BigInt) + ensures + o@ == x@ * y, +; + +pub assume_specification[ >::mul ](x: BigInt, y: i32) -> (o: BigInt) + ensures + o@ == x@ * y, +; + +pub assume_specification[ >::mul ](x: BigInt, y: i64) -> (o: BigInt) + ensures + o@ == x@ * y, +; + +pub assume_specification[ >::mul ](x: BigInt, y: i128) -> (o: BigInt) + ensures + o@ == x@ * y, +; + +pub assume_specification<'a>[ >::mul ](x: BigInt, y: &u8) -> (o: BigInt) + ensures + o@ == x@ * *y, +; + +// Division + +pub assume_specification[ ::div ](x: BigInt, y: BigInt) -> (o: BigInt) + ensures + o@ == x@ / y@, +; + +pub assume_specification<'a>[ >::div ](x: BigInt, y: &BigInt) -> (o: BigInt) + ensures + o@ == x@ / (*y)@, +; + +pub assume_specification<'a, 'b>[ <&BigInt as core::ops::Div<&BigInt>>::div ](x: &'b BigInt, y: &BigInt) -> (o: BigInt) + ensures + o@ == (*x)@ / (*y)@, +; + +pub assume_specification[ >::div ](x: BigInt, y: u8) -> (o: BigInt) + ensures + o@ == x@ / (y as int), +; + +pub assume_specification[ >::div ](x: BigInt, y: u16) -> (o: BigInt) + ensures + o@ == x@ / (y as int), +; + +pub assume_specification[ >::div ](x: BigInt, y: u32) -> (o: BigInt) + ensures + o@ == x@ / (y as int), +; + +pub assume_specification[ >::div ](x: BigInt, y: u64) -> (o: BigInt) + ensures + o@ == x@ / (y as int), +; + +pub assume_specification[ >::div ](x: BigInt, y: u128) -> (o: BigInt) + ensures + o@ == x@ / (y as int), +; + +pub assume_specification[ >::div ](x: BigInt, y: i8) -> (o: BigInt) + ensures + o@ == x@ / (y as int), +; + +pub assume_specification[ >::div ](x: BigInt, y: i16) -> (o: BigInt) + ensures + o@ == x@ / (y as int), +; + +pub assume_specification[ >::div ](x: BigInt, y: i32) -> (o: BigInt) + ensures + o@ == x@ / (y as int), +; + +pub assume_specification[ >::div ](x: BigInt, y: i64) -> (o: BigInt) + ensures + o@ == x@ / (y as int), +; + +pub assume_specification[ >::div ](x: BigInt, y: i128) -> (o: BigInt) + ensures + o@ == x@ / (y as int), +; + +pub assume_specification<'a>[ >::div ](x: BigInt, y: &u8) -> (o: BigInt) + ensures + o@ == x@ / (*y as int), +; + +} // end verus! + +// Verus's encoding of ToPrimitive relies on an unstable feature +// `sized_hierarchy`, so we can only talk about it when verifying. +// So, we wrap it all in `#[cfg(verus_keep_ghost)]`. + +#[cfg(verus_keep_ghost)] +verus! { + +// ToPrimitive + +#[verifier::external_trait_specification] +#[verifier::external_trait_extension(ToPrimitiveSpec via ToPrimitiveSpecImpl)] +pub trait ExToPrimitive { + type ExternalTraitSpecificationFor: num_traits::ToPrimitive; + + spec fn obeys_to_primitive_spec() -> bool; + + spec fn spec_to_int(&self) -> Option; + + fn to_isize(&self) -> (res: Option) + ensures + Self::obeys_to_primitive_spec() ==> + match (self.spec_to_int(), res) { + (None, None) => true, + (None, Some(_)) => false, + (Some(n1), Some(n2)) => n1 == n2, + (Some(n), None) => !(isize::MIN <= n <= isize::MAX), + }, + default_ensures + true, + ; + + fn to_i8(&self) -> (res: Option) + ensures + Self::obeys_to_primitive_spec() ==> + match (self.spec_to_int(), res) { + (None, None) => true, + (None, Some(_)) => false, + (Some(n1), Some(n2)) => n1 == n2, + (Some(n), None) => !(i8::MIN <= n <= i8::MAX), + }, + default_ensures + true, + ; + + fn to_i16(&self) -> (res: Option) + ensures + Self::obeys_to_primitive_spec() ==> + match (self.spec_to_int(), res) { + (None, None) => true, + (None, Some(_)) => false, + (Some(n1), Some(n2)) => n1 == n2, + (Some(n), None) => !(i16::MIN <= n <= i16::MAX), + }, + default_ensures + true, + ; + + fn to_i32(&self) -> (res: Option) + ensures + Self::obeys_to_primitive_spec() ==> + match (self.spec_to_int(), res) { + (None, None) => true, + (None, Some(_)) => false, + (Some(n1), Some(n2)) => n1 == n2, + (Some(n), None) => !(i32::MIN <= n <= i32::MAX), + }, + default_ensures + true, + ; + + fn to_i64(&self) -> (res: Option) + ensures + Self::obeys_to_primitive_spec() ==> + match (self.spec_to_int(), res) { + (None, None) => true, + (None, Some(_)) => false, + (Some(n1), Some(n2)) => n1 == n2, + (Some(n), None) => !(i64::MIN <= n <= i64::MAX), + }, + ; + + fn to_i128(&self) -> (res: Option) + ensures + Self::obeys_to_primitive_spec() ==> + match (self.spec_to_int(), res) { + (None, None) => true, + (None, Some(_)) => false, + (Some(n1), Some(n2)) => n1 == n2, + (Some(n), None) => !(i128::MIN <= n <= i128::MAX), + }, + default_ensures + true, + ; + + fn to_usize(&self) -> (res: Option) + ensures + Self::obeys_to_primitive_spec() ==> + match (self.spec_to_int(), res) { + (None, None) => true, + (None, Some(_)) => false, + (Some(n1), Some(n2)) => n1 == n2, + (Some(n), None) => !(usize::MIN <= n <= usize::MAX), + }, + default_ensures + true, + ; + + fn to_u8(&self) -> (res: Option) + ensures + Self::obeys_to_primitive_spec() ==> + match (self.spec_to_int(), res) { + (None, None) => true, + (None, Some(_)) => false, + (Some(n1), Some(n2)) => n1 == n2, + (Some(n), None) => !(u8::MIN <= n <= u8::MAX), + }, + default_ensures + true, + ; + + fn to_u16(&self) -> (res: Option) + ensures + Self::obeys_to_primitive_spec() ==> + match (self.spec_to_int(), res) { + (None, None) => true, + (None, Some(_)) => false, + (Some(n1), Some(n2)) => n1 == n2, + (Some(n), None) => !(u16::MIN <= n <= u16::MAX), + }, + default_ensures + true, + ; + + fn to_u32(&self) -> (res: Option) + ensures + Self::obeys_to_primitive_spec() ==> + match (self.spec_to_int(), res) { + (None, None) => true, + (None, Some(_)) => false, + (Some(n1), Some(n2)) => n1 == n2, + (Some(n), None) => !(u32::MIN <= n <= u32::MAX), + }, + default_ensures + true, + ; + + fn to_u64(&self) -> (res: Option) + ensures + Self::obeys_to_primitive_spec() ==> + match (self.spec_to_int(), res) { + (None, None) => true, + (None, Some(_)) => false, + (Some(n1), Some(n2)) => n1 == n2, + (Some(n), None) => !(u64::MIN <= n <= u64::MAX), + }, + ; + + fn to_u128(&self) -> (res: Option) + ensures + Self::obeys_to_primitive_spec() ==> + match (self.spec_to_int(), res) { + (None, None) => true, + (None, Some(_)) => false, + (Some(n1), Some(n2)) => n1 == n2, + (Some(n), None) => !(u128::MIN <= n <= u128::MAX), + }, + default_ensures + true, + ; + + spec fn spec_to_f32(&self) -> Option; + + fn to_f32(&self) -> (res: Option) + ensures + Self::obeys_to_primitive_spec() ==> res == self.spec_to_f32(), + default_ensures + true, + ; + + spec fn spec_to_f64(&self) -> Option; + + fn to_f64(&self) -> (res: Option) + ensures + Self::obeys_to_primitive_spec() ==> res == self.spec_to_f64(), + default_ensures + true, + ; +} + +impl ToPrimitiveSpecImpl for num_bigint::BigInt +{ + open spec fn obeys_to_primitive_spec() -> bool + { + true + } + + open spec fn spec_to_int(&self) -> Option + { + Some(self@) + } + + uninterp spec fn spec_to_f32(&self) -> Option; + + uninterp spec fn spec_to_f64(&self) -> Option; +} + +// These are the methods of ToPrimitive that BigInt implements because there is no default in ToPrimitive +pub assume_specification[ ::to_i64 ](x: &BigInt) -> (res: Option); +pub assume_specification[ ::to_u64 ](x: &BigInt) -> (res: Option); + +// These are the methods of ToPrimitive that BigInt overrides the defaults for because they'd otherwise be wrong +pub assume_specification[ ::to_i128 ](x: &BigInt) -> (res: Option); +pub assume_specification[ ::to_u128 ](x: &BigInt) -> (res: Option); +pub assume_specification[ ::to_f32 ](x: &BigInt) -> (res: Option); +pub assume_specification[ ::to_f64 ](x: &BigInt) -> (res: Option); + +} // end verus! hidden by cfg(verus_keep_ghost) diff --git a/src/verify/f64_assumptions.rs b/src/verify/f64_assumptions.rs new file mode 100644 index 000000000..524c25277 --- /dev/null +++ b/src/verify/f64_assumptions.rs @@ -0,0 +1,124 @@ +// This file contains assumptions about `f64`, encoded in Verus. +// +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#![allow( + clippy::arithmetic_side_effects, + clippy::float_cmp, + clippy::unwrap_used, + clippy::unreachable, + clippy::option_if_let_else, + clippy::unseparated_literal_suffix, + clippy::as_conversions, + clippy::unused_trait_names, + clippy::pattern_type_mismatch +)] + +#[cfg(feature = "verus")] +use vstd::prelude::*; + +#[cfg(feature = "verus")] +verus! { + +use vstd::float::*; +use vstd::std_specs::cmp::PartialEqIs; +use vstd::std_specs::cmp::PartialOrdIs; + +pub axiom fn axiom_f64_obeys_eq_spec() + ensures + ::obeys_eq_spec(), +; + +pub axiom fn axiom_f64_obeys_partial_cmp_spec() + ensures + ::obeys_partial_cmp_spec(), +; + +pub axiom fn axiom_f64_comparisons_match_ieee() + ensures + forall|f1: f64, f2: f64| #[trigger] f1.ieee_lt(f2) <==> f1.is_lt(&f2), + forall|f1: f64, f2: f64| #[trigger] f1.ieee_le(f2) <==> f1.is_le(&f2), + forall|f1: f64, f2: f64| #[trigger] f1.ieee_gt(f2) <==> f1.is_gt(&f2), + forall|f1: f64, f2: f64| #[trigger] f1.ieee_ge(f2) <==> f1.is_ge(&f2), +; + +pub axiom fn axiom_f64_ops_deterministic() + ensures + ::obeys_neg_spec(), + ::obeys_add_spec(), + ::obeys_sub_spec(), + ::obeys_mul_spec(), + ::obeys_div_spec(), + forall|n: i8, f: f64| float_cast_spec::(n, f) ==> f == ieee_float_cast::(n), + forall|n: u8, f: f64| float_cast_spec::(n, f) ==> f == ieee_float_cast::(n), + forall|n: i8, f: f64| float_cast_spec::(f, n) ==> n == ieee_float_cast::(f), + forall|n: u8, f: f64| float_cast_spec::(f, n) ==> n == ieee_float_cast::(f), + forall|n: i16, f: f64| float_cast_spec::(n, f) ==> f == ieee_float_cast::(n), + forall|n: u16, f: f64| float_cast_spec::(n, f) ==> f == ieee_float_cast::(n), + forall|n: i16, f: f64| float_cast_spec::(f, n) ==> n == ieee_float_cast::(f), + forall|n: u16, f: f64| float_cast_spec::(f, n) ==> n == ieee_float_cast::(f), + forall|n: i32, f: f64| float_cast_spec::(n, f) ==> f == ieee_float_cast::(n), + forall|n: u32, f: f64| float_cast_spec::(n, f) ==> f == ieee_float_cast::(n), + forall|n: i32, f: f64| float_cast_spec::(f, n) ==> n == ieee_float_cast::(f), + forall|n: u32, f: f64| float_cast_spec::(f, n) ==> n == ieee_float_cast::(f), + forall|n: i64, f: f64| float_cast_spec::(n, f) ==> f == ieee_float_cast::(n), + forall|n: u64, f: f64| float_cast_spec::(n, f) ==> f == ieee_float_cast::(n), + forall|n: i64, f: f64| float_cast_spec::(f, n) ==> n == ieee_float_cast::(f), + forall|n: u64, f: f64| float_cast_spec::(f, n) ==> n == ieee_float_cast::(f), + forall|n: i128, f: f64| float_cast_spec::(n, f) ==> f == ieee_float_cast::(n), + forall|n: u128, f: f64| float_cast_spec::(n, f) ==> f == ieee_float_cast::(n), + forall|n: i128, f: f64| float_cast_spec::(f, n) ==> n == ieee_float_cast::(f), + forall|n: u128, f: f64| float_cast_spec::(f, n) ==> n == ieee_float_cast::(f), +; + +pub assume_specification [ f64::is_finite ](f: f64) -> (res: bool) + ensures + res == f.is_finite_spec(), +; + +pub uninterp spec fn spec_f64_fract(f: f64) -> f64; + +pub assume_specification [ f64::fract ](f: f64) -> (res: f64) + requires + f.is_finite_spec(), + ensures + res == spec_f64_fract(f), +; + +pub uninterp spec fn spec_f64_abs(f: f64) -> f64; + +pub assume_specification [ f64::abs ](f: f64) -> (res: f64) + requires + f.is_finite_spec(), + ensures + res == spec_f64_abs(f), +; + +pub assume_specification [ f64::is_nan ](f: f64) -> (res: bool) + ensures + res == f.is_nan_spec(), +; + +pub uninterp spec fn spec_f64_neg_infinity() -> f64; + +#[inline] +#[verifier::external_body] +pub fn f64_neg_infinity() -> (res: f64) + ensures + res == spec_f64_neg_infinity(), +{ + f64::NEG_INFINITY +} + +pub uninterp spec fn spec_f64_infinity() -> f64; + +#[inline] +#[verifier::external_body] +pub fn f64_infinity() -> (res: f64) + ensures + res == spec_f64_infinity(), +{ + f64::INFINITY +} + +} // end verus! diff --git a/src/verify/mod.rs b/src/verify/mod.rs new file mode 100644 index 000000000..c656e0292 --- /dev/null +++ b/src/verify/mod.rs @@ -0,0 +1,5 @@ +pub(crate) mod bigint_assumptions; +pub(crate) mod f64_assumptions; +pub(crate) mod utils; +pub mod number_specs; + diff --git a/src/verify/number_specs.rs b/src/verify/number_specs.rs new file mode 100644 index 000000000..bf6c658a4 --- /dev/null +++ b/src/verify/number_specs.rs @@ -0,0 +1,224 @@ +// This file contains specifications for `Number` and its methods. +// +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#![allow( + clippy::arithmetic_side_effects, + clippy::float_cmp, + clippy::unwrap_used, + clippy::unreachable, + clippy::option_if_let_else, + clippy::unseparated_literal_suffix, + clippy::as_conversions, + clippy::unused_trait_names, + clippy::pattern_type_mismatch +)] + +#[cfg(feature = "verus")] +use vstd::prelude::*; + +#[cfg(feature = "verus")] +verus! { + +use core::cmp::Ordering; +use crate::number::*; +use super::bigint_assumptions::*; +use super::f64_assumptions::*; +use vstd::float::*; +use vstd::std_specs::cmp::*; +use vstd::std_specs::convert::*; + +pub assume_specification[ ::clone ](n: &Number) -> (res: Number) + ensures + res == n, +; + +pub enum NumberView { + Integer(int), + Float(f64), +} + +impl View for Number +{ + type V = NumberView; + + open spec fn view(&self) -> NumberView + { + match self { + Number::UInt(n) => NumberView::Integer(n as int), + Number::Int(n) => NumberView::Integer(n as int), + Number::Float(f) => NumberView::Float(*f), + Number::BigInt(b) => NumberView::Integer(b@), + } + } +} + +pub open spec fn float_to_small_int(value: f64) -> Option +{ + if !value.is_finite_spec() || + !spec_f64_fract(value).eq_spec(&0.0f64) || + spec_f64_abs(value) > 9_007_199_254_740_992.0 { + None + } + else if value >= 0.0 { + if ieee_float_cast::(ieee_float_cast::(value)).eq_spec(&value) { + Some(ieee_float_cast::(value) as int) + } + else { + None + } + } + else { + if ieee_float_cast::(ieee_float_cast::(value)).eq_spec(&value) { + Some(ieee_float_cast::(value) as int) + } + else { + None + } + } +} + +impl NumberView { + pub open spec fn to_int(&self) -> Option + { + match *self { + Self::Integer(n) => Some(n), + Self::Float(f) => float_to_small_int(f), + } + } + + pub open spec fn to_f64_lossy_ensures(self: Self, f: f64) -> bool + { + match self { + NumberView::Integer(v) => + { + ||| 0 <= v <= u64::MAX && f == ieee_float_cast::(v as u64) + ||| i64::MIN <= v <= i64::MAX && f == ieee_float_cast::(v as i64) + ||| exists|bi: BigInt| { + &&& bi@ == v + &&& match #[trigger] super::bigint_assumptions::ToPrimitiveSpec::spec_to_f64(&bi) { + Some(x) => f == x, + None => f == if v < 0 { spec_f64_neg_infinity() } else { spec_f64_infinity() } + } + } + }, + NumberView::Float(v) => f == v, + } + } +} + +impl FromSpecImpl for Number { + open spec fn obeys_from_spec() -> bool + { + false + } + + uninterp spec fn from_spec(v: BigInt) -> Number; +} + +impl FromSpecImpl for Number { + open spec fn obeys_from_spec() -> bool + { + false + } + + uninterp spec fn from_spec(v: u64) -> Number; +} + +impl FromSpecImpl for Number { + open spec fn obeys_from_spec() -> bool + { + false + } + + uninterp spec fn from_spec(v: usize) -> Number; +} + +impl FromSpecImpl for Number { + open spec fn obeys_from_spec() -> bool + { + false + } + + uninterp spec fn from_spec(v: u128) -> Number; +} + +impl FromSpecImpl for Number { + open spec fn obeys_from_spec() -> bool + { + false + } + + uninterp spec fn from_spec(v: i64) -> Number; +} + +impl FromSpecImpl for Number { + open spec fn obeys_from_spec() -> bool + { + false + } + + uninterp spec fn from_spec(v: i128) -> Number; +} + +impl FromSpecImpl for Number { + open spec fn obeys_from_spec() -> bool + { + false + } + + uninterp spec fn from_spec(v: f64) -> Number; +} + +impl PartialEqSpecImpl for Number { + open spec fn obeys_eq_spec() -> bool + { + false + } + + open spec fn eq_spec(&self, other: &Self) -> bool + { + *self == *other + } +} + +impl Number { + spec fn spec_to_f64_lossy(&self) -> f64 + { + match *self { + Number::UInt(v) => ieee_float_cast::(v), + Number::Int(v) => ieee_float_cast::(v), + Number::Float(v) => v, + Number::BigInt(v) => { + if let Some(f) = ::spec_to_f64(&v) { + f + } else if v@ < 0 { + spec_f64_neg_infinity() + } else { + spec_f64_infinity() + } + }, + } + } +} + +impl OrdSpecImpl for Number { + open spec fn obeys_cmp_spec() -> bool + { + true + } + + closed spec fn cmp_spec(&self, other: &Self) -> Ordering + { + match (self@.to_int(), other@.to_int()) { + (Some(n1), Some(n2)) => n1.cmp_spec(&n2), + _ => { + let f1 = self.spec_to_f64_lossy(); + let f2 = self.spec_to_f64_lossy(); + f1.partial_cmp_spec(&f2).unwrap_or(Ordering::Equal) + }, + } + } +} + +} // end verus! diff --git a/src/verify/utils.rs b/src/verify/utils.rs new file mode 100644 index 000000000..a6d0f9c08 --- /dev/null +++ b/src/verify/utils.rs @@ -0,0 +1,65 @@ +use anyhow::{bail, Result}; +use std::format; +use std::string::String; + +use vstd::prelude::*; + +verus! { + +#[cfg(verus_keep_ghost)] +#[verifier::external_body] +pub fn verus_format_helper() -> String +{ + format!("who cares") +} + +macro_rules! verus_format { + ( $( $tt0:tt )* ) => { + { + #[cfg(not(verus_keep_ghost))] + { format!($($tt0)*) } + #[cfg(verus_keep_ghost)] + { verus_format_helper() } + } + } +} + +#[allow(dead_code)] +fn my_test_verus_format(fcn: &'static str, x: u32) -> String +{ + verus_format!("The parameters are `{fcn}` and `{x}`") +} + +#[cfg(verus_keep_ghost)] +#[verifier::external_type_specification] +#[verifier::external_body] +pub struct ExAnyhowError(anyhow::Error); + +#[cfg(verus_keep_ghost)] +#[verifier::external_body] +pub fn verus_bail_helper() -> Result +{ + bail!("who cares") +} + +macro_rules! verus_bail { + ( $( $tt0:tt )* ) => { + { + #[cfg(not(verus_keep_ghost))] + { bail!($($tt0)*) } + #[cfg(verus_keep_ghost)] + { return verus_bail_helper(); } + } + } +} + +#[allow(dead_code)] +fn my_test_verus_bail(fcn: &'static str, x: u32) -> Result<()> +{ + if x > 0 { + verus_bail!("Invalid parameters `{}` and `{}`", fcn, x) + } + Ok(()) +} + +} // end verus! From 855567d59db00f1df8c930d42557eadcdff3fd96 Mon Sep 17 00:00:00 2001 From: Jay Lorch Date: Wed, 22 Jul 2026 21:08:28 -0700 Subject: [PATCH 02/41] Prove much of number.rs --- src/number.rs | 155 ++++++++++++++++++++++++++++++++++ src/verify/f64_assumptions.rs | 20 ++--- src/verify/number_specs.rs | 6 +- 3 files changed, 164 insertions(+), 17 deletions(-) diff --git a/src/number.rs b/src/number.rs index 0c55bbeca..aa990593a 100644 --- a/src/number.rs +++ b/src/number.rs @@ -28,8 +28,20 @@ use serde::ser::Serializer; use serde::Serialize; use vstd::prelude::*; +#[cfg(feature = "verus")] +use vstd::float::*; +#[cfg(feature = "verus")] +use vstd::std_specs::cmp::*; +#[cfg(feature = "verus")] +use vstd::std_specs::convert::*; use crate::*; +#[cfg(feature = "verus")] +use crate::verify::bigint_assumptions::*; +#[cfg(feature = "verus")] +use crate::verify::f64_assumptions::*; +#[cfg(feature = "verus")] +use crate::verify::number_specs::*; pub type BigInt = NumBigInt; @@ -46,7 +58,12 @@ pub enum Number { BigInt(Rc), } +#[verus_verify] impl Number { + #[verus_spec(result => + ensures + result@ == NumberView::Integer(value@), + )] fn from_bigint_owned(value: BigInt) -> Self { if value.is_zero() { return Number::Int(0); @@ -65,6 +82,10 @@ impl Number { Number::BigInt(Rc::new(value)) } + #[verus_spec(result => + ensures + result@ == NumberView::Integer(value as int), + )] fn from_i128(value: i128) -> Self { if value >= 0 { if let Ok(u) = u64::try_from(value) { @@ -79,6 +100,19 @@ impl Number { } } + #[verus_spec(result => + ensures + match self@ { + NumberView::Integer(n) => result matches Some(bi) && bi@ == n, + NumberView::Float(f) => + { + match result { + Some(bi) => float_to_small_int(f) == Some(bi@), + None => float_to_small_int(f) is None, + } + }, + }, + )] fn to_bigint_owned(&self) -> Option { match self { Number::UInt(v) => Some(BigInt::from(*v)), @@ -88,7 +122,21 @@ impl Number { } } + #[verus_spec(result => + ensures + match result { + Some(bi) => float_to_small_int(value) == Some(bi@), + None => float_to_small_int(value) is None, + }, + )] fn float_to_small_bigint(value: f64) -> Option { + proof! { + axiom_f64_obeys_eq_spec(); + axiom_f64_obeys_partial_cmp_spec(); + axiom_f64_ops_deterministic(); + axiom_f64_comparisons_match_ieee(); + } + if !value.is_finite() || value.fract() != 0.0 { return None; } @@ -112,6 +160,17 @@ impl Number { None } + #[verus_spec(result => + ensures + match self@ { + NumberView::Integer(n) => result matches Some(bi) && bi@ == n, + NumberView::Float(f) => + match result { + Some(bi) => float_to_small_int(f) == Some(bi@), + None => float_to_small_int(f) is None, + }, + }, + )] fn to_bigint_rc(&self) -> Option> { match self { Number::BigInt(v) => Some(v.clone()), @@ -119,7 +178,13 @@ impl Number { } } + #[verus_spec(result => + ensures + self@.to_f64_lossy_ensures(result), + result == self.spec_to_f64_lossy(), + )] fn to_f64_lossy(&self) -> f64 { + proof! { axiom_f64_ops_deterministic(); } match self { Number::UInt(v) => *v as f64, Number::Int(v) => *v as f64, @@ -136,7 +201,15 @@ impl Number { } } + #[verus_spec(result => + ensures + match self@ { + NumberView::Integer(n) => result == (n == 0), + NumberView::Float(f) => result == f.eq_spec(&0.0f64), + }, + )] fn is_zero(&self) -> bool { + proof! { axiom_f64_obeys_eq_spec(); } match self { Number::UInt(0) | Number::Int(0) => true, Number::Float(f) => *f == 0.0, @@ -145,6 +218,13 @@ impl Number { } } + #[verus_spec(result => + ensures + match result@ { + NumberView::Integer(n) => float_to_small_int(value) == Some(n), + NumberView::Float(f) => float_to_small_int(value) is None && f == value, + } + )] fn normalize_float(value: f64) -> Number { if let Some(i) = Self::float_to_small_bigint(value) { return Self::from_bigint_owned(i); @@ -152,6 +232,13 @@ impl Number { Number::Float(value) } + #[verus_spec(result => + ensures + match self@ { + NumberView::Integer(v) => if 0 <= v <= u32::MAX { result == Some(v as u32) } else { result is None }, + NumberView::Float(_) => result is None, + }, + )] fn as_u32(&self) -> Option { match self { Number::UInt(v) if *v <= u32::MAX as u64 => Some(*v as u32), @@ -180,25 +267,45 @@ impl Serialize for Number { } } +#[verus_verify] impl From for Number { + #[verus_spec(result => + ensures + result@ == NumberView::Integer(value@), + )] fn from(value: BigInt) -> Self { Number::from_bigint_owned(value) } } +#[verus_verify] impl From for Number { + #[verus_spec(result => + ensures + result@ == NumberView::Integer(value as int), + )] fn from(value: u64) -> Self { Number::UInt(value) } } +#[verus_verify] impl From for Number { + #[verus_spec(result => + ensures + result@ == NumberView::Integer(value as int), + )] fn from(value: usize) -> Self { Number::UInt(value as u64) } } +#[verus_verify] impl From for Number { + #[verus_spec(result => + ensures + result@ == NumberView::Integer(value as int), + )] fn from(value: u128) -> Self { if let Ok(n) = u64::try_from(value) { Number::UInt(n) @@ -208,19 +315,34 @@ impl From for Number { } } +#[verus_verify] impl From for Number { + #[verus_spec(result => + ensures + result@ == NumberView::Integer(value as int), + )] fn from(value: i64) -> Self { Number::Int(value) } } +#[verus_verify] impl From for Number { + #[verus_spec(result => + ensures + result@ == NumberView::Integer(value as int), + )] fn from(value: i128) -> Self { Number::from_i128(value) } } +#[verus_verify] impl From for Number { + #[verus_spec(result => + ensures + result@ == NumberView::Float(value), + )] fn from(value: f64) -> Self { Number::Float(value) } @@ -288,8 +410,25 @@ impl FromStr for Number { } } +#[verus_verify] impl PartialEq for Number { + #[verus_spec(result => + ensures + match (self@.to_int(), other@.to_int()) { + (Some(n1), Some(n2)) => result == (n1 == n2), + _ => exists|f1: f64, f2: f64| #![trigger self@.to_f64_lossy_ensures(f1), other@.to_f64_lossy_ensures(f2)] { + &&& self@.to_f64_lossy_ensures(f1) + &&& other@.to_f64_lossy_ensures(f2) + &&& result == (!f1.is_nan_spec() && !f2.is_nan_spec() && f1.eq_spec(&f2)) + }, + }, + )] fn eq(&self, other: &Self) -> bool { + proof! { + axiom_bigint_obeys_eq_spec(); + axiom_f64_obeys_eq_spec(); + } + if let (Some(a), Some(b)) = (self.to_bigint_owned(), other.to_bigint_owned()) { return a == b; } @@ -305,8 +444,24 @@ impl PartialEq for Number { impl Eq for Number {} +#[verus_verify] impl Ord for Number { + #[verus_spec(result => + ensures + match (self@.to_int(), other@.to_int()) { + (Some(n1), Some(n2)) => result == n1.cmp_spec(&n2), + _ => exists|f1: f64, f2: f64| #![trigger self@.to_f64_lossy_ensures(f1), other@.to_f64_lossy_ensures(f2)] { + &&& self@.to_f64_lossy_ensures(f1) + &&& other@.to_f64_lossy_ensures(f2) + &&& result == f1.partial_cmp_spec(&f2).unwrap_or(Ordering::Equal) + }, + }, + )] fn cmp(&self, other: &Self) -> Ordering { + proof! { + axiom_f64_obeys_partial_cmp_spec(); + axiom_bigint_obeys_cmp_spec(); + } if let (Some(a), Some(b)) = (self.to_bigint_owned(), other.to_bigint_owned()) { return a.cmp(&b); } diff --git a/src/verify/f64_assumptions.rs b/src/verify/f64_assumptions.rs index 524c25277..9f0aea6d9 100644 --- a/src/verify/f64_assumptions.rs +++ b/src/verify/f64_assumptions.rs @@ -101,24 +101,16 @@ pub assume_specification [ f64::is_nan ](f: f64) -> (res: bool) pub uninterp spec fn spec_f64_neg_infinity() -> f64; -#[inline] -#[verifier::external_body] -pub fn f64_neg_infinity() -> (res: f64) +pub uninterp spec fn spec_f64_infinity() -> f64; + +pub assume_specification[ f64::NEG_INFINITY ] -> (res: f64) ensures res == spec_f64_neg_infinity(), -{ - f64::NEG_INFINITY -} - -pub uninterp spec fn spec_f64_infinity() -> f64; +; -#[inline] -#[verifier::external_body] -pub fn f64_infinity() -> (res: f64) +pub assume_specification[ f64::INFINITY ] -> (res: f64) ensures res == spec_f64_infinity(), -{ - f64::INFINITY -} +; } // end verus! diff --git a/src/verify/number_specs.rs b/src/verify/number_specs.rs index bf6c658a4..869e4a37e 100644 --- a/src/verify/number_specs.rs +++ b/src/verify/number_specs.rs @@ -183,7 +183,7 @@ impl PartialEqSpecImpl for Number { } impl Number { - spec fn spec_to_f64_lossy(&self) -> f64 + pub open spec fn spec_to_f64_lossy(&self) -> f64 { match *self { Number::UInt(v) => ieee_float_cast::(v), @@ -208,13 +208,13 @@ impl OrdSpecImpl for Number { true } - closed spec fn cmp_spec(&self, other: &Self) -> Ordering + open spec fn cmp_spec(&self, other: &Self) -> Ordering { match (self@.to_int(), other@.to_int()) { (Some(n1), Some(n2)) => n1.cmp_spec(&n2), _ => { let f1 = self.spec_to_f64_lossy(); - let f2 = self.spec_to_f64_lossy(); + let f2 = other.spec_to_f64_lossy(); f1.partial_cmp_spec(&f2).unwrap_or(Ordering::Equal) }, } From 2180cd6dafe78b4ee5faf427304bcf757626eef1 Mon Sep 17 00:00:00 2001 From: Jay Lorch Date: Thu, 23 Jul 2026 11:04:47 -0700 Subject: [PATCH 03/41] Make build work without Verus --- .gitignore | 3 +++ Cargo.lock | 5 +++++ Cargo.toml | 6 ++++++ bindings/ffi/Cargo.lock | 5 +++++ bindings/java/Cargo.lock | 5 +++++ bindings/python/Cargo.lock | 5 +++++ bindings/wasm/Cargo.lock | 5 +++++ src/lib.rs | 2 +- src/number.rs | 34 ++++++++++++++++++++------------- src/verify/mod.rs | 3 +-- verus-shim/Cargo.toml | 14 ++++++++++++++ verus-shim/src/lib.rs | 39 ++++++++++++++++++++++++++++++++++++++ 12 files changed, 110 insertions(+), 16 deletions(-) create mode 100644 verus-shim/Cargo.toml create mode 100644 verus-shim/src/lib.rs diff --git a/.gitignore b/.gitignore index 9634914ba..174ef0274 100644 --- a/.gitignore +++ b/.gitignore @@ -54,3 +54,6 @@ bindings/ruby/bin/ bindings/java/.classpath bindings/java/.project bindings/java/.settings/ + +# Emacs backup files +*~ diff --git a/Cargo.lock b/Cargo.lock index 3c4c6e871..811088b8f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1428,6 +1428,7 @@ dependencies = [ "rand", "regex", "regorus-mimalloc", + "regorus-verus-shim", "semver", "serde", "serde_json", @@ -1455,6 +1456,10 @@ dependencies = [ "cc", ] +[[package]] +name = "regorus-verus-shim" +version = "0.0.0" + [[package]] name = "rustversion" version = "1.0.23" diff --git a/Cargo.toml b/Cargo.toml index d43ff2c7c..91ab00aab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,7 @@ members = [ "tests/ensure_no_std", "xtask", + "verus-shim", ] [package] @@ -145,6 +146,11 @@ postcard = { version = "1.1.3", default-features = false, features = ["alloc"], # the crate's `std` feature additionally enables `vstd/std` (matching vstd's default features). vstd = { version = "=0.0.0-2026-07-12-0122", optional = true, default-features = false, features = ["alloc"] } +# No-op stand-ins for verus_verify/verus_spec/proof, used when the `verus` feature +# is disabled so the annotated source still compiles as ordinary Rust. This is a +# compile-time-only proc-macro crate and pulls in no runtime/verus dependencies. +regorus-verus-shim = { path = "verus-shim", version = "0.0.0" } + [dev-dependencies] anyhow = "1.0.102" cfg-if = "1.0.0" diff --git a/bindings/ffi/Cargo.lock b/bindings/ffi/Cargo.lock index e3055b830..7bc3ff501 100644 --- a/bindings/ffi/Cargo.lock +++ b/bindings/ffi/Cargo.lock @@ -1152,6 +1152,7 @@ dependencies = [ "rand", "regex", "regorus-mimalloc", + "regorus-verus-shim", "semver", "serde", "serde_json", @@ -1188,6 +1189,10 @@ dependencies = [ "cc", ] +[[package]] +name = "regorus-verus-shim" +version = "0.0.0" + [[package]] name = "rustix" version = "1.1.4" diff --git a/bindings/java/Cargo.lock b/bindings/java/Cargo.lock index 5cb2f2970..3e369765b 100644 --- a/bindings/java/Cargo.lock +++ b/bindings/java/Cargo.lock @@ -1024,6 +1024,7 @@ dependencies = [ "rand", "regex", "regorus-mimalloc", + "regorus-verus-shim", "semver", "serde", "serde_json", @@ -1059,6 +1060,10 @@ dependencies = [ "cc", ] +[[package]] +name = "regorus-verus-shim" +version = "0.0.0" + [[package]] name = "rustc_version" version = "0.4.1" diff --git a/bindings/python/Cargo.lock b/bindings/python/Cargo.lock index 909232e36..eaa4acc52 100644 --- a/bindings/python/Cargo.lock +++ b/bindings/python/Cargo.lock @@ -1032,6 +1032,7 @@ dependencies = [ "rand", "regex", "regorus-mimalloc", + "regorus-verus-shim", "semver", "serde", "serde_json", @@ -1057,6 +1058,10 @@ dependencies = [ "cc", ] +[[package]] +name = "regorus-verus-shim" +version = "0.0.0" + [[package]] name = "regoruspy" version = "0.11.0" diff --git a/bindings/wasm/Cargo.lock b/bindings/wasm/Cargo.lock index 3294ad4dd..481c43903 100644 --- a/bindings/wasm/Cargo.lock +++ b/bindings/wasm/Cargo.lock @@ -1022,6 +1022,7 @@ dependencies = [ "postcard", "rand", "regex", + "regorus-verus-shim", "semver", "serde", "serde_json", @@ -1033,6 +1034,10 @@ dependencies = [ "vstd", ] +[[package]] +name = "regorus-verus-shim" +version = "0.0.0" + [[package]] name = "regorusjs" version = "0.11.0" diff --git a/src/lib.rs b/src/lib.rs index 0f66140d4..d850c9221 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -127,7 +127,7 @@ mod compiler; mod engine; mod indexchecker; mod interpreter; -#[cfg(feature = "verus")] +#[cfg(verus_keep_ghost)] mod verify; pub mod languages { diff --git a/src/number.rs b/src/number.rs index aa990593a..21f6b5807 100644 --- a/src/number.rs +++ b/src/number.rs @@ -27,25 +27,29 @@ use num_traits::{One, Signed, ToPrimitive, Zero}; use serde::ser::Serializer; use serde::Serialize; -use vstd::prelude::*; +#[cfg(not(feature = "verus"))] +use regorus_verus_shim::{proof, verus_spec, verus_verify}; #[cfg(feature = "verus")] +use vstd::prelude::*; + +#[cfg(verus_keep_ghost)] +use crate::verify::bigint_assumptions::*; +#[cfg(verus_keep_ghost)] +use crate::verify::f64_assumptions::*; +#[cfg(verus_keep_ghost)] +use crate::verify::number_specs::*; +#[cfg(verus_keep_ghost)] use vstd::float::*; -#[cfg(feature = "verus")] +#[cfg(verus_keep_ghost)] use vstd::std_specs::cmp::*; -#[cfg(feature = "verus")] +#[cfg(verus_keep_ghost)] use vstd::std_specs::convert::*; use crate::*; -#[cfg(feature = "verus")] -use crate::verify::bigint_assumptions::*; -#[cfg(feature = "verus")] -use crate::verify::f64_assumptions::*; -#[cfg(feature = "verus")] -use crate::verify::number_specs::*; pub type BigInt = NumBigInt; -#[cfg_attr(feature = "verus", verus_verify)] +#[verus_verify] const F64_SAFE_INTEGER: f64 = 9_007_199_254_740_992.0; // 2^53 #[verus_verify] @@ -463,12 +467,16 @@ impl Ord for Number { axiom_bigint_obeys_cmp_spec(); } if let (Some(a), Some(b)) = (self.to_bigint_owned(), other.to_bigint_owned()) { + proof! { + assert(self@.to_int() == Some(a@)); + assert(other@.to_int() == Some(b@)); + } return a.cmp(&b); } - self.to_f64_lossy() - .partial_cmp(&other.to_f64_lossy()) - .unwrap_or(Ordering::Equal) + let f1 = self.to_f64_lossy(); + let f2 = other.to_f64_lossy(); + f1.partial_cmp(&f2).unwrap_or(Ordering::Equal) } } diff --git a/src/verify/mod.rs b/src/verify/mod.rs index c656e0292..b78d817fa 100644 --- a/src/verify/mod.rs +++ b/src/verify/mod.rs @@ -1,5 +1,4 @@ pub(crate) mod bigint_assumptions; pub(crate) mod f64_assumptions; -pub(crate) mod utils; pub mod number_specs; - +pub(crate) mod utils; diff --git a/verus-shim/Cargo.toml b/verus-shim/Cargo.toml new file mode 100644 index 000000000..a7d83a94d --- /dev/null +++ b/verus-shim/Cargo.toml @@ -0,0 +1,14 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +[package] +name = "regorus-verus-shim" +description = "No-op stand-ins for Verus's verus_verify/verus_spec/proof macros, used when the `verus` feature is disabled so annotated source still compiles as ordinary Rust." +version = "0.0.0" +edition = "2021" +license = "MIT AND Apache-2.0 AND BSD-3-Clause" +repository = "https://github.com/microsoft/regorus" +publish = false + +[lib] +proc-macro = true diff --git a/verus-shim/src/lib.rs b/verus-shim/src/lib.rs new file mode 100644 index 000000000..43b08a8d0 --- /dev/null +++ b/verus-shim/src/lib.rs @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +//! No-op stand-ins for the Verus attribute/macros used to annotate source for +//! verification (`verus_verify`, `verus_spec`, `proof!`). +//! +//! When the `verus` feature is enabled, the real macros are provided by +//! `vstd::prelude`. When it is disabled, these no-ops are imported instead so +//! that a normal `cargo build` compiles the annotated source as ordinary Rust: +//! the attributes are stripped (their contents discarded) and `proof!` blocks +//! expand to nothing. + +use proc_macro::TokenStream; + +/// No-op replacement for `#[verus_verify]`. Returns the annotated item +/// unchanged, discarding any attribute arguments (e.g. `external_derive`). +#[proc_macro_attribute] +pub fn verus_verify(_attr: TokenStream, item: TokenStream) -> TokenStream { + item +} + +/// No-op replacement for `#[verus_spec(...)]`. Returns the annotated item +/// unchanged, discarding the specification. +#[proc_macro_attribute] +pub fn verus_spec(_attr: TokenStream, item: TokenStream) -> TokenStream { + item +} + +/// No-op replacement for `proof! { ... }`. Expands to nothing. +#[proc_macro] +pub fn proof(_input: TokenStream) -> TokenStream { + TokenStream::new() +} + +/// No-op replacement for `proof_decl! { ... }`. Expands to nothing. +#[proc_macro] +pub fn proof_decl(_input: TokenStream) -> TokenStream { + TokenStream::new() +} From e0effed8498e7a0663b454c00c1e926d343630a9 Mon Sep 17 00:00:00 2001 From: Jay Lorch Date: Thu, 23 Jul 2026 11:10:37 -0700 Subject: [PATCH 04/41] Restore original Number::cmp --- src/number.rs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/number.rs b/src/number.rs index 21f6b5807..2060ea6e4 100644 --- a/src/number.rs +++ b/src/number.rs @@ -467,16 +467,12 @@ impl Ord for Number { axiom_bigint_obeys_cmp_spec(); } if let (Some(a), Some(b)) = (self.to_bigint_owned(), other.to_bigint_owned()) { - proof! { - assert(self@.to_int() == Some(a@)); - assert(other@.to_int() == Some(b@)); - } return a.cmp(&b); } - let f1 = self.to_f64_lossy(); - let f2 = other.to_f64_lossy(); - f1.partial_cmp(&f2).unwrap_or(Ordering::Equal) + self.to_f64_lossy() + .partial_cmp(&other.to_f64_lossy()) + .unwrap_or(Ordering::Equal) } } From 4d51022731e0ba28470dd88117a228f288ec0836 Mon Sep 17 00:00:00 2001 From: Jay Lorch Date: Thu, 23 Jul 2026 15:26:52 -0700 Subject: [PATCH 05/41] More Number proofs (some not yet audited) --- src/lib.rs | 2 +- src/number.rs | 454 ++++++++++++++++++++++++++++++- src/verify/bigint_assumptions.rs | 69 +++++ src/verify/bigint_proofs.rs | 33 +++ src/verify/f64_assumptions.rs | 57 +++- src/verify/mod.rs | 8 + src/verify/number_specs.rs | 113 ++++++++ 7 files changed, 726 insertions(+), 10 deletions(-) create mode 100644 src/verify/bigint_proofs.rs diff --git a/src/lib.rs b/src/lib.rs index d850c9221..18d1b16c0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -127,7 +127,7 @@ mod compiler; mod engine; mod indexchecker; mod interpreter; -#[cfg(verus_keep_ghost)] +#[cfg(any(verus_keep_ghost, test))] mod verify; pub mod languages { diff --git a/src/number.rs b/src/number.rs index 2060ea6e4..c8a918494 100644 --- a/src/number.rs +++ b/src/number.rs @@ -35,10 +35,14 @@ use vstd::prelude::*; #[cfg(verus_keep_ghost)] use crate::verify::bigint_assumptions::*; #[cfg(verus_keep_ghost)] +use crate::verify::bigint_proofs::*; +#[cfg(verus_keep_ghost)] use crate::verify::f64_assumptions::*; #[cfg(verus_keep_ghost)] use crate::verify::number_specs::*; #[cfg(verus_keep_ghost)] +use vstd::arithmetic::power2::pow2; +#[cfg(verus_keep_ghost)] use vstd::float::*; #[cfg(verus_keep_ghost)] use vstd::std_specs::cmp::*; @@ -224,10 +228,7 @@ impl Number { #[verus_spec(result => ensures - match result@ { - NumberView::Integer(n) => float_to_small_int(value) == Some(n), - NumberView::Float(f) => float_to_small_int(value) is None && f == value, - } + result@ == normalize_float(value), )] fn normalize_float(value: f64) -> Number { if let Some(i) = Self::float_to_small_bigint(value) { @@ -483,7 +484,36 @@ impl PartialOrd for Number { } impl Number { + #[verus_verify(external_body)] + #[verus_spec(result => + ensures + match self@ { + NumberView::Integer(n) => { + if 0 <= n <= u128::MAX { + result matches Some(value) && value as int == n + } else { + result is None + } + }, + NumberView::Float(f) => { + let convertible = f.is_finite_spec() + && f.ieee_ge(0.0f64) + && spec_f64_fract(f).eq_spec(&0.0f64) + && ieee_float_cast::(ieee_float_cast::(f)).eq_spec(&f); + match result { + Some(value) => convertible && value == ieee_float_cast::(f), + None => !convertible, + } + }, + }, + )] pub fn as_u128(&self) -> Option { + proof! { + axiom_f64_obeys_eq_spec(); + axiom_f64_obeys_partial_cmp_spec(); + axiom_f64_ops_deterministic(); + axiom_f64_comparisons_match_ieee(); + } match self { Number::UInt(v) => Some(*v as u128), Number::Int(v) if *v >= 0 => Some(*v as u128), @@ -501,7 +531,33 @@ impl Number { } } + #[verus_verify(external_body)] + #[verus_spec(result => + ensures + match self@ { + NumberView::Integer(n) => { + if i128::MIN <= n <= i128::MAX { + result matches Some(value) && value as int == n + } else { + result is None + } + }, + NumberView::Float(f) => { + let convertible = f.is_finite_spec() + && spec_f64_fract(f).eq_spec(&0.0f64) + && ieee_float_cast::(ieee_float_cast::(f)).eq_spec(&f); + match result { + Some(value) => convertible && value == ieee_float_cast::(f), + None => !convertible, + } + }, + }, + )] pub fn as_i128(&self) -> Option { + proof! { + axiom_f64_obeys_eq_spec(); + axiom_f64_ops_deterministic(); + } match self { Number::UInt(v) => Some(*v as i128), Number::Int(v) => Some(*v as i128), @@ -518,7 +574,37 @@ impl Number { } } + #[verus_verify] + #[verus_spec(result => + ensures + match self@ { + NumberView::Integer(n) => { + if 0 <= n <= u64::MAX { + result matches Some(value) && value as int == n + } else { + result is None + } + }, + NumberView::Float(f) => { + let convertible = f.is_finite_spec() + && f.ieee_ge(0.0f64) + && spec_f64_fract(f).eq_spec(&0.0f64) + && f.ieee_le(ieee_float_cast::(u64::MAX)) + && ieee_float_cast::(ieee_float_cast::(f)).eq_spec(&f); + match result { + Some(value) => convertible && value == ieee_float_cast::(f), + None => !convertible, + } + }, + }, + )] pub fn as_u64(&self) -> Option { + proof! { + axiom_f64_obeys_eq_spec(); + axiom_f64_obeys_partial_cmp_spec(); + axiom_f64_ops_deterministic(); + axiom_f64_comparisons_match_ieee(); + } match self { Number::UInt(v) => Some(*v), Number::Int(v) if *v >= 0 => Some(*v as u64), @@ -536,7 +622,37 @@ impl Number { } } + #[verus_verify] + #[verus_spec(result => + ensures + match self@ { + NumberView::Integer(n) => { + if i64::MIN <= n <= i64::MAX { + result matches Some(value) && value as int == n + } else { + result is None + } + }, + NumberView::Float(f) => { + let convertible = f.is_finite_spec() + && spec_f64_fract(f).eq_spec(&0.0f64) + && f.ieee_ge(ieee_float_cast::(i64::MIN)) + && f.ieee_le(ieee_float_cast::(i64::MAX)) + && ieee_float_cast::(ieee_float_cast::(f)).eq_spec(&f); + match result { + Some(value) => convertible && value == ieee_float_cast::(f), + None => !convertible, + } + }, + }, + )] pub fn as_i64(&self) -> Option { + proof! { + axiom_f64_obeys_eq_spec(); + axiom_f64_obeys_partial_cmp_spec(); + axiom_f64_ops_deterministic(); + axiom_f64_comparisons_match_ieee(); + } match self { Number::UInt(v) if *v <= i64::MAX as u64 => Some(*v as i64), Number::Int(v) => Some(*v), @@ -558,7 +674,30 @@ impl Number { } } + #[verus_verify] + #[verus_spec(result => + ensures + match (self@, result) { + (NumberView::Float(f), Some(value)) => f.is_finite_spec() && value == f, + (NumberView::Float(f), None) => !f.is_finite_spec(), + (NumberView::Integer(n), Some(value)) => { + &&& -9_007_199_254_740_992 <= n <= 9_007_199_254_740_992 + &&& self@.to_f64_lossy_ensures(value) + }, + // The bounds intentionally overlap at +/-2^53: primitive variants return + // Some there, while a BigInt variant with the same NumberView returns None. + (NumberView::Integer(n), None) => { + n <= -9_007_199_254_740_992 || 9_007_199_254_740_992 <= n + }, + }, + )] pub fn as_f64(&self) -> Option { + proof! { + axiom_f64_ops_deterministic(); + axiom_f64_safe_integer_casts(); + axiom_safe_bigints_to_f64(); + lemma_bigint_bits_le_53(); + } match self { Number::Float(f) if f.is_finite() => Some(*f), Number::UInt(v) if *v <= F64_SAFE_INTEGER as u64 => Some(*v as f64), @@ -574,20 +713,57 @@ impl Number { } } + #[verus_verify] + #[verus_spec(result => + ensures + match self@ { + NumberView::Integer(n) => result matches Some(bi) && bi@ == n, + NumberView::Float(f) => { + match result { + Some(bi) => float_to_small_int(f) == Some(bi@), + None => float_to_small_int(f) is None, + } + }, + }, + )] pub fn as_big(&self) -> Option> { self.to_bigint_rc() } + #[verus_verify(external_body)] + #[verus_spec(result => + ensures + match self@ { + NumberView::Integer(n) => result matches Ok(bi) && bi@ == n, + NumberView::Float(f) => { + match result { + Ok(bi) => float_to_small_int(f) == Some(bi@), + Err(_) => float_to_small_int(f) is None, + } + }, + }, + )] pub fn to_big(&self) -> Result> { self.as_big() .ok_or_else(|| anyhow!("Number::to_big failed")) } + #[verus_verify] + #[verus_spec(result => + ensures + result is Ok, + old(self)@.add_ensures(rhs@, final(self)@), + )] pub fn add_assign(&mut self, rhs: &Self) -> Result<()> { *self = self.add(rhs)?; Ok(()) } + #[verus_verify(external_body)] + #[verus_spec(result => + ensures + result matches Ok(value) && self@.add_ensures(rhs@, value@), + )] pub fn add(&self, rhs: &Self) -> Result { if matches!(self, Number::Float(_)) || matches!(rhs, Number::Float(_)) { return Ok(Number::normalize_float( @@ -629,11 +805,22 @@ impl Number { } } + #[verus_verify] + #[verus_spec(result => + ensures + result is Ok, + old(self)@.sub_ensures(rhs@, final(self)@), + )] pub fn sub_assign(&mut self, rhs: &Self) -> Result<()> { *self = self.sub(rhs)?; Ok(()) } + #[verus_verify(external_body)] + #[verus_spec(result => + ensures + result matches Ok(value) && self@.sub_ensures(rhs@, value@), + )] pub fn sub(&self, rhs: &Self) -> Result { if matches!(self, Number::Float(_)) || matches!(rhs, Number::Float(_)) { return Ok(Number::normalize_float( @@ -677,11 +864,22 @@ impl Number { } } + #[verus_verify] + #[verus_spec(result => + ensures + result is Ok, + old(self)@.mul_ensures(rhs@, final(self)@), + )] pub fn mul_assign(&mut self, rhs: &Self) -> Result<()> { *self = self.mul(rhs)?; Ok(()) } + #[verus_verify(external_body)] + #[verus_spec(result => + ensures + result matches Ok(value) && self@.mul_ensures(rhs@, value@), + )] pub fn mul(&self, rhs: &Self) -> Result { if matches!(self, Number::Float(_)) || matches!(rhs, Number::Float(_)) { return Ok(Number::normalize_float( @@ -729,6 +927,26 @@ impl Number { } } + #[verus_verify(external_body)] + #[verus_spec(result => + ensures + match (self@, rhs@, result) { + (NumberView::Integer(lhs), NumberView::Integer(divisor), Ok(value)) => { + &&& divisor != 0 + &&& if vstd::arithmetic::div_mod::rust_rem(lhs, divisor) == 0 { + value@ == NumberView::Integer(vstd::arithmetic::div_mod::rust_div(lhs, divisor)) + } else { + value@ == NumberView::Float(self.spec_to_f64_lossy() / rhs.spec_to_f64_lossy()) + } + }, + (NumberView::Float(_), _, Ok(value)) + | (NumberView::Integer(_), NumberView::Float(_), Ok(value)) => { + &&& !rhs@.is_zero() + &&& value@ == NumberView::Float(self.spec_to_f64_lossy() / rhs.spec_to_f64_lossy()) + }, + (_, _, Err(_)) => rhs@.is_zero(), + }, + )] pub fn divide(self, rhs: &Self) -> Result { if rhs.is_zero() { bail!("division by zero"); @@ -819,6 +1037,17 @@ impl Number { } } + #[verus_verify(external_body)] + #[verus_spec(result => + ensures + match (self@.to_int(), rhs@.to_int(), result) { + (Some(a), Some(b), Ok(value)) => { + b != 0 && value@ == NumberView::Integer(vstd::arithmetic::div_mod::rust_rem(a, b)) + }, + (_, _, Ok(_)) => false, + (_, _, Err(_)) => rhs@.is_zero() || !self@.is_integer() || !rhs@.is_integer(), + }, + )] pub fn modulo(self, rhs: &Self) -> Result { // Conversion fails for a non-integral float, and also for an integral // one whose magnitude exceeds 2^53, which cannot be represented exactly. @@ -835,13 +1064,27 @@ impl Number { Ok(Number::from_bigint_owned(rem)) } + #[verus_verify] + #[verus_spec(result => + ensures + result == self@.is_integer(), + )] pub fn is_integer(&self) -> bool { + proof! { axiom_f64_obeys_eq_spec(); } match self { Number::Float(f) => f.is_finite() && f.fract() == 0.0, _ => true, } } + #[verus_verify] + #[verus_spec(result => + ensures + match self@ { + NumberView::Integer(n) => result == (n >= 0), + NumberView::Float(f) => result == spec_f64_is_sign_positive(f), + }, + )] pub fn is_positive(&self) -> bool { match self { Number::UInt(_) => true, @@ -850,7 +1093,21 @@ impl Number { Number::Float(f) => f.is_sign_positive(), } } +} +#[verus_verify] +impl Number { + #[verus_spec(result => + ensures + match (a@.to_int(), b@.to_int(), result) { + (Some(lhs), Some(rhs), Some((lhs_big, rhs_big))) => { + lhs_big@ == lhs && rhs_big@ == rhs + }, + (Some(_), Some(_), None) => false, + (_, _, Some(_)) => false, + (_, _, None) => true, + }, + )] #[allow(clippy::if_then_some_else_none)] fn ensure_integers(a: &Number, b: &Number) -> Option<(BigInt, BigInt)> { if a.is_integer() && b.is_integer() { @@ -860,6 +1117,13 @@ impl Number { } } + #[verus_spec(result => + ensures + match self@.to_int() { + Some(value) => result matches Some(big) && big@ == value, + None => result is None, + }, + )] fn ensure_integer(&self) -> Option { if self.is_integer() { self.to_bigint_owned() @@ -868,21 +1132,72 @@ impl Number { } } + #[verus_spec(result => + ensures + match (self@.to_int(), rhs@.to_int(), result) { + (Some(lhs), Some(rhs), Some(value)) => { + value@ == NumberView::Integer(spec_bigint_bitand(lhs, rhs)) + }, + (Some(_), Some(_), None) => false, + (_, _, Some(_)) => false, + (_, _, None) => true, + }, + )] pub fn and(&self, rhs: &Self) -> Option { + proof! { axiom_bigint_obeys_bitand_spec(); } let (a, b) = Self::ensure_integers(self, rhs)?; Some(Number::from_bigint_owned(a & b)) } + #[verus_spec(result => + ensures + match (self@.to_int(), rhs@.to_int(), result) { + (Some(lhs), Some(rhs), Some(value)) => { + value@ == NumberView::Integer(spec_bigint_bitor(lhs, rhs)) + }, + (Some(_), Some(_), None) => false, + (_, _, Some(_)) => false, + (_, _, None) => true, + }, + )] pub fn or(&self, rhs: &Self) -> Option { + proof! { axiom_bigint_obeys_bitor_spec(); } let (a, b) = Self::ensure_integers(self, rhs)?; Some(Number::from_bigint_owned(a | b)) } + #[verus_spec(result => + ensures + match (self@.to_int(), rhs@.to_int(), result) { + (Some(lhs), Some(rhs), Some(value)) => { + value@ == NumberView::Integer(spec_bigint_bitxor(lhs, rhs)) + }, + (Some(_), Some(_), None) => false, + (_, _, Some(_)) => false, + (_, _, None) => true, + }, + )] pub fn xor(&self, rhs: &Self) -> Option { + proof! { axiom_bigint_obeys_bitxor_spec(); } let (a, b) = Self::ensure_integers(self, rhs)?; Some(Number::from_bigint_owned(a ^ b)) } +} +impl Number { + #[verus_verify(external_body)] + #[verus_spec(result => + ensures + match (self@.to_int(), rhs@.to_int(), result) { + (Some(value), Some(shift), Some(result)) => { + &&& 0 <= shift <= u32::MAX + &&& result@ == NumberView::Integer(value * pow2(shift as nat) as int) + }, + (Some(_), Some(shift), None) => !(0 <= shift <= u32::MAX), + (_, _, Some(_)) => false, + (_, _, None) => true, + }, + )] pub fn lsh(&self, rhs: &Self) -> Option { let shift = rhs.as_u32()? as usize; let mut value = self.ensure_integer()?; @@ -890,6 +1205,19 @@ impl Number { Some(Number::from_bigint_owned(value)) } + #[verus_verify(external_body)] + #[verus_spec(result => + ensures + match (self@.to_int(), rhs@.to_int(), result) { + (Some(value), Some(shift), Some(result)) => { + &&& 0 <= shift <= u32::MAX + &&& result@ == NumberView::Integer(value / (pow2(shift as nat) as int)) + }, + (Some(_), Some(shift), None) => !(0 <= shift <= u32::MAX), + (_, _, Some(_)) => false, + (_, _, None) => true, + }, + )] pub fn rsh(&self, rhs: &Self) -> Option { let shift = rhs.as_u32()? as usize; let mut value = self.ensure_integer()?; @@ -897,12 +1225,42 @@ impl Number { Some(Number::from_bigint_owned(value)) } + #[verus_verify] + #[verus_spec(result => + ensures + match (self@.to_int(), result) { + (Some(value), Some(result)) => { + result@ == NumberView::Integer(-value - 1) + }, + (Some(_), None) => false, + (None, Some(_)) => false, + (None, None) => true, + }, + )] pub fn neg(&self) -> Option { let mut value = self.ensure_integer()?; - value = !value; + proof! { axiom_bigint_not_spec(value); } + #[cfg(feature = "verus")] + { + value = core::ops::Not::not(value); + } + #[cfg(not(feature = "verus"))] + { + value = !value; + } Some(Number::from_bigint_owned(value)) } + #[verus_verify] + #[verus_spec(result => + ensures + match self@ { + NumberView::Integer(value) => { + result@ matches NumberView::Integer(abs) && abs == if value < 0 { -value } else { value } + }, + NumberView::Float(value) => result@ == NumberView::Float(spec_f64_abs(value)), + }, + )] pub fn abs(&self) -> Number { match self { Number::UInt(_) => self.clone(), @@ -918,6 +1276,14 @@ impl Number { } } + #[verus_verify] + #[verus_spec(result => + ensures + match self@ { + NumberView::Integer(_) => result@ == self@, + NumberView::Float(value) => result@ == normalize_float(spec_f64_floor(value)), + }, + )] pub fn floor(&self) -> Number { match self { Number::Float(f) => Number::normalize_float(f.floor()), @@ -925,6 +1291,14 @@ impl Number { } } + #[verus_verify] + #[verus_spec(result => + ensures + match self@ { + NumberView::Integer(_) => result@ == self@, + NumberView::Float(value) => result@ == normalize_float(spec_f64_ceil(value)), + }, + )] pub fn ceil(&self) -> Number { match self { Number::Float(f) => Number::normalize_float(f.ceil()), @@ -932,6 +1306,14 @@ impl Number { } } + #[verus_verify] + #[verus_spec(result => + ensures + match self@ { + NumberView::Integer(_) => result@ == self@, + NumberView::Float(value) => result@ == normalize_float(spec_f64_round(value)), + }, + )] pub fn round(&self) -> Number { match self { Number::Float(f) => Number::normalize_float(f.round()), @@ -959,18 +1341,33 @@ impl Number { } } + #[verus_verify(external_body)] + #[verus_spec(result => + ensures + (result@.len() == 0) == self@.to_int() is None, + )] pub fn format_bin(&self) -> String { self.ensure_integer() .map(|v| v.to_str_radix(2)) .unwrap_or_default() } + #[verus_verify(external_body)] + #[verus_spec(result => + ensures + (result@.len() == 0) == self@.to_int() is None, + )] pub fn format_octal(&self) -> String { self.ensure_integer() .map(|v| v.to_str_radix(8)) .unwrap_or_default() } + #[verus_verify(external_body)] + #[verus_spec(result => + ensures + result@.len() > 0, + )] pub fn format_scientific(&self) -> String { match self { Number::Float(f) => format!("{:e}", f), @@ -981,6 +1378,11 @@ impl Number { } } + #[verus_verify(external_body)] + #[verus_spec(result => + ensures + result@.len() > 0, + )] pub fn format_decimal(&self) -> String { match self { Number::UInt(v) => v.to_string(), @@ -996,6 +1398,11 @@ impl Number { } } + #[verus_verify(external_body)] + #[verus_spec(result => + ensures + result@.len() > 0, + )] pub fn format_decimal_with_width(&self, d: u32) -> String { match self { Number::Float(f) => { @@ -1007,12 +1414,22 @@ impl Number { } } + #[verus_verify(external_body)] + #[verus_spec(result => + ensures + (result@.len() == 0) == self@.to_int() is None, + )] pub fn format_hex(&self) -> String { self.ensure_integer() .map(|v| v.to_str_radix(16)) .unwrap_or_default() } + #[verus_verify(external_body)] + #[verus_spec(result => + ensures + (result@.len() == 0) == self@.to_int() is None, + )] pub fn format_big_hex(&self) -> String { self.ensure_integer() .map(|v| v.to_str_radix(16).to_ascii_uppercase()) @@ -1020,6 +1437,11 @@ impl Number { } } +#[verus_verify(external_body)] +#[verus_spec(result => + ensures + result@ matches NumberView::Integer(value) && value > 0, +)] fn two_pow_positive(exp: u32) -> Number { if exp < 64 { Number::UInt(1u64 << exp) @@ -1030,6 +1452,11 @@ fn two_pow_positive(exp: u32) -> Number { } } +#[verus_verify(external_body)] +#[verus_spec(result => + ensures + result@ > 0, +)] fn pow10_bigint(exp: u32) -> BigInt { if exp == 0 { return BigInt::one(); @@ -1052,6 +1479,11 @@ fn pow10_bigint(exp: u32) -> BigInt { result } +#[verus_verify(external_body)] +#[verus_spec(result => + ensures + result@ matches NumberView::Integer(value) && value > 0, +)] fn ten_pow_positive(exp: u32) -> Number { if let Some(value) = 10u64.checked_pow(exp) { Number::UInt(value) @@ -1060,6 +1492,11 @@ fn ten_pow_positive(exp: u32) -> Number { } } +#[verus_verify(external_body)] +#[verus_spec(result => + ensures + result@.len() > 0, +)] fn bigint_to_scientific(value: &BigInt) -> String { let s = value.to_string(); let (sign, digits) = if let Some(rest) = s.strip_prefix('-') { @@ -1076,12 +1513,18 @@ fn bigint_to_scientific(value: &BigInt) -> String { format!("{}{}.{}e{}", sign, &digits[0..1], &digits[1..], exponent) } +#[verus_verify(external_body)] fn parse_scientific_bigint(input: &str) -> Option { let (mantissa, exponent_part) = split_scientific_parts(input)?; let exponent = exponent_part.parse::().ok()?; scientific_parts_to_bigint(mantissa, exponent) } +#[verus_verify(external_body)] +#[verus_spec(result => + ensures + result matches Some((_, exponent)) ==> exponent@.len() > 0, +)] fn split_scientific_parts(input: &str) -> Option<(&str, &str)> { let idx = input.find(['e', 'E'])?; let mantissa = &input[..idx]; @@ -1093,6 +1536,7 @@ fn split_scientific_parts(input: &str) -> Option<(&str, &str)> { } } +#[verus_verify(external_body)] fn scientific_parts_to_bigint(mantissa: &str, exponent: i32) -> Option { let (sign, unsigned) = if let Some(rest) = mantissa.strip_prefix('-') { (-1, rest) diff --git a/src/verify/bigint_assumptions.rs b/src/verify/bigint_assumptions.rs index 0997f354b..1e519bf8d 100644 --- a/src/verify/bigint_assumptions.rs +++ b/src/verify/bigint_assumptions.rs @@ -23,6 +23,7 @@ verus! { use core::cmp::Ordering; use num_bigint::BigInt; +use vstd::arithmetic::power2::pow2; use vstd::std_specs::cmp::OrdSpec; #[verifier::external_type_specification] @@ -42,8 +43,63 @@ impl BigIntAdditionalSpecFns for BigInt { uninterp spec fn view(&self) -> int; } +pub uninterp spec fn spec_bigint_bitand(lhs: int, rhs: int) -> int; + +pub axiom fn axiom_bigint_obeys_bitand_spec() + ensures + ::obeys_bitand_spec(), + forall|lhs: BigInt, rhs: BigInt| #[trigger] + ::bitand_req(lhs, rhs), + forall|lhs: BigInt, rhs: BigInt| #[trigger] + ::bitand_spec(lhs, rhs)@ + == spec_bigint_bitand(lhs@, rhs@), +; + +pub uninterp spec fn spec_bigint_bitor(lhs: int, rhs: int) -> int; + +pub axiom fn axiom_bigint_obeys_bitor_spec() + ensures + ::obeys_bitor_spec(), + forall|lhs: BigInt, rhs: BigInt| #[trigger] + ::bitor_req(lhs, rhs), + forall|lhs: BigInt, rhs: BigInt| #[trigger] + ::bitor_spec(lhs, rhs)@ + == spec_bigint_bitor(lhs@, rhs@), +; + +pub uninterp spec fn spec_bigint_bitxor(lhs: int, rhs: int) -> int; + +pub axiom fn axiom_bigint_obeys_bitxor_spec() + ensures + ::obeys_bitxor_spec(), + forall|lhs: BigInt, rhs: BigInt| #[trigger] + ::bitxor_req(lhs, rhs), + forall|lhs: BigInt, rhs: BigInt| #[trigger] + ::bitxor_spec(lhs, rhs)@ + == spec_bigint_bitxor(lhs@, rhs@), +; + +pub axiom fn axiom_bigint_not_spec(value: BigInt) + ensures + ::obeys_not_spec(), + ::not_req(value), + ::not_spec(value)@ == -(value@) - 1, +; + // Conditions +pub open spec fn bigint_bits_ensures(value: int, bits: nat) -> bool +{ + &&& -(pow2(bits) as int) < value < pow2(bits) + &&& forall|n: nat| #![trigger pow2(n)] n < bits ==> + !( -(pow2(n) as int) < value < pow2(n) ) +} + +pub assume_specification[ BigInt::bits ](x: &BigInt) -> (res: u64) + ensures + bigint_bits_ensures(x@, res as nat), +; + pub assume_specification[ ::is_zero ](x: &BigInt) -> (res: bool) ensures res == (x@ == 0), @@ -54,6 +110,11 @@ pub assume_specification[ ::is_negative ](x: &BigI res == (x@ < 0), ; +pub assume_specification[ ::abs ](x: &BigInt) -> (res: BigInt) + ensures + res@ == if x@ < 0 { -x@ } else { x@ }, +; + // PartialEq pub axiom fn axiom_bigint_obeys_eq_spec() @@ -699,6 +760,14 @@ impl ToPrimitiveSpecImpl for num_bigint::BigInt uninterp spec fn spec_to_f64(&self) -> Option; } +pub axiom fn axiom_safe_bigints_to_f64() + ensures + forall|x: &BigInt| { + -9_007_199_254_740_992 < x@ < 9_007_199_254_740_992 ==> + ::spec_to_f64(x) is Some + }, +; + // These are the methods of ToPrimitive that BigInt implements because there is no default in ToPrimitive pub assume_specification[ ::to_i64 ](x: &BigInt) -> (res: Option); pub assume_specification[ ::to_u64 ](x: &BigInt) -> (res: Option); diff --git a/src/verify/bigint_proofs.rs b/src/verify/bigint_proofs.rs new file mode 100644 index 000000000..78d322e5b --- /dev/null +++ b/src/verify/bigint_proofs.rs @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#[cfg(feature = "verus")] +use vstd::prelude::*; + +#[cfg(feature = "verus")] +verus! { + +use super::bigint_assumptions::bigint_bits_ensures; +use vstd::arithmetic::power2::{lemma2_to64_rest, lemma_pow2_strictly_increases, pow2}; + +pub proof fn lemma_bigint_bits_le_53() + ensures + forall|value: int, bits: nat| #[trigger] bigint_bits_ensures(value, bits) ==> + ((bits <= 53) == (-9_007_199_254_740_992 < value < 9_007_199_254_740_992)), +{ + lemma2_to64_rest(); + assert(pow2(53) == 9_007_199_254_740_992); + assert forall|value: int, bits: nat| #[trigger] bigint_bits_ensures(value, bits) implies + ((bits <= 53) == (-9_007_199_254_740_992 < value < 9_007_199_254_740_992)) by + { + if bigint_bits_ensures(value, bits) { + if bits < 53 { + lemma_pow2_strictly_increases(bits, 53); + } else if bits > 53 { + assert(!( -(pow2(53) as int) < value < pow2(53) )); + } + } + } +} + +} diff --git a/src/verify/f64_assumptions.rs b/src/verify/f64_assumptions.rs index 9f0aea6d9..c304cffc4 100644 --- a/src/verify/f64_assumptions.rs +++ b/src/verify/f64_assumptions.rs @@ -14,10 +14,10 @@ clippy::pattern_type_mismatch )] -#[cfg(feature = "verus")] +#[cfg(verus_keep_ghost)] use vstd::prelude::*; -#[cfg(feature = "verus")] +#[cfg(verus_keep_ghost)] verus! { use vstd::float::*; @@ -71,6 +71,13 @@ pub axiom fn axiom_f64_ops_deterministic() forall|n: u128, f: f64| float_cast_spec::(f, n) ==> n == ieee_float_cast::(f), ; +// The executable test below validates these casts and justifies this axiom. +pub axiom fn axiom_f64_safe_integer_casts() + ensures + ieee_float_cast::(9_007_199_254_740_992.0f64) == 9_007_199_254_740_992u64, + ieee_float_cast::(9_007_199_254_740_992.0f64) == 9_007_199_254_740_992i128, +; + pub assume_specification [ f64::is_finite ](f: f64) -> (res: bool) ensures res == f.is_finite_spec(), @@ -88,17 +95,48 @@ pub assume_specification [ f64::fract ](f: f64) -> (res: f64) pub uninterp spec fn spec_f64_abs(f: f64) -> f64; pub assume_specification [ f64::abs ](f: f64) -> (res: f64) - requires - f.is_finite_spec(), ensures res == spec_f64_abs(f), ; +pub assume_specification [ ::abs ](f: &f64) -> (res: f64) + ensures + res == spec_f64_abs(*f), +; + +pub uninterp spec fn spec_f64_floor(f: f64) -> f64; + +pub assume_specification [ f64::floor ](f: f64) -> (res: f64) + ensures + res == spec_f64_floor(f), +; + +pub uninterp spec fn spec_f64_ceil(f: f64) -> f64; + +pub assume_specification [ f64::ceil ](f: f64) -> (res: f64) + ensures + res == spec_f64_ceil(f), +; + +pub uninterp spec fn spec_f64_round(f: f64) -> f64; + +pub assume_specification [ f64::round ](f: f64) -> (res: f64) + ensures + res == spec_f64_round(f), +; + pub assume_specification [ f64::is_nan ](f: f64) -> (res: bool) ensures res == f.is_nan_spec(), ; +pub uninterp spec fn spec_f64_is_sign_positive(f: f64) -> bool; + +pub assume_specification [ f64::is_sign_positive ](f: f64) -> (res: bool) + ensures + res == spec_f64_is_sign_positive(f), +; + pub uninterp spec fn spec_f64_neg_infinity() -> f64; pub uninterp spec fn spec_f64_infinity() -> f64; @@ -114,3 +152,14 @@ pub assume_specification[ f64::INFINITY ] -> (res: f64) ; } // end verus! + +#[cfg(test)] +mod tests { + #[test] + fn f64_safe_integer_casts_match_runtime() { + let safe_integer = 9_007_199_254_740_992.0f64; + + assert_eq!(safe_integer as u64, 9_007_199_254_740_992u64); + assert_eq!(safe_integer as i128, 9_007_199_254_740_992i128); + } +} diff --git a/src/verify/mod.rs b/src/verify/mod.rs index b78d817fa..9aa3afdb3 100644 --- a/src/verify/mod.rs +++ b/src/verify/mod.rs @@ -1,4 +1,12 @@ +#[cfg(verus_keep_ghost)] pub(crate) mod bigint_assumptions; +#[cfg(verus_keep_ghost)] +pub(crate) mod bigint_proofs; +#[cfg(verus_keep_ghost)] pub(crate) mod f64_assumptions; +#[cfg(all(test, not(verus_keep_ghost)))] +mod f64_assumptions; +#[cfg(verus_keep_ghost)] pub mod number_specs; +#[cfg(verus_keep_ghost)] pub(crate) mod utils; diff --git a/src/verify/number_specs.rs b/src/verify/number_specs.rs index 869e4a37e..20152ed11 100644 --- a/src/verify/number_specs.rs +++ b/src/verify/number_specs.rs @@ -33,6 +33,22 @@ pub assume_specification[ ::clone ](n: &Number) -> (res: Number res == n, ; +pub assume_specification[ i128::abs ](value: i128) -> (res: i128) + requires + value != i128::MIN, + ensures + res as int == if value < 0 { -(value as int) } else { value as int }, +; + +pub assume_specification[ i64::checked_abs ](value: i64) -> (res: Option) + ensures + if value == i64::MIN { + res is None + } else { + res matches Some(abs) && abs as int == if value < 0 { -(value as int) } else { value as int } + }, +; + pub enum NumberView { Integer(int), Float(f64), @@ -78,7 +94,39 @@ pub open spec fn float_to_small_int(value: f64) -> Option } } +pub open spec fn normalize_float(value: f64) -> NumberView +{ + match float_to_small_int(value) { + Some(n) => NumberView::Integer(n), + None => NumberView::Float(value), + } +} + impl NumberView { + pub open spec fn integer_value(&self) -> Option + { + match *self { + Self::Integer(n) => Some(n), + Self::Float(_) => None, + } + } + + pub open spec fn is_integer(&self) -> bool + { + match *self { + Self::Integer(_) => true, + Self::Float(f) => f.is_finite_spec() && spec_f64_fract(f).eq_spec(&0.0f64), + } + } + + pub open spec fn is_zero(&self) -> bool + { + match *self { + Self::Integer(n) => n == 0, + Self::Float(f) => f.eq_spec(&0.0f64), + } + } + pub open spec fn to_int(&self) -> Option { match *self { @@ -105,8 +153,73 @@ impl NumberView { NumberView::Float(v) => f == v, } } + + pub open spec fn add_ensures(self: Self, rhs: Self, result: Self) -> bool + { + match (self.integer_value(), rhs.integer_value()) { + (Some(lhs), Some(rhs)) => result matches NumberView::Integer(sum) && sum == lhs + rhs, + _ => exists|lhs_float: f64, rhs_float: f64| { + &&& self.to_f64_lossy_ensures(lhs_float) + &&& rhs.to_f64_lossy_ensures(rhs_float) + &&& match result { + NumberView::Integer(sum) => float_to_small_int(lhs_float + rhs_float) == Some(sum), + NumberView::Float(sum) => { + float_to_small_int(lhs_float + rhs_float) is None && sum == lhs_float + rhs_float + }, + } + }, + } + } + + pub open spec fn sub_ensures(self: Self, rhs: Self, result: Self) -> bool + { + match (self.integer_value(), rhs.integer_value()) { + (Some(lhs), Some(rhs)) => result matches NumberView::Integer(diff) && diff == lhs - rhs, + _ => exists|lhs_float: f64, rhs_float: f64| { + &&& self.to_f64_lossy_ensures(lhs_float) + &&& rhs.to_f64_lossy_ensures(rhs_float) + &&& match result { + NumberView::Integer(diff) => float_to_small_int(lhs_float - rhs_float) == Some(diff), + NumberView::Float(diff) => { + float_to_small_int(lhs_float - rhs_float) is None && diff == lhs_float - rhs_float + }, + } + }, + } + } + + pub open spec fn mul_ensures(self: Self, rhs: Self, result: Self) -> bool + { + match (self.integer_value(), rhs.integer_value()) { + (Some(lhs), Some(rhs)) => result matches NumberView::Integer(product) && product == lhs * rhs, + _ => exists|lhs_float: f64, rhs_float: f64| { + &&& self.to_f64_lossy_ensures(lhs_float) + &&& rhs.to_f64_lossy_ensures(rhs_float) + &&& match result { + NumberView::Integer(product) => float_to_small_int(lhs_float * rhs_float) == Some(product), + NumberView::Float(product) => { + float_to_small_int(lhs_float * rhs_float) is None && product == lhs_float * rhs_float + }, + } + }, + } + } } +pub assume_specification[ Number::two_pow ](e: i32) -> (result: anyhow::Result) + ensures + result is Ok, + e >= 0 ==> (result matches Ok(value) && value@ is Integer), + e < 0 ==> (result matches Ok(value) && value@ is Float), +; + +pub assume_specification[ Number::ten_pow ](e: i32) -> (result: anyhow::Result) + ensures + result is Ok, + e >= 0 ==> (result matches Ok(value) && value@ is Integer), + e < 0 ==> (result matches Ok(value) && value@ is Float), +; + impl FromSpecImpl for Number { open spec fn obeys_from_spec() -> bool { From 7842e81094d4cd7c4fc641c69c7875155c1816fb Mon Sep 17 00:00:00 2001 From: Jay Lorch Date: Thu, 23 Jul 2026 18:50:42 -0700 Subject: [PATCH 06/41] Fix two_pow bug --- src/number.rs | 83 ++++++++++++++++++++++---------- src/verify/bigint_assumptions.rs | 14 ++++-- src/verify/number_specs.rs | 7 --- 3 files changed, 69 insertions(+), 35 deletions(-) diff --git a/src/number.rs b/src/number.rs index c8a918494..4804bf15c 100644 --- a/src/number.rs +++ b/src/number.rs @@ -288,6 +288,7 @@ impl From for Number { #[verus_spec(result => ensures result@ == NumberView::Integer(value as int), + result.spec_to_f64_lossy() == ieee_float_cast::(value), )] fn from(value: u64) -> Self { Number::UInt(value) @@ -483,6 +484,7 @@ impl PartialOrd for Number { } } +#[verus_verify] impl Number { #[verus_verify(external_body)] #[verus_spec(result => @@ -1093,10 +1095,6 @@ impl Number { Number::Float(f) => f.is_sign_positive(), } } -} - -#[verus_verify] -impl Number { #[verus_spec(result => ensures match (a@.to_int(), b@.to_int(), result) { @@ -1182,18 +1180,17 @@ impl Number { let (a, b) = Self::ensure_integers(self, rhs)?; Some(Number::from_bigint_owned(a ^ b)) } -} - -impl Number { #[verus_verify(external_body)] #[verus_spec(result => ensures - match (self@.to_int(), rhs@.to_int(), result) { - (Some(value), Some(shift), Some(result)) => { + match (self@.to_int(), rhs@, result) { + (Some(value), NumberView::Integer(shift), Some(result)) => { &&& 0 <= shift <= u32::MAX &&& result@ == NumberView::Integer(value * pow2(shift as nat) as int) }, - (Some(_), Some(shift), None) => !(0 <= shift <= u32::MAX), + (Some(_), NumberView::Integer(shift), None) => { + !(0 <= shift <= u32::MAX) + }, (_, _, Some(_)) => false, (_, _, None) => true, }, @@ -1205,15 +1202,18 @@ impl Number { Some(Number::from_bigint_owned(value)) } + // Verus does not yet support overloaded op-assignment operators such as `>>=`. #[verus_verify(external_body)] #[verus_spec(result => ensures - match (self@.to_int(), rhs@.to_int(), result) { - (Some(value), Some(shift), Some(result)) => { + match (self@.to_int(), rhs@, result) { + (Some(value), NumberView::Integer(shift), Some(result)) => { &&& 0 <= shift <= u32::MAX &&& result@ == NumberView::Integer(value / (pow2(shift as nat) as int)) }, - (Some(_), Some(shift), None) => !(0 <= shift <= u32::MAX), + (Some(_), NumberView::Integer(shift), None) => { + !(0 <= shift <= u32::MAX) + }, (_, _, Some(_)) => false, (_, _, None) => true, }, @@ -1225,7 +1225,8 @@ impl Number { Some(Number::from_bigint_owned(value)) } - #[verus_verify] + // Verus panics while translating overloaded `!` on an external `BigInt`. + #[verus_verify(external_body)] #[verus_spec(result => ensures match (self@.to_int(), result) { @@ -1239,15 +1240,7 @@ impl Number { )] pub fn neg(&self) -> Option { let mut value = self.ensure_integer()?; - proof! { axiom_bigint_not_spec(value); } - #[cfg(feature = "verus")] - { - value = core::ops::Not::not(value); - } - #[cfg(not(feature = "verus"))] - { - value = !value; - } + value = !value; Some(Number::from_bigint_owned(value)) } @@ -1320,8 +1313,38 @@ impl Number { _ => self.clone(), } } - + #[verus_spec(result => + ensures + match result { + Ok(value) => if e >= 0 { + value@ == NumberView::Integer(pow2(e as nat) as int) + } else { + exists|denominator: Number| { + &&& #[trigger] denominator@ == NumberView::Integer(pow2((-(e as int)) as nat) as int) + &&& value@ == NumberView::Float( + ieee_float_cast::(1u64) + / denominator.spec_to_f64_lossy() + ) + } + }, + Err(_) => false, + }, + )] pub fn two_pow(e: i32) -> Result { + proof! { + axiom_f64_ops_deterministic(); + if e >= 0 { + assert((e as u32) as nat == e as nat); + } else { + let exp = (-(e as i64)) as u32; + assert(exp > 0); + vstd::arithmetic::power2::lemma2_to64(); + vstd::arithmetic::power2::lemma_pow2_strictly_increases(0, exp as nat); + assert(1 < pow2(exp as nat)); + vstd::arithmetic::div_mod::lemma_small_mod(1, pow2(exp as nat)); + assert(vstd::arithmetic::div_mod::rust_rem(1, pow2(exp as nat) as int) == 1); + } + } if e >= 0 { Ok(two_pow_positive(e as u32)) } else { @@ -1331,6 +1354,7 @@ impl Number { } } + #[verus_verify(external)] pub fn ten_pow(e: i32) -> Result { if e >= 0 { Ok(ten_pow_positive(e as u32)) @@ -1437,10 +1461,11 @@ impl Number { } } +// Verus does not yet support overloaded op-assignment operators such as `<<=`. #[verus_verify(external_body)] #[verus_spec(result => ensures - result@ matches NumberView::Integer(value) && value > 0, + result@ == NumberView::Integer(pow2(exp as nat) as int), )] fn two_pow_positive(exp: u32) -> Number { if exp < 64 { @@ -1660,4 +1685,12 @@ mod tests { Some("modulo on floating-point number".to_string()) ); } + + #[test] + fn two_pow_computes_minimum_exponent() { + assert!(matches!( + Number::two_pow(i32::MIN), + Ok(Number::Float(value)) if value == 0.0 + )); + } } diff --git a/src/verify/bigint_assumptions.rs b/src/verify/bigint_assumptions.rs index 1e519bf8d..6d6a07446 100644 --- a/src/verify/bigint_assumptions.rs +++ b/src/verify/bigint_assumptions.rs @@ -79,11 +79,19 @@ pub axiom fn axiom_bigint_obeys_bitxor_spec() == spec_bigint_bitxor(lhs@, rhs@), ; +pub assume_specification[ >::shr_assign ]( + value: &mut BigInt, + shift: usize, +) + ensures + (*final(value))@ == (*old(value))@ / (pow2(shift as nat) as int), +; + pub axiom fn axiom_bigint_not_spec(value: BigInt) ensures - ::obeys_not_spec(), - ::not_req(value), - ::not_spec(value)@ == -(value@) - 1, + ::obeys_not_spec(), + ::not_req(value), + ::not_spec(value)@ == -(value@) - 1, ; // Conditions diff --git a/src/verify/number_specs.rs b/src/verify/number_specs.rs index 20152ed11..0ca6b3dea 100644 --- a/src/verify/number_specs.rs +++ b/src/verify/number_specs.rs @@ -206,13 +206,6 @@ impl NumberView { } } -pub assume_specification[ Number::two_pow ](e: i32) -> (result: anyhow::Result) - ensures - result is Ok, - e >= 0 ==> (result matches Ok(value) && value@ is Integer), - e < 0 ==> (result matches Ok(value) && value@ is Float), -; - pub assume_specification[ Number::ten_pow ](e: i32) -> (result: anyhow::Result) ensures result is Ok, From 05b0f929d285b5c10f2a4e9313199b817842d02f Mon Sep 17 00:00:00 2001 From: Jay Lorch Date: Fri, 24 Jul 2026 10:30:25 -0700 Subject: [PATCH 07/41] Remove extraneous verus_verify --- .github/skills/verus-verification/SKILL.md | 257 +++++++++++++++++++++ src/number.rs | 44 ++-- src/verify/number_specs.rs | 7 - 3 files changed, 286 insertions(+), 22 deletions(-) create mode 100644 .github/skills/verus-verification/SKILL.md diff --git a/.github/skills/verus-verification/SKILL.md b/.github/skills/verus-verification/SKILL.md new file mode 100644 index 000000000..ddb916ec0 --- /dev/null +++ b/.github/skills/verus-verification/SKILL.md @@ -0,0 +1,257 @@ +--- +name: verus-verification +description: >- + Rigorous Verus specification and proof work for Regorus. Use when adding, + strengthening, debugging, or reviewing Verus contracts, proofs, external-body + boundaries, assume_specification declarations, BigInt or Number models, or + minimal Verus bug reproducers. Preserves executable behavior while minimizing + trusted assumptions and verifier workarounds. +--- + +# Regorus Verus Verification + +Use this workflow for proof-oriented changes in Regorus, especially `Number`, +`BigInt`, arithmetic, conversions, and policy-critical value semantics. + +The objective is not merely to make Verus pass. The objective is to establish an +exact, useful contract for the real executable implementation with the smallest +honest trusted boundary. + +## Core Rules + +1. **Specify executable semantics exactly.** + - Model every meaningful result variant and error path. + - Preserve distinctions such as integer versus float representation. + - For floating-point operations, specify IEEE-754 behavior rather than ideal + real arithmetic. + - Do not weaken a contract just because the stronger proof is inconvenient. + +2. **Prove bodies whenever Verus supports them.** + - Prefer a verified implementation over `assume_specification`. + - Remove a trusted assumption once the implementation carries a proved spec. + - Never describe an `external_body` function as body-proved. + +3. **Preserve executable behavior.** + - Before editing, compare the function with `main` or the relevant base. + - Keep executable statements unchanged unless the task explicitly requires a + runtime fix. + - Put ghost reasoning in `proof!` blocks. Move proof work to the beginning of + the function when it depends only on inputs. + - Afterward, inspect the focused diff against the base and confirm that only + contracts and erased proof code differ, unless a runtime change was intended. + +4. **Do not use preconditions to hide valid edge cases.** + - Check minimum signed values, maximum values, zero, and representation + boundaries explicitly. + - Negating `i32::MIN` as `i32` overflows, but its magnitude $2^31$ fits in + `u32`. Widen before negation, for example `(-(e as i64)) as u32`. + - If the API can compute a valid result, prove and compute it instead of + excluding the input or returning an invented error. + +5. **Reuse existing semantic models.** + - Search `src/verify/` before adding an uninterpreted spec function. + - Prefer established models such as `pow2`, `NumberView`, + `spec_to_f64_lossy`, and BigInt view/spec traits. + - If the same mathematical value can have representation-dependent runtime + behavior, quantify over the concrete modeled value rather than pretending + the view alone determines the result. + +6. **Minimize and explain trust.** + - Use `external_body` only at the smallest unsupported boundary. + - Give an exact postcondition, not merely positivity or successful return, + whenever downstream proofs depend on exact behavior. + - Add a short comment naming the concrete verifier limitation, for example: + overloaded `<<=`/`>>=` is unsupported, or overloaded `!` on external + `BigInt` crashes this Verus version. + - Avoid broad external wrappers around otherwise verifiable callers. + +## Workflow + +### 1. Establish the Runtime Baseline + +Start with the function, its helper contracts, its callers, and any existing +trusted specification. + +```bash +git show main:path/to/file.rs +rg -n 'function_name|assume_specification|relevant_helper' src tests +``` + +Record one falsifiable hypothesis: +- what the exact behavior should be; +- which helper contracts it depends on; +- the cheapest verification or runtime check that could disprove it. + +Do not map the whole subsystem before making a small grounded edit. + +### 2. Write the Contract Before the Proof + +The contract should answer: +- Which inputs return `Ok`, `Err`, `Some`, or `None`? +- What exact mathematical value is represented? +- Is the result an integer or float variant? +- Which rounding, overflow, saturation, or lossy-conversion rule applies? +- Are multiple concrete representations possible for the same view? + +For arithmetic returning `Result`, avoid vague contracts such as only +`result is Ok` when the exact value is knowable. + +For BigInt operators, provide exact operator models and prove the caller against +them. For division producing a float, model the exact lossy conversions used by +the executable code. + +### 3. Remove Redundant Trust + +Search for existing assumptions: + +```bash +rg -n 'assume_specification.*function_name|uninterp spec fn' src/verify src +``` + +When moving a spec onto a body-verified function: +- delete the old `assume_specification` in the same change; +- ensure no duplicate specification remains; +- strengthen helper contracts only as much as the body proof requires. + +An external helper may remain trusted when Verus cannot translate its syntax, +but its contract must expose all facts needed by verified callers. + +### 4. Keep Proofs Separate From Execution + +Prefer this shape: + +```rust +pub fn operation(input: i32) -> Result { + proof! { + // Input-only lemmas, cast equalities, and arithmetic facts. + } + + // Original executable body. +} +``` + +Use local proof blocks later only when facts genuinely depend on an executable +value produced at that point. + +Do not introduce executable temporaries solely to help a proof. If a temporary +is ghost-only, keep it inside `proof!`. + +### 5. Handle Casts and Boundaries Explicitly + +Verus often needs explicit facts connecting machine integers and mathematical +integers/naturals: + +```rust +assert((e as u32) as nat == e as nat); +``` + +For negative signed values, widen before negating: + +```rust +let magnitude = (-(e as i64)) as u32; +``` + +Then prove: +- the magnitude is positive; +- its cast equals the intended mathematical magnitude; +- required power/division lemmas apply; +- remainder is nonzero when the runtime should choose floating division. + +Check memory implications separately. A mathematically valid BigInt may be very +large. Prove extreme paths, but do not execute resource-heavy regression tests +unless the cost is acceptable and intentional. Test the conversion and a smaller +representative behavior instead. + +### 6. Use Verification Attributes Deliberately + +- `#[verus_verify]` on an `impl` applies to all methods in that impl. +- Do not split adjacent inherent impls merely to change verification scope when + one impl-level annotation plus narrow method overrides is clearer. +- Use `#[verus_verify(external)]` only when an item must remain entirely outside + verification and has a separate specification. +- Use `#[verus_verify(external_body)]` when Verus should trust a stated contract + but cannot verify the implementation body. +- Method-level attributes can override the impl-wide default. + +Before diagnosing missing internal markers or macro bugs, inspect braces and +attributes. Confirm the method is actually inside the annotated impl. + +## Diagnosing Verus Failures + +### Translation or Compiler Failure + +1. Reduce to the exact operator, type, attribute, and impl context. +2. Test a one-file reproducer with the same relevant structure. +3. Do not introduce macros, missing impl annotations, or different ownership + patterns unless they exist in the failing code. +4. If a small candidate passes, it is not a reproducer. Keep reducing the real + context or state that the failure was caused by local annotation structure. +5. Inspect `~/verus` only after the local code path is understood. + +A valid verifier bug report must: +- fail on the stated Verus version; +- contain no unrelated repository dependencies when avoidable; +- reproduce the same failure mechanism; +- document any workaround retained in Regorus. + +### Proof Failure + +Treat the first focused failure as evidence: +- failed arithmetic safety means the implementation has an unhandled machine + boundary or needs a justified precondition; +- failed postcondition may indicate a missing helper fact, a representation + mismatch, or an incorrect contract; +- unsupported library internals should be isolated in the narrowest helper, not + used to externalize the verified caller. + +Do not respond to a failed proof by immediately weakening the postcondition. +First trace a concrete input through the runtime behavior. + +## Validation + +After the first substantive edit, immediately run the narrowest check: + +```bash +cargo verus verify --features verus \ + --fwd-verus-args-to roots -- --verify-module number +``` + +Use a fresh target directory when checking for stale macro or compiler behavior. + +After the focused proof passes: + +```bash +cargo test focused_test_name +cargo fmt --all -- --check +git diff --check +``` + +For broader or final validation, use repository commands as appropriate: + +```bash +cargo xtask fmt +cargo xtask clippy +cargo xtask ci-debug +``` + +Report verification counts accurately. Distinguish: +- body-verified functions; +- external-body contracts; +- trusted assumptions; +- runtime tests actually executed; +- extreme tests skipped due to resource cost. + +## Completion Checklist + +- [ ] Contract matches exact executable semantics. +- [ ] Integer/float and `Undefined` distinctions remain intact where relevant. +- [ ] Minimum/maximum signed values and casts were considered. +- [ ] Original executable body is preserved unless a runtime bug was fixed. +- [ ] Proof-only code is inside `proof!` and placed early when possible. +- [ ] No redundant uninterpreted helper or trusted assumption remains. +- [ ] Every `external_body` has the narrowest useful exact contract and a reason. +- [ ] Impl-level verification annotations cover the intended methods without + unnecessary splits. +- [ ] Any claimed verifier reproducer is representative and independently fails. +- [ ] Focused Verus verification passes. +- [ ] Relevant runtime tests, formatting, and diff checks pass. diff --git a/src/number.rs b/src/number.rs index 4804bf15c..d0411e65f 100644 --- a/src/number.rs +++ b/src/number.rs @@ -576,7 +576,6 @@ impl Number { } } - #[verus_verify] #[verus_spec(result => ensures match self@ { @@ -624,7 +623,6 @@ impl Number { } } - #[verus_verify] #[verus_spec(result => ensures match self@ { @@ -676,7 +674,6 @@ impl Number { } } - #[verus_verify] #[verus_spec(result => ensures match (self@, result) { @@ -715,7 +712,6 @@ impl Number { } } - #[verus_verify] #[verus_spec(result => ensures match self@ { @@ -750,7 +746,6 @@ impl Number { .ok_or_else(|| anyhow!("Number::to_big failed")) } - #[verus_verify] #[verus_spec(result => ensures result is Ok, @@ -761,6 +756,7 @@ impl Number { Ok(()) } + // Verus does not yet support overloaded op-assignment operators like `+=`. #[verus_verify(external_body)] #[verus_spec(result => ensures @@ -807,7 +803,6 @@ impl Number { } } - #[verus_verify] #[verus_spec(result => ensures result is Ok, @@ -866,7 +861,6 @@ impl Number { } } - #[verus_verify] #[verus_spec(result => ensures result is Ok, @@ -1066,7 +1060,6 @@ impl Number { Ok(Number::from_bigint_owned(rem)) } - #[verus_verify] #[verus_spec(result => ensures result == self@.is_integer(), @@ -1079,7 +1072,6 @@ impl Number { } } - #[verus_verify] #[verus_spec(result => ensures match self@ { @@ -1180,6 +1172,8 @@ impl Number { let (a, b) = Self::ensure_integers(self, rhs)?; Some(Number::from_bigint_owned(a ^ b)) } + + // Verus does not yet support overloaded assigment operators like `<<=`. #[verus_verify(external_body)] #[verus_spec(result => ensures @@ -1244,7 +1238,6 @@ impl Number { Some(Number::from_bigint_owned(value)) } - #[verus_verify] #[verus_spec(result => ensures match self@ { @@ -1269,7 +1262,6 @@ impl Number { } } - #[verus_verify] #[verus_spec(result => ensures match self@ { @@ -1284,7 +1276,6 @@ impl Number { } } - #[verus_verify] #[verus_spec(result => ensures match self@ { @@ -1299,7 +1290,6 @@ impl Number { } } - #[verus_verify] #[verus_spec(result => ensures match self@ { @@ -1313,6 +1303,7 @@ impl Number { _ => self.clone(), } } + #[verus_spec(result => ensures match result { @@ -1354,8 +1345,20 @@ impl Number { } } - #[verus_verify(external)] + #[verus_spec(result => + ensures + result is Ok, + e >= 0 ==> (result matches Ok(value) && value@ is Integer), + e < 0 ==> (result matches Ok(value) && value@ is Float), + )] pub fn ten_pow(e: i32) -> Result { + proof! { + axiom_f64_ops_deterministic(); + if e < 0 { + let exp = (-(e as i64)) as u32; + assert(exp > 0); + } + } if e >= 0 { Ok(ten_pow_positive(e as u32)) } else { @@ -1507,7 +1510,9 @@ fn pow10_bigint(exp: u32) -> BigInt { #[verus_verify(external_body)] #[verus_spec(result => ensures - result@ matches NumberView::Integer(value) && value > 0, + result@ matches NumberView::Integer(value) + && value > 0 + && (exp > 0 ==> vstd::arithmetic::div_mod::rust_rem(1, value) == 1), )] fn ten_pow_positive(exp: u32) -> Number { if let Some(value) = 10u64.checked_pow(exp) { @@ -1693,4 +1698,13 @@ mod tests { Ok(Number::Float(value)) if value == 0.0 )); } + + #[test] + fn ten_pow_uses_unsigned_minimum_exponent_magnitude() { + assert_eq!((-(i32::MIN as i64)) as u32, 1u32 << 31); + assert!(matches!( + Number::ten_pow(-3), + Ok(Number::Float(value)) if value == 0.001 + )); + } } diff --git a/src/verify/number_specs.rs b/src/verify/number_specs.rs index 0ca6b3dea..a31b0849d 100644 --- a/src/verify/number_specs.rs +++ b/src/verify/number_specs.rs @@ -206,13 +206,6 @@ impl NumberView { } } -pub assume_specification[ Number::ten_pow ](e: i32) -> (result: anyhow::Result) - ensures - result is Ok, - e >= 0 ==> (result matches Ok(value) && value@ is Integer), - e < 0 ==> (result matches Ok(value) && value@ is Float), -; - impl FromSpecImpl for Number { open spec fn obeys_from_spec() -> bool { From c1656c102b4572d30e41edc366f5266c8f6fc90d Mon Sep 17 00:00:00 2001 From: Jay Lorch Date: Fri, 24 Jul 2026 11:45:24 -0700 Subject: [PATCH 08/41] Remove some external_body --- .github/skills/verus-verification/SKILL.md | 4 +++ src/number.rs | 29 ++++++++++++++++------ src/verify/bigint_assumptions.rs | 10 ++++++++ src/verify/f64_assumptions.rs | 5 ++++ 4 files changed, 40 insertions(+), 8 deletions(-) diff --git a/.github/skills/verus-verification/SKILL.md b/.github/skills/verus-verification/SKILL.md index ddb916ec0..5e8b5653b 100644 --- a/.github/skills/verus-verification/SKILL.md +++ b/.github/skills/verus-verification/SKILL.md @@ -35,6 +35,10 @@ honest trusted boundary. - Before editing, compare the function with `main` or the relevant base. - Keep executable statements unchanged unless the task explicitly requires a runtime fix. + - Never use conditional compilation to give Verus and ordinary Rust different + executable bodies or behavior. If Verus cannot verify the shared body, + retain the narrowest `external_body` boundary and document the unsupported + construct. - Put ghost reasoning in `proof!` blocks. Move proof work to the beginning of the function when it depends only on inputs. - Afterward, inspect the focused diff against the base and confirm that only diff --git a/src/number.rs b/src/number.rs index d0411e65f..d9d097cf2 100644 --- a/src/number.rs +++ b/src/number.rs @@ -486,7 +486,6 @@ impl PartialOrd for Number { #[verus_verify] impl Number { - #[verus_verify(external_body)] #[verus_spec(result => ensures match self@ { @@ -533,7 +532,6 @@ impl Number { } } - #[verus_verify(external_body)] #[verus_spec(result => ensures match self@ { @@ -728,6 +726,7 @@ impl Number { self.to_bigint_rc() } + // Verus does not support the formatting internals used by `anyhow!`. #[verus_verify(external_body)] #[verus_spec(result => ensures @@ -813,6 +812,7 @@ impl Number { Ok(()) } + // Verus does not yet support overloaded op-assignment operators like `-=`. #[verus_verify(external_body)] #[verus_spec(result => ensures @@ -871,19 +871,33 @@ impl Number { Ok(()) } - #[verus_verify(external_body)] #[verus_spec(result => ensures result matches Ok(value) && self@.mul_ensures(rhs@, value@), )] pub fn mul(&self, rhs: &Self) -> Result { - if matches!(self, Number::Float(_)) || matches!(rhs, Number::Float(_)) { - return Ok(Number::normalize_float( - self.to_f64_lossy() * rhs.to_f64_lossy(), - )); + proof! { + axiom_f64_ops_deterministic(); + axiom_bigint_obeys_mul_spec(); + reveal(NumberView::integer_value); + reveal(NumberView::mul_ensures); + assert(forall|lhs: int, rhs_value: int| + self@.integer_value() == Some(lhs) + && rhs@.integer_value() == Some(rhs_value) + ==> self@.mul_ensures( + rhs@, + NumberView::Integer(lhs * rhs_value), + )); + if let (Number::UInt(lhs), Number::UInt(rhs_value)) = (self, rhs) { + assert((*lhs as int) * (*rhs_value as int) <= u128::MAX as int) + by(nonlinear_arith); + } } match (self, rhs) { + (Number::Float(_), _) | (_, Number::Float(_)) => Ok(Number::normalize_float( + self.to_f64_lossy() * rhs.to_f64_lossy(), + )), (Number::UInt(a), Number::UInt(b)) => { let product = (*a as u128) * (*b as u128); if let Ok(v) = u64::try_from(product) { @@ -919,7 +933,6 @@ impl Number { let product = (**a).clone() * other.to_bigint_owned().unwrap(); Ok(Number::from_bigint_owned(product)) } - _ => unreachable!(), } } diff --git a/src/verify/bigint_assumptions.rs b/src/verify/bigint_assumptions.rs index 6d6a07446..e8892efbe 100644 --- a/src/verify/bigint_assumptions.rs +++ b/src/verify/bigint_assumptions.rs @@ -418,6 +418,16 @@ pub assume_specification<'a>[ >::sub ](x: BigInt // Multiplication +pub axiom fn axiom_bigint_obeys_mul_spec() + ensures + ::obeys_mul_spec(), + forall|lhs: BigInt, rhs: BigInt| #[trigger] + ::mul_req(lhs, rhs), + forall|lhs: BigInt, rhs: BigInt| #[trigger] + ::mul_spec(lhs, rhs)@ + == lhs@ * rhs@, +; + pub assume_specification[ ::mul ](x: BigInt, y: BigInt) -> (o: BigInt) ensures o@ == x@ * y@, diff --git a/src/verify/f64_assumptions.rs b/src/verify/f64_assumptions.rs index c304cffc4..640376c9f 100644 --- a/src/verify/f64_assumptions.rs +++ b/src/verify/f64_assumptions.rs @@ -49,6 +49,11 @@ pub axiom fn axiom_f64_ops_deterministic() ::obeys_sub_spec(), ::obeys_mul_spec(), ::obeys_div_spec(), + forall|lhs: f64, rhs: f64| #[trigger] + ::mul_req(lhs, rhs), + forall|lhs: f64, rhs: f64| #[trigger] + ::mul_spec(lhs, rhs) + == lhs.ieee_mul(rhs), forall|n: i8, f: f64| float_cast_spec::(n, f) ==> f == ieee_float_cast::(n), forall|n: u8, f: f64| float_cast_spec::(n, f) ==> f == ieee_float_cast::(n), forall|n: i8, f: f64| float_cast_spec::(f, n) ==> n == ieee_float_cast::(f), From 9698fd0d14928e1830bec0caabba3dc8844940c1 Mon Sep 17 00:00:00 2001 From: Jay Lorch Date: Fri, 24 Jul 2026 15:52:07 -0700 Subject: [PATCH 09/41] Prove correctness of Number::div (including bug fix) --- src/number.rs | 55 +++-- src/verify/bigint_assumptions.rs | 50 +++-- src/verify/f64_assumptions.rs | 5 + src/verify/mod.rs | 2 + src/verify/number_proofs.rs | 371 +++++++++++++++++++++++++++++++ src/verify/number_specs.rs | 28 +++ 6 files changed, 481 insertions(+), 30 deletions(-) create mode 100644 src/verify/number_proofs.rs diff --git a/src/number.rs b/src/number.rs index d9d097cf2..3135995e6 100644 --- a/src/number.rs +++ b/src/number.rs @@ -39,6 +39,8 @@ use crate::verify::bigint_proofs::*; #[cfg(verus_keep_ghost)] use crate::verify::f64_assumptions::*; #[cfg(verus_keep_ghost)] +use crate::verify::number_proofs::*; +#[cfg(verus_keep_ghost)] use crate::verify::number_specs::*; #[cfg(verus_keep_ghost)] use vstd::arithmetic::power2::pow2; @@ -936,29 +938,39 @@ impl Number { } } + // Verus does not support the formatting internals used by `anyhow!`. #[verus_verify(external_body)] + fn division_by_zero_error() -> anyhow::Error { + anyhow!("division by zero") + } + #[verus_spec(result => ensures - match (self@, rhs@, result) { - (NumberView::Integer(lhs), NumberView::Integer(divisor), Ok(value)) => { - &&& divisor != 0 - &&& if vstd::arithmetic::div_mod::rust_rem(lhs, divisor) == 0 { - value@ == NumberView::Integer(vstd::arithmetic::div_mod::rust_div(lhs, divisor)) - } else { - value@ == NumberView::Float(self.spec_to_f64_lossy() / rhs.spec_to_f64_lossy()) - } - }, - (NumberView::Float(_), _, Ok(value)) - | (NumberView::Integer(_), NumberView::Float(_), Ok(value)) => { - &&& !rhs@.is_zero() - &&& value@ == NumberView::Float(self.spec_to_f64_lossy() / rhs.spec_to_f64_lossy()) - }, - (_, _, Err(_)) => rhs@.is_zero(), + match result { + Ok(value) => self@.div_ensures( + rhs@, + self.spec_to_f64_lossy(), + rhs.spec_to_f64_lossy(), + value@, + ), + Err(_) => rhs@.is_zero(), }, )] pub fn divide(self, rhs: &Self) -> Result { + proof! { + axiom_f64_ops_deterministic(); + axiom_bigint_obeys_div_rem_spec(); + lemma_div_ensures_cases( + self@, + rhs@, + self.spec_to_f64_lossy(), + rhs.spec_to_f64_lossy(), + ); + lemma_number_primitive_division_facts(&self, rhs); + } + if rhs.is_zero() { - bail!("division by zero"); + return Err(Self::division_by_zero_error()); } if matches!(self, Number::Float(_)) || matches!(rhs, Number::Float(_)) { @@ -980,6 +992,9 @@ impl Number { Ok(Number::from_bigint_owned(quotient)) } else if *a % *b == 0 { if let Some(q) = a.checked_div(*b) { + proof! { + lemma_checked_div_matches_rust_i64(*a, *b, q); + } Ok(Number::Int(q)) } else { let quotient = BigInt::from(*a) / BigInt::from(*b); @@ -1720,4 +1735,12 @@ mod tests { Ok(Number::Float(value)) if value == 0.001 )); } + + #[test] + fn divide_handles_minimum_i64_by_negative_one() { + assert!(matches!( + Number::Int(i64::MIN).divide(&Number::Int(-1)), + Ok(Number::UInt(value)) if value == 1u64 << 63 + )); + } } diff --git a/src/verify/bigint_assumptions.rs b/src/verify/bigint_assumptions.rs index e8892efbe..2b5df404a 100644 --- a/src/verify/bigint_assumptions.rs +++ b/src/verify/bigint_assumptions.rs @@ -23,6 +23,7 @@ verus! { use core::cmp::Ordering; use num_bigint::BigInt; +use vstd::arithmetic::div_mod::{rust_div, rust_rem}; use vstd::arithmetic::power2::pow2; use vstd::std_specs::cmp::OrdSpec; @@ -500,74 +501,95 @@ pub assume_specification<'a>[ >::mul ](x: BigInt, // Division +pub axiom fn axiom_bigint_obeys_div_rem_spec() + ensures + ::obeys_div_spec(), + ::obeys_rem_spec(), + forall|lhs: BigInt, rhs: BigInt| rhs@ != 0 ==> #[trigger] + ::div_req(lhs, rhs), + forall|lhs: BigInt, rhs: BigInt| rhs@ != 0 ==> #[trigger] + ::rem_req(lhs, rhs), + forall|lhs: BigInt, rhs: BigInt| rhs@ != 0 ==> #[trigger] + ::div_spec(lhs, rhs)@ + == rust_div(lhs@, rhs@), + forall|lhs: BigInt, rhs: BigInt| rhs@ != 0 ==> #[trigger] + ::rem_spec(lhs, rhs)@ + == rust_rem(lhs@, rhs@), +; + pub assume_specification[ ::div ](x: BigInt, y: BigInt) -> (o: BigInt) ensures - o@ == x@ / y@, + y@ != 0 ==> o@ == rust_div(x@, y@), ; pub assume_specification<'a>[ >::div ](x: BigInt, y: &BigInt) -> (o: BigInt) ensures - o@ == x@ / (*y)@, + y@ != 0 ==> o@ == rust_div(x@, (*y)@), ; pub assume_specification<'a, 'b>[ <&BigInt as core::ops::Div<&BigInt>>::div ](x: &'b BigInt, y: &BigInt) -> (o: BigInt) ensures - o@ == (*x)@ / (*y)@, + y@ != 0 ==> o@ == rust_div((*x)@, (*y)@), +; + +pub assume_specification<'a, 'b>[ <&BigInt as core::ops::Rem<&BigInt>>::rem ](x: &'b BigInt, y: &BigInt) -> (o: BigInt) + ensures + y@ != 0 ==> o@ == rust_rem((*x)@, (*y)@), ; pub assume_specification[ >::div ](x: BigInt, y: u8) -> (o: BigInt) ensures - o@ == x@ / (y as int), + y != 0 ==> o@ == rust_div(x@, y as int), ; pub assume_specification[ >::div ](x: BigInt, y: u16) -> (o: BigInt) ensures - o@ == x@ / (y as int), + y != 0 ==> o@ == rust_div(x@, y as int), ; pub assume_specification[ >::div ](x: BigInt, y: u32) -> (o: BigInt) ensures - o@ == x@ / (y as int), + y != 0 ==> o@ == rust_div(x@, y as int), ; pub assume_specification[ >::div ](x: BigInt, y: u64) -> (o: BigInt) ensures - o@ == x@ / (y as int), + y != 0 ==> o@ == rust_div(x@, y as int), ; pub assume_specification[ >::div ](x: BigInt, y: u128) -> (o: BigInt) ensures - o@ == x@ / (y as int), + y != 0 ==> o@ == rust_div(x@, y as int), ; pub assume_specification[ >::div ](x: BigInt, y: i8) -> (o: BigInt) ensures - o@ == x@ / (y as int), + y != 0 ==> o@ == rust_div(x@, y as int), ; pub assume_specification[ >::div ](x: BigInt, y: i16) -> (o: BigInt) ensures - o@ == x@ / (y as int), + y != 0 ==> o@ == rust_div(x@, y as int), ; pub assume_specification[ >::div ](x: BigInt, y: i32) -> (o: BigInt) ensures - o@ == x@ / (y as int), + y != 0 ==> o@ == rust_div(x@, y as int), ; pub assume_specification[ >::div ](x: BigInt, y: i64) -> (o: BigInt) ensures - o@ == x@ / (y as int), + y != 0 ==> o@ == rust_div(x@, y as int), ; pub assume_specification[ >::div ](x: BigInt, y: i128) -> (o: BigInt) ensures - o@ == x@ / (y as int), + y != 0 ==> o@ == rust_div(x@, y as int), ; pub assume_specification<'a>[ >::div ](x: BigInt, y: &u8) -> (o: BigInt) ensures - o@ == x@ / (*y as int), + *y != 0 ==> o@ == rust_div(x@, *y as int), ; } // end verus! diff --git a/src/verify/f64_assumptions.rs b/src/verify/f64_assumptions.rs index 640376c9f..0f62a1fe4 100644 --- a/src/verify/f64_assumptions.rs +++ b/src/verify/f64_assumptions.rs @@ -54,6 +54,11 @@ pub axiom fn axiom_f64_ops_deterministic() forall|lhs: f64, rhs: f64| #[trigger] ::mul_spec(lhs, rhs) == lhs.ieee_mul(rhs), + forall|lhs: f64, rhs: f64| #[trigger] + ::div_req(lhs, rhs), + forall|lhs: f64, rhs: f64| #[trigger] + ::div_spec(lhs, rhs) + == lhs.ieee_div(rhs), forall|n: i8, f: f64| float_cast_spec::(n, f) ==> f == ieee_float_cast::(n), forall|n: u8, f: f64| float_cast_spec::(n, f) ==> f == ieee_float_cast::(n), forall|n: i8, f: f64| float_cast_spec::(f, n) ==> n == ieee_float_cast::(f), diff --git a/src/verify/mod.rs b/src/verify/mod.rs index 9aa3afdb3..90e46746f 100644 --- a/src/verify/mod.rs +++ b/src/verify/mod.rs @@ -7,6 +7,8 @@ pub(crate) mod f64_assumptions; #[cfg(all(test, not(verus_keep_ghost)))] mod f64_assumptions; #[cfg(verus_keep_ghost)] +pub(crate) mod number_proofs; +#[cfg(verus_keep_ghost)] pub mod number_specs; #[cfg(verus_keep_ghost)] pub(crate) mod utils; diff --git a/src/verify/number_proofs.rs b/src/verify/number_proofs.rs new file mode 100644 index 000000000..990b99854 --- /dev/null +++ b/src/verify/number_proofs.rs @@ -0,0 +1,371 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#[cfg(feature = "verus")] +use vstd::prelude::*; + +#[cfg(feature = "verus")] +verus! { + +use super::number_specs::NumberView; +use crate::number::Number; +use vstd::arithmetic::div_mod::{ + lemma_div_of0, + lemma_fundamental_div_mod, + rust_div, + rust_rem, +}; +use vstd::arithmetic::mul::{ + lemma_mul_cancels_negatives, + lemma_mul_increases, + lemma_mul_strictly_increases, + lemma_mul_unary_negation, +}; +use vstd::std_specs::ops::*; + +pub proof fn lemma_div_ensures_cases( + lhs: NumberView, + rhs: NumberView, + lhs_float: f64, + rhs_float: f64, +) + ensures + forall|integer_lhs: int, divisor: int| + lhs == NumberView::Integer(integer_lhs) + && rhs == NumberView::Integer(divisor) + && divisor != 0 + && rust_rem(integer_lhs, divisor) == 0 + ==> #[trigger] lhs.div_ensures( + rhs, + lhs_float, + rhs_float, + NumberView::Integer(rust_div(integer_lhs, divisor)), + ), + forall|integer_lhs: int, divisor: int| + lhs == NumberView::Integer(integer_lhs) + && rhs == NumberView::Integer(divisor) + && divisor != 0 + && rust_rem(integer_lhs, divisor) != 0 + ==> #[trigger] lhs.div_ensures( + rhs, + lhs_float, + rhs_float, + NumberView::Float(lhs_float / rhs_float), + ), + (lhs is Float || rhs is Float) && !rhs.is_zero() + ==> lhs.div_ensures( + rhs, + lhs_float, + rhs_float, + NumberView::Float(lhs_float / rhs_float), + ), +{ + reveal(NumberView::is_zero); + reveal(NumberView::div_ensures); +} + +proof fn lemma_rust_div_rem_identity(lhs: int, rhs: int) + requires + rhs != 0, + ensures + lhs == rhs * rust_div(lhs, rhs) + rust_rem(lhs, rhs), +{ + reveal(rust_div); + reveal(rust_rem); + if lhs == 0 { + lemma_div_of0(rhs); + lemma_fundamental_div_mod(lhs, rhs); + } else if lhs > 0 { + lemma_fundamental_div_mod(lhs, rhs); + } else { + let positive_lhs = -lhs; + let quotient = positive_lhs / rhs; + let remainder = positive_lhs % rhs; + lemma_fundamental_div_mod(positive_lhs, rhs); + lemma_mul_unary_negation(rhs, quotient); + assert(positive_lhs == rhs * quotient + remainder); + assert(rhs * (-quotient) == -(rhs * quotient)); + assert(lhs == rhs * (-quotient) + (-remainder)); + assert(rust_div(lhs, rhs) == -quotient); + assert(rust_rem(lhs, rhs) == -remainder); + } +} + +proof fn lemma_exact_rust_div_fits( + dividend: int, + divisor: int, + minimum: int, + maximum: int, +) + requires + minimum == -maximum - 1, + minimum <= dividend <= maximum, + minimum <= divisor <= maximum, + divisor != 0, + !(dividend == minimum && divisor == -1), + rust_rem(dividend, divisor) == 0, + ensures + minimum <= rust_div(dividend, divisor) <= maximum, +{ + let quotient = rust_div(dividend, divisor); + lemma_rust_div_rem_identity(dividend, divisor); + assert(dividend == divisor * quotient); + + if quotient < minimum { + assert(quotient <= minimum - 1); + assert(-quotient > maximum); + if divisor > 0 { + lemma_mul_increases(divisor, -quotient); + lemma_mul_unary_negation(divisor, quotient); + assert(divisor * quotient <= quotient); + assert(dividend < minimum); + } else { + assert(divisor <= -1); + lemma_mul_increases(-divisor, -quotient); + lemma_mul_cancels_negatives(divisor, quotient); + assert(dividend > maximum); + } + } else if quotient > maximum { + assert(quotient >= maximum + 1); + assert(quotient >= -minimum); + if divisor > 0 { + lemma_mul_increases(divisor, quotient); + assert(dividend > maximum); + } else { + assert(divisor <= -1); + lemma_mul_increases(-divisor, quotient); + lemma_mul_unary_negation(divisor, quotient); + assert(dividend <= minimum); + assert(dividend == minimum); + if divisor < -1 { + lemma_mul_strictly_increases(-divisor, quotient); + assert(quotient < (-divisor) * quotient); + assert((-divisor) * quotient == -dividend); + assert(false); + } + assert(divisor == -1); + } + } +} + +pub proof fn lemma_rust_div_fits_i64(lhs: i64, rhs: i64) + requires + rhs != 0, + !(lhs == i64::MIN && rhs == -1), + rust_rem(lhs as int, rhs as int) == 0, + ensures + i64::MIN as int <= rust_div(lhs as int, rhs as int) <= i64::MAX as int, +{ + lemma_exact_rust_div_fits( + lhs as int, + rhs as int, + i64::MIN as int, + i64::MAX as int, + ); +} + +pub proof fn lemma_checked_div_matches_rust_i64(lhs: i64, rhs: i64, quotient: i64) + requires + rhs != 0, + !(lhs == i64::MIN && rhs == -1), + rust_rem(lhs as int, rhs as int) == 0, + i64::checked_div(lhs, rhs) == Some(quotient), + ensures + quotient as int == rust_div(lhs as int, rhs as int), +{ + let mathematical_quotient = rust_div(lhs as int, rhs as int); + lemma_rust_div_fits_i64(lhs, rhs); + assert(i64::MIN as int <= mathematical_quotient <= i64::MAX as int); + assert(quotient == mathematical_quotient as i64); +} + +pub proof fn lemma_remainder_matches_rust_i64(lhs: i64, rhs: i64) + requires + rhs != 0, + !(lhs == i64::MIN && rhs == -1), + ensures + ::obeys_rem_spec(), + ::rem_req(lhs, rhs), + ::rem_spec(lhs, rhs) as int == rust_rem(lhs as int, rhs as int), +{ + assert(::obeys_rem_spec()); + assert(::rem_req(lhs, rhs)); + reveal(rust_rem); +} + +pub proof fn lemma_remainder_matches_rust_i128(lhs: i128, rhs: i128) + requires + rhs != 0, + !(lhs == i128::MIN && rhs == -1), + ensures + ::obeys_rem_spec(), + ::rem_req(lhs, rhs), + ::rem_spec(lhs, rhs) as int == rust_rem(lhs as int, rhs as int), +{ + assert(::obeys_rem_spec()); + assert(::rem_req(lhs, rhs)); + reveal(rust_rem); +} + +pub proof fn lemma_remainder_matches_rust_u64(lhs: u64, rhs: u64) + requires + rhs != 0, + ensures + ::obeys_rem_spec(), + ::rem_req(lhs, rhs), + rust_rem(lhs as int, rhs as int) == 0 ==> + ::rem_spec(lhs, rhs) == 0, + ::rem_spec(lhs, rhs) != 0 ==> + rust_rem(lhs as int, rhs as int) != 0, +{ + assert(::obeys_rem_spec()); + assert(::rem_req(lhs, rhs)); + reveal(rust_rem); + if rust_rem(lhs as int, rhs as int) == 0 { + if lhs == 0 { + lemma_div_of0(rhs as int); + } else { + assert(lhs > 0); + } + assert((lhs as int) % (rhs as int) == 0); + assert(::rem_spec(lhs, rhs) + == ((lhs as int) % (rhs as int)) as u64); + } +} + +pub proof fn lemma_number_primitive_division_facts(lhs: &Number, rhs: &Number) + ensures + match (lhs, rhs) { + (Number::Int(lhs), Number::Int(rhs)) => + *rhs != 0 && !(*lhs == i64::MIN && *rhs == -1) ==> + ::obeys_rem_spec() + && ::rem_req(*lhs, *rhs) + && ::rem_spec(*lhs, *rhs) as int + == rust_rem(*lhs as int, *rhs as int), + (Number::UInt(lhs), Number::UInt(rhs)) => + *rhs != 0 ==> + ::obeys_rem_spec() + && ::rem_req(*lhs, *rhs) + && (rust_rem(*lhs as int, *rhs as int) == 0 ==> + ::rem_spec(*lhs, *rhs) == 0) + && (::rem_spec(*lhs, *rhs) != 0 ==> + rust_rem(*lhs as int, *rhs as int) != 0), + (Number::Int(lhs), Number::UInt(rhs)) => + *rhs != 0 ==> + *rhs as i128 > 0 + && ::obeys_rem_spec() + && ::rem_req(*lhs as i128, *rhs as i128) + && ::rem_spec(*lhs as i128, *rhs as i128) as int + == rust_rem(*lhs as int, *rhs as int) + && (rust_rem(*lhs as int, *rhs as int) == 0 ==> + i128::MIN as int <= rust_div(*lhs as int, *rhs as int) + <= i128::MAX as int), + (Number::UInt(lhs), Number::Int(rhs)) => + *rhs != 0 ==> + *lhs as i128 >= 0 + && *rhs as i128 != 0 + && rust_div(*lhs as int, *rhs as int) + == (*lhs as int) / (*rhs as int) + && ::obeys_rem_spec() + && ::rem_req(*lhs as i128, *rhs as i128) + && (rust_rem(*lhs as int, *rhs as int) == 0 ==> + ::rem_spec(*lhs as i128, *rhs as i128) == 0) + && (::rem_spec(*lhs as i128, *rhs as i128) != 0 ==> + rust_rem(*lhs as int, *rhs as int) != 0) + && (rust_rem(*lhs as int, *rhs as int) == 0 ==> + i128::MIN as int <= rust_div(*lhs as int, *rhs as int) + <= i128::MAX as int), + _ => true, + }, +{ + match (lhs, rhs) { + (Number::Int(lhs), Number::Int(rhs)) => { + if *rhs != 0 && !(*lhs == i64::MIN && *rhs == -1) { + lemma_remainder_matches_rust_i64(*lhs, *rhs); + } + }, + (Number::UInt(lhs), Number::UInt(rhs)) => { + if *rhs != 0 { + lemma_remainder_matches_rust_u64(*lhs, *rhs); + } + }, + (Number::Int(lhs), Number::UInt(rhs)) => { + if *rhs != 0 { + lemma_remainder_matches_rust_i128(*lhs as i128, *rhs as i128); + if rust_rem(*lhs as int, *rhs as int) == 0 { + lemma_rust_div_fits_i128(*lhs as i128, *rhs as i128); + } + } + }, + (Number::UInt(lhs), Number::Int(rhs)) => { + if *rhs != 0 { + lemma_nonnegative_div_matches_rust(*lhs as i128, *rhs as i128); + lemma_nonnegative_remainder_matches_rust(*lhs as i128, *rhs as i128); + if rust_rem(*lhs as int, *rhs as int) == 0 { + lemma_rust_div_fits_i128(*lhs as i128, *rhs as i128); + } + } + }, + _ => {}, + } +} + +pub proof fn lemma_rust_div_fits_i128(lhs: i128, rhs: i128) + requires + rhs != 0, + !(lhs == i128::MIN && rhs == -1), + rust_rem(lhs as int, rhs as int) == 0, + ensures + i128::MIN as int <= rust_div(lhs as int, rhs as int) <= i128::MAX as int, +{ + lemma_exact_rust_div_fits( + lhs as int, + rhs as int, + i128::MIN as int, + i128::MAX as int, + ); +} + +pub proof fn lemma_nonnegative_div_matches_rust(lhs: i128, rhs: i128) + requires + lhs >= 0, + rhs != 0, + ensures + rust_div(lhs as int, rhs as int) == (lhs as int) / (rhs as int), +{ + reveal(rust_div); + if lhs == 0 { + lemma_div_of0(rhs as int); + } else { + assert(lhs > 0); + } +} + +pub proof fn lemma_nonnegative_remainder_matches_rust(lhs: i128, rhs: i128) + requires + lhs >= 0, + rhs != 0, + ensures + ::obeys_rem_spec(), + ::rem_req(lhs, rhs), + rust_rem(lhs as int, rhs as int) == 0 ==> + ::rem_spec(lhs, rhs) == 0, + ::rem_spec(lhs, rhs) != 0 ==> + rust_rem(lhs as int, rhs as int) != 0, +{ + assert(::obeys_rem_spec()); + assert(::rem_req(lhs, rhs)); + reveal(rust_rem); + if rust_rem(lhs as int, rhs as int) == 0 { + if lhs == 0 { + lemma_div_of0(rhs as int); + } else { + assert(lhs > 0); + } + assert((lhs as int) % (rhs as int) == 0); + assert(::rem_spec(lhs, rhs) + == ((lhs as int) % (rhs as int)) as i128); + } +} + +} diff --git a/src/verify/number_specs.rs b/src/verify/number_specs.rs index a31b0849d..f2b78c44c 100644 --- a/src/verify/number_specs.rs +++ b/src/verify/number_specs.rs @@ -204,6 +204,33 @@ impl NumberView { }, } } + + pub open spec fn div_ensures( + self: Self, + rhs: Self, + lhs_float: f64, + rhs_float: f64, + result: Self, + ) -> bool + { + match (self, rhs) { + (NumberView::Integer(lhs), NumberView::Integer(divisor)) => { + &&& divisor != 0 + &&& if vstd::arithmetic::div_mod::rust_rem(lhs, divisor) == 0 { + result == NumberView::Integer( + vstd::arithmetic::div_mod::rust_div(lhs, divisor), + ) + } else { + result == NumberView::Float(lhs_float / rhs_float) + } + }, + (NumberView::Float(_), _) | (NumberView::Integer(_), NumberView::Float(_)) => { + &&& !rhs.is_zero() + &&& result == NumberView::Float(lhs_float / rhs_float) + }, + } + } + } impl FromSpecImpl for Number { @@ -299,6 +326,7 @@ impl Number { }, } } + } impl OrdSpecImpl for Number { From 78438781d628920edd27e1518dc585590b5dfd70 Mon Sep 17 00:00:00 2001 From: Jay Lorch Date: Fri, 24 Jul 2026 17:51:05 -0700 Subject: [PATCH 10/41] Fix bug in Number::modulo --- src/number.rs | 71 ++++++++++++++++++++++++++++++++++++--------- src/verify/utils.rs | 5 ++++ 2 files changed, 63 insertions(+), 13 deletions(-) diff --git a/src/number.rs b/src/number.rs index 3135995e6..2d1ceac65 100644 --- a/src/number.rs +++ b/src/number.rs @@ -18,7 +18,7 @@ use core::cmp::Ordering; use core::fmt::{Debug, Formatter}; use core::str::FromStr; -use anyhow::{anyhow, bail, Result}; +use anyhow::{anyhow, Result}; use num_bigint::BigInt as NumBigInt; #[allow(unused)] use num_traits::float::FloatCore; @@ -938,10 +938,8 @@ impl Number { } } - // Verus does not support the formatting internals used by `anyhow!`. - #[verus_verify(external_body)] - fn division_by_zero_error() -> anyhow::Error { - anyhow!("division by zero") + fn make_error(message: &str) -> anyhow::Error { + anyhow::Error::msg(message.to_string()) } #[verus_spec(result => @@ -970,7 +968,7 @@ impl Number { } if rhs.is_zero() { - return Err(Self::division_by_zero_error()); + return Err(Self::make_error("division by zero")); } if matches!(self, Number::Float(_)) || matches!(rhs, Number::Float(_)) { @@ -1061,18 +1059,23 @@ impl Number { } } - #[verus_verify(external_body)] #[verus_spec(result => ensures - match (self@.to_int(), rhs@.to_int(), result) { - (Some(a), Some(b), Ok(value)) => { - b != 0 && value@ == NumberView::Integer(vstd::arithmetic::div_mod::rust_rem(a, b)) - }, - (_, _, Ok(_)) => false, - (_, _, Err(_)) => rhs@.is_zero() || !self@.is_integer() || !rhs@.is_integer(), + match (self@.to_int(), rhs@.to_int()) { + (Some(a), Some(b)) => + if b == 0 { + result is Err + } else { + result matches Ok(value) + && value@ == NumberView::Integer( + vstd::arithmetic::div_mod::rust_rem(a, b), + ) + }, + _ => result is Err, }, )] pub fn modulo(self, rhs: &Self) -> Result { +<<<<<<< HEAD // Conversion fails for a non-integral float, and also for an integral // one whose magnitude exceeds 2^53, which cannot be represented exactly. let (a, b) = match (self.to_bigint_owned(), rhs.to_bigint_owned()) { @@ -1084,6 +1087,23 @@ impl Number { bail!("modulo by zero"); } +======= + proof! { + axiom_bigint_obeys_div_rem_spec(); + } + + // Conversion fails for a non-integral float, and also for an integral + // one whose magnitude exceeds 2^53, which cannot be represented exactly. + let (a, b) = match (self.to_bigint_owned(), rhs.to_bigint_owned()) { + (Some(a), Some(b)) => (a, b), + _ => return Err(Self::make_error("modulo on floating-point number")), + }; + + if b.is_zero() { + return Err(Self::make_error("modulo by zero")); + } + +>>>>>>> efd56bf (Fix bug in Number::modulo) let rem = a % &b; Ok(Number::from_bigint_owned(rem)) } @@ -1743,4 +1763,29 @@ mod tests { Ok(Number::UInt(value)) if value == 1u64 << 63 )); } + + #[test] + fn modulo_handles_floats_that_are_really_integers() { + // An integral float is a valid operand. + assert!(matches!( + Number::Float(4.0).modulo(&Number::Int(3)), + Ok(Number::UInt(1)) + )); + // `1e300` has no fractional part, but it is too large to convert to an + // integer exactly. This must report an error, not panic. + assert_eq!( + Number::Float(1e300) + .modulo(&Number::Int(3)) + .err() + .map(|e| e.to_string()), + Some("modulo on floating-point number".to_string()) + ); + assert_eq!( + Number::Int(3) + .modulo(&Number::Float(1e300)) + .err() + .map(|e| e.to_string()), + Some("modulo on floating-point number".to_string()) + ); + } } diff --git a/src/verify/utils.rs b/src/verify/utils.rs index a6d0f9c08..8c222b02a 100644 --- a/src/verify/utils.rs +++ b/src/verify/utils.rs @@ -35,6 +35,11 @@ fn my_test_verus_format(fcn: &'static str, x: u32) -> String #[verifier::external_body] pub struct ExAnyhowError(anyhow::Error); +#[cfg(verus_keep_ghost)] +pub assume_specification[ + anyhow::Error::msg:: +](message: M) -> (error: anyhow::Error); + #[cfg(verus_keep_ghost)] #[verifier::external_body] pub fn verus_bail_helper() -> Result From 59572bff7a8d797445ddbd0b351bd5c1463eae60 Mon Sep 17 00:00:00 2001 From: Jay Lorch Date: Fri, 24 Jul 2026 19:28:54 -0700 Subject: [PATCH 11/41] Support anyhow! --- src/lib.rs | 4 ++++ src/number.rs | 10 +++------- src/verify/utils.rs | 23 +++++++++++++++++++++++ 3 files changed, 30 insertions(+), 7 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 18d1b16c0..7fc6baab5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,6 +6,10 @@ // The `verus` feature is only used during verification, never in production // builds, so the forbid remains in force for all shipped code. #![cfg_attr(not(feature = "verus"), forbid(unsafe_code))] +// `anyhow!` with a literal lowers to `Arguments::from_str`, which is unstable. +// Verus needs to name it to give it a specification. Verification builds use the +// Verus toolchain, so this gate never applies to shipped code. +#![cfg_attr(verus_keep_ghost, feature(fmt_arguments_from_str))] // Ensure that all lint names are valid. #![deny(unknown_lints)] // Fail-fast lints: correctness, safety, and API surface diff --git a/src/number.rs b/src/number.rs index 2d1ceac65..97627fde6 100644 --- a/src/number.rs +++ b/src/number.rs @@ -938,10 +938,6 @@ impl Number { } } - fn make_error(message: &str) -> anyhow::Error { - anyhow::Error::msg(message.to_string()) - } - #[verus_spec(result => ensures match result { @@ -968,7 +964,7 @@ impl Number { } if rhs.is_zero() { - return Err(Self::make_error("division by zero")); + return Err(anyhow!("division by zero")); } if matches!(self, Number::Float(_)) || matches!(rhs, Number::Float(_)) { @@ -1096,11 +1092,11 @@ impl Number { // one whose magnitude exceeds 2^53, which cannot be represented exactly. let (a, b) = match (self.to_bigint_owned(), rhs.to_bigint_owned()) { (Some(a), Some(b)) => (a, b), - _ => return Err(Self::make_error("modulo on floating-point number")), + _ => return Err(anyhow!("modulo on floating-point number")), }; if b.is_zero() { - return Err(Self::make_error("modulo by zero")); + return Err(anyhow!("modulo by zero")); } >>>>>>> efd56bf (Fix bug in Number::modulo) diff --git a/src/verify/utils.rs b/src/verify/utils.rs index 8c222b02a..9a73c35be 100644 --- a/src/verify/utils.rs +++ b/src/verify/utils.rs @@ -40,6 +40,29 @@ pub assume_specification ](message: M) -> (error: anyhow::Error); +// The `anyhow!` macro expands to `must_use(format_err(format_args!(..)))`, so +// each of those pieces needs a specification. None of them promises anything +// about the resulting error, which is all the callers rely on. +#[cfg(verus_keep_ghost)] +#[verifier::external_type_specification] +#[verifier::external_body] +pub struct ExFormatArguments<'a>(core::fmt::Arguments<'a>); + +#[cfg(verus_keep_ghost)] +pub assume_specification<'a>[ + core::fmt::Arguments::<'a>::from_str +](message: &'static str) -> (args: core::fmt::Arguments<'a>); + +#[cfg(verus_keep_ghost)] +pub assume_specification<'a>[ + anyhow::__private::format_err +](args: core::fmt::Arguments<'a>) -> (error: anyhow::Error); + +#[cfg(verus_keep_ghost)] +pub assume_specification[ + anyhow::__private::must_use +](error: anyhow::Error) -> (result: anyhow::Error); + #[cfg(verus_keep_ghost)] #[verifier::external_body] pub fn verus_bail_helper() -> Result From e7cc8a1769d04df3a07a7f2b9a49e3ba7f0dae28 Mon Sep 17 00:00:00 2001 From: Jay Lorch Date: Fri, 24 Jul 2026 19:36:17 -0700 Subject: [PATCH 12/41] Verify Number::to_big --- src/number.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/number.rs b/src/number.rs index 97627fde6..e23a57b44 100644 --- a/src/number.rs +++ b/src/number.rs @@ -728,8 +728,6 @@ impl Number { self.to_bigint_rc() } - // Verus does not support the formatting internals used by `anyhow!`. - #[verus_verify(external_body)] #[verus_spec(result => ensures match self@ { From eccbb5418e0ec3088459c447169f6916606e7de1 Mon Sep 17 00:00:00 2001 From: Jay Lorch Date: Sat, 25 Jul 2026 08:17:20 -0700 Subject: [PATCH 13/41] Restore use of bail! macro --- src/number.rs | 8 ++++---- src/verify/utils.rs | 28 ---------------------------- 2 files changed, 4 insertions(+), 32 deletions(-) diff --git a/src/number.rs b/src/number.rs index e23a57b44..7f98dc2c5 100644 --- a/src/number.rs +++ b/src/number.rs @@ -18,7 +18,7 @@ use core::cmp::Ordering; use core::fmt::{Debug, Formatter}; use core::str::FromStr; -use anyhow::{anyhow, Result}; +use anyhow::{anyhow, bail, Result}; use num_bigint::BigInt as NumBigInt; #[allow(unused)] use num_traits::float::FloatCore; @@ -962,7 +962,7 @@ impl Number { } if rhs.is_zero() { - return Err(anyhow!("division by zero")); + bail!("division by zero"); } if matches!(self, Number::Float(_)) || matches!(rhs, Number::Float(_)) { @@ -1090,11 +1090,11 @@ impl Number { // one whose magnitude exceeds 2^53, which cannot be represented exactly. let (a, b) = match (self.to_bigint_owned(), rhs.to_bigint_owned()) { (Some(a), Some(b)) => (a, b), - _ => return Err(anyhow!("modulo on floating-point number")), + _ => bail!("modulo on floating-point number"), }; if b.is_zero() { - return Err(anyhow!("modulo by zero")); + bail!("modulo by zero"); } >>>>>>> efd56bf (Fix bug in Number::modulo) diff --git a/src/verify/utils.rs b/src/verify/utils.rs index 9a73c35be..a19a25bb3 100644 --- a/src/verify/utils.rs +++ b/src/verify/utils.rs @@ -1,4 +1,3 @@ -use anyhow::{bail, Result}; use std::format; use std::string::String; @@ -63,31 +62,4 @@ pub assume_specification[ anyhow::__private::must_use ](error: anyhow::Error) -> (result: anyhow::Error); -#[cfg(verus_keep_ghost)] -#[verifier::external_body] -pub fn verus_bail_helper() -> Result -{ - bail!("who cares") -} - -macro_rules! verus_bail { - ( $( $tt0:tt )* ) => { - { - #[cfg(not(verus_keep_ghost))] - { bail!($($tt0)*) } - #[cfg(verus_keep_ghost)] - { return verus_bail_helper(); } - } - } -} - -#[allow(dead_code)] -fn my_test_verus_bail(fcn: &'static str, x: u32) -> Result<()> -{ - if x > 0 { - verus_bail!("Invalid parameters `{}` and `{}`", fcn, x) - } - Ok(()) -} - } // end verus! From f6514e588d5fd65cd863bdba5e9c3a35866dc70e Mon Sep 17 00:00:00 2001 From: Jay Lorch Date: Sat, 25 Jul 2026 08:44:26 -0700 Subject: [PATCH 14/41] Clean up cfg dependencies --- src/verify/bigint_assumptions.rs | 10 +--------- src/verify/bigint_proofs.rs | 2 -- src/verify/f64_assumptions.rs | 16 ++-------------- src/verify/f64_tests.rs | 14 ++++++++++++++ src/verify/mod.rs | 4 ++-- src/verify/number_proofs.rs | 2 -- src/verify/number_specs.rs | 2 -- src/verify/utils.rs | 7 ------- 8 files changed, 19 insertions(+), 38 deletions(-) create mode 100644 src/verify/f64_tests.rs diff --git a/src/verify/bigint_assumptions.rs b/src/verify/bigint_assumptions.rs index 2b5df404a..230af8393 100644 --- a/src/verify/bigint_assumptions.rs +++ b/src/verify/bigint_assumptions.rs @@ -15,10 +15,8 @@ clippy::pattern_type_mismatch )] -#[cfg(feature = "verus")] use vstd::prelude::*; -#[cfg(feature = "verus")] verus! { use core::cmp::Ordering; @@ -592,14 +590,8 @@ pub assume_specification<'a>[ >::div ](x: BigInt, *y != 0 ==> o@ == rust_div(x@, *y as int), ; -} // end verus! - // Verus's encoding of ToPrimitive relies on an unstable feature // `sized_hierarchy`, so we can only talk about it when verifying. -// So, we wrap it all in `#[cfg(verus_keep_ghost)]`. - -#[cfg(verus_keep_ghost)] -verus! { // ToPrimitive @@ -818,4 +810,4 @@ pub assume_specification[ ::to_u1 pub assume_specification[ ::to_f32 ](x: &BigInt) -> (res: Option); pub assume_specification[ ::to_f64 ](x: &BigInt) -> (res: Option); -} // end verus! hidden by cfg(verus_keep_ghost) +} // end verus! diff --git a/src/verify/bigint_proofs.rs b/src/verify/bigint_proofs.rs index 78d322e5b..ae24bd705 100644 --- a/src/verify/bigint_proofs.rs +++ b/src/verify/bigint_proofs.rs @@ -1,10 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -#[cfg(feature = "verus")] use vstd::prelude::*; -#[cfg(feature = "verus")] verus! { use super::bigint_assumptions::bigint_bits_ensures; diff --git a/src/verify/f64_assumptions.rs b/src/verify/f64_assumptions.rs index 0f62a1fe4..395c9407a 100644 --- a/src/verify/f64_assumptions.rs +++ b/src/verify/f64_assumptions.rs @@ -14,10 +14,8 @@ clippy::pattern_type_mismatch )] -#[cfg(verus_keep_ghost)] use vstd::prelude::*; -#[cfg(verus_keep_ghost)] verus! { use vstd::float::*; @@ -81,7 +79,8 @@ pub axiom fn axiom_f64_ops_deterministic() forall|n: u128, f: f64| float_cast_spec::(f, n) ==> n == ieee_float_cast::(f), ; -// The executable test below validates these casts and justifies this axiom. +// The executable test in `f64_tests.rs` validates these casts and justifies +// this axiom. pub axiom fn axiom_f64_safe_integer_casts() ensures ieee_float_cast::(9_007_199_254_740_992.0f64) == 9_007_199_254_740_992u64, @@ -162,14 +161,3 @@ pub assume_specification[ f64::INFINITY ] -> (res: f64) ; } // end verus! - -#[cfg(test)] -mod tests { - #[test] - fn f64_safe_integer_casts_match_runtime() { - let safe_integer = 9_007_199_254_740_992.0f64; - - assert_eq!(safe_integer as u64, 9_007_199_254_740_992u64); - assert_eq!(safe_integer as i128, 9_007_199_254_740_992i128); - } -} diff --git a/src/verify/f64_tests.rs b/src/verify/f64_tests.rs new file mode 100644 index 000000000..c5920fa28 --- /dev/null +++ b/src/verify/f64_tests.rs @@ -0,0 +1,14 @@ +// This file contains executable tests that justify the assumptions about `f64` +// declared in `f64_assumptions.rs`. +// +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#![allow(clippy::as_conversions, clippy::unseparated_literal_suffix)] + +#[test] +fn f64_safe_integer_casts_match_runtime() { + let safe_integer = 9_007_199_254_740_992.0f64; + + assert_eq!(safe_integer as u64, 9_007_199_254_740_992u64); + assert_eq!(safe_integer as i128, 9_007_199_254_740_992i128); +} diff --git a/src/verify/mod.rs b/src/verify/mod.rs index 90e46746f..409bdef73 100644 --- a/src/verify/mod.rs +++ b/src/verify/mod.rs @@ -4,8 +4,8 @@ pub(crate) mod bigint_assumptions; pub(crate) mod bigint_proofs; #[cfg(verus_keep_ghost)] pub(crate) mod f64_assumptions; -#[cfg(all(test, not(verus_keep_ghost)))] -mod f64_assumptions; +#[cfg(test)] +mod f64_tests; #[cfg(verus_keep_ghost)] pub(crate) mod number_proofs; #[cfg(verus_keep_ghost)] diff --git a/src/verify/number_proofs.rs b/src/verify/number_proofs.rs index 990b99854..5197173cc 100644 --- a/src/verify/number_proofs.rs +++ b/src/verify/number_proofs.rs @@ -1,10 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -#[cfg(feature = "verus")] use vstd::prelude::*; -#[cfg(feature = "verus")] verus! { use super::number_specs::NumberView; diff --git a/src/verify/number_specs.rs b/src/verify/number_specs.rs index f2b78c44c..ccba54576 100644 --- a/src/verify/number_specs.rs +++ b/src/verify/number_specs.rs @@ -14,10 +14,8 @@ clippy::pattern_type_mismatch )] -#[cfg(feature = "verus")] use vstd::prelude::*; -#[cfg(feature = "verus")] verus! { use core::cmp::Ordering; diff --git a/src/verify/utils.rs b/src/verify/utils.rs index a19a25bb3..0a56da2c2 100644 --- a/src/verify/utils.rs +++ b/src/verify/utils.rs @@ -5,7 +5,6 @@ use vstd::prelude::*; verus! { -#[cfg(verus_keep_ghost)] #[verifier::external_body] pub fn verus_format_helper() -> String { @@ -29,12 +28,10 @@ fn my_test_verus_format(fcn: &'static str, x: u32) -> String verus_format!("The parameters are `{fcn}` and `{x}`") } -#[cfg(verus_keep_ghost)] #[verifier::external_type_specification] #[verifier::external_body] pub struct ExAnyhowError(anyhow::Error); -#[cfg(verus_keep_ghost)] pub assume_specification[ anyhow::Error::msg:: ](message: M) -> (error: anyhow::Error); @@ -42,22 +39,18 @@ pub assume_specification(core::fmt::Arguments<'a>); -#[cfg(verus_keep_ghost)] pub assume_specification<'a>[ core::fmt::Arguments::<'a>::from_str ](message: &'static str) -> (args: core::fmt::Arguments<'a>); -#[cfg(verus_keep_ghost)] pub assume_specification<'a>[ anyhow::__private::format_err ](args: core::fmt::Arguments<'a>) -> (error: anyhow::Error); -#[cfg(verus_keep_ghost)] pub assume_specification[ anyhow::__private::must_use ](error: anyhow::Error) -> (result: anyhow::Error); From c481d613a6505f8b96ab5ffd7d05fb66a5c7951f Mon Sep 17 00:00:00 2001 From: Jay Lorch Date: Sat, 25 Jul 2026 12:15:20 -0700 Subject: [PATCH 15/41] Prove ten_pow --- src/number.rs | 80 ++++++++++++++------------------ src/verify/bigint_assumptions.rs | 7 +++ src/verify/mod.rs | 2 + src/verify/num_assumptions.rs | 37 +++++++++++++++ src/verify/number_specs.rs | 16 ------- src/verify/utils.rs | 4 ++ 6 files changed, 85 insertions(+), 61 deletions(-) create mode 100644 src/verify/num_assumptions.rs diff --git a/src/number.rs b/src/number.rs index 7f98dc2c5..a8810af0f 100644 --- a/src/number.rs +++ b/src/number.rs @@ -1129,6 +1129,7 @@ impl Number { Number::Float(f) => f.is_sign_positive(), } } + #[verus_spec(result => ensures match (a@.to_int(), b@.to_int(), result) { @@ -1389,9 +1390,22 @@ impl Number { #[verus_spec(result => ensures - result is Ok, - e >= 0 ==> (result matches Ok(value) && value@ is Integer), - e < 0 ==> (result matches Ok(value) && value@ is Float), + match result { + Ok(value) => if e >= 0 { + value@ == NumberView::Integer(vstd::arithmetic::power::pow(10, e as nat)) + } else { + exists|denominator: Number| { + &&& #[trigger] denominator@ == NumberView::Integer( + vstd::arithmetic::power::pow(10, (-(e as int)) as nat) + ) + &&& value@ == NumberView::Float( + ieee_float_cast::(1u64) + / denominator.spec_to_f64_lossy() + ) + } + }, + Err(_) => false, + }, )] pub fn ten_pow(e: i32) -> Result { proof! { @@ -1399,6 +1413,16 @@ impl Number { if e < 0 { let exp = (-(e as i64)) as u32; assert(exp > 0); + vstd::arithmetic::power::lemma_pow0(10); + vstd::arithmetic::power::lemma_pow_strictly_increases(10, 0, exp as nat); + assert(1 < vstd::arithmetic::power::pow(10, exp as nat)); + vstd::arithmetic::div_mod::lemma_small_mod( + 1, + vstd::arithmetic::power::pow(10, exp as nat) as nat, + ); + } else { + assert((e as u32) as nat == e as nat); + vstd::arithmetic::power::lemma_pow_positive(10, (e as u32) as nat); } } if e >= 0 { @@ -1410,33 +1434,21 @@ impl Number { } } - #[verus_verify(external_body)] - #[verus_spec(result => - ensures - (result@.len() == 0) == self@.to_int() is None, - )] pub fn format_bin(&self) -> String { self.ensure_integer() .map(|v| v.to_str_radix(2)) .unwrap_or_default() } - #[verus_verify(external_body)] - #[verus_spec(result => - ensures - (result@.len() == 0) == self@.to_int() is None, - )] pub fn format_octal(&self) -> String { self.ensure_integer() .map(|v| v.to_str_radix(8)) .unwrap_or_default() } + // Verus doesn't support format! #[verus_verify(external_body)] - #[verus_spec(result => - ensures - result@.len() > 0, - )] + #[verus_spec(ensures true)] pub fn format_scientific(&self) -> String { match self { Number::Float(f) => format!("{:e}", f), @@ -1447,11 +1459,6 @@ impl Number { } } - #[verus_verify(external_body)] - #[verus_spec(result => - ensures - result@.len() > 0, - )] pub fn format_decimal(&self) -> String { match self { Number::UInt(v) => v.to_string(), @@ -1467,11 +1474,9 @@ impl Number { } } + // Verus doesn't support format! #[verus_verify(external_body)] - #[verus_spec(result => - ensures - result@.len() > 0, - )] + #[verus_spec(ensures true)] pub fn format_decimal_with_width(&self, d: u32) -> String { match self { Number::Float(f) => { @@ -1483,22 +1488,12 @@ impl Number { } } - #[verus_verify(external_body)] - #[verus_spec(result => - ensures - (result@.len() == 0) == self@.to_int() is None, - )] pub fn format_hex(&self) -> String { self.ensure_integer() .map(|v| v.to_str_radix(16)) .unwrap_or_default() } - #[verus_verify(external_body)] - #[verus_spec(result => - ensures - (result@.len() == 0) == self@.to_int() is None, - )] pub fn format_big_hex(&self) -> String { self.ensure_integer() .map(|v| v.to_str_radix(16).to_ascii_uppercase()) @@ -1522,10 +1517,11 @@ fn two_pow_positive(exp: u32) -> Number { } } +// Verus does not yet support overloaded op-assignment operators such as `*=`. #[verus_verify(external_body)] #[verus_spec(result => ensures - result@ > 0, + result@ == vstd::arithmetic::power::pow(10, exp as nat), )] fn pow10_bigint(exp: u32) -> BigInt { if exp == 0 { @@ -1549,12 +1545,9 @@ fn pow10_bigint(exp: u32) -> BigInt { result } -#[verus_verify(external_body)] #[verus_spec(result => ensures - result@ matches NumberView::Integer(value) - && value > 0 - && (exp > 0 ==> vstd::arithmetic::div_mod::rust_rem(1, value) == 1), + result@ == NumberView::Integer(vstd::arithmetic::power::pow(10, exp as nat)), )] fn ten_pow_positive(exp: u32) -> Number { if let Some(value) = 10u64.checked_pow(exp) { @@ -1564,11 +1557,8 @@ fn ten_pow_positive(exp: u32) -> Number { } } +// Verus does not support format! #[verus_verify(external_body)] -#[verus_spec(result => - ensures - result@.len() > 0, -)] fn bigint_to_scientific(value: &BigInt) -> String { let s = value.to_string(); let (sign, digits) = if let Some(rest) = s.strip_prefix('-') { diff --git a/src/verify/bigint_assumptions.rs b/src/verify/bigint_assumptions.rs index 230af8393..950d2fb22 100644 --- a/src/verify/bigint_assumptions.rs +++ b/src/verify/bigint_assumptions.rs @@ -122,6 +122,13 @@ pub assume_specification[ ::abs ](x: &BigInt) -> ( res@ == if x@ < 0 { -x@ } else { x@ }, ; +// Formatting + +// Nothing is promised about the rendered digits; callers only need this to be +// callable from verified code. +pub assume_specification[ BigInt::to_str_radix ](x: &BigInt, radix: u32) -> (res: + alloc::string::String); + // PartialEq pub axiom fn axiom_bigint_obeys_eq_spec() diff --git a/src/verify/mod.rs b/src/verify/mod.rs index 409bdef73..5a26bccea 100644 --- a/src/verify/mod.rs +++ b/src/verify/mod.rs @@ -7,6 +7,8 @@ pub(crate) mod f64_assumptions; #[cfg(test)] mod f64_tests; #[cfg(verus_keep_ghost)] +pub(crate) mod num_assumptions; +#[cfg(verus_keep_ghost)] pub(crate) mod number_proofs; #[cfg(verus_keep_ghost)] pub mod number_specs; diff --git a/src/verify/num_assumptions.rs b/src/verify/num_assumptions.rs new file mode 100644 index 000000000..a4ce7cca6 --- /dev/null +++ b/src/verify/num_assumptions.rs @@ -0,0 +1,37 @@ +// This file contains assumptions about Rust's primitive numeric types, +// encoded in Verus. +// +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use vstd::prelude::*; + +verus! { + +use vstd::arithmetic::power::pow; + +pub assume_specification[ i128::abs ](value: i128) -> (res: i128) + requires + value != i128::MIN, + ensures + res as int == if value < 0 { -(value as int) } else { value as int }, +; + +pub assume_specification[ i64::checked_abs ](value: i64) -> (res: Option) + ensures + if value == i64::MIN { + res is None + } else { + res matches Some(abs) && abs as int == if value < 0 { -(value as int) } else { value as int } + }, +; + +pub assume_specification[ u64::checked_pow ](x: u64, exp: u32) -> (res: Option) + ensures + match res { + Some(value) => value == pow(x as int, exp as nat), + None => pow(x as int, exp as nat) > u64::MAX, + }, +; + +} // end verus! diff --git a/src/verify/number_specs.rs b/src/verify/number_specs.rs index ccba54576..462b92dc5 100644 --- a/src/verify/number_specs.rs +++ b/src/verify/number_specs.rs @@ -31,22 +31,6 @@ pub assume_specification[ ::clone ](n: &Number) -> (res: Number res == n, ; -pub assume_specification[ i128::abs ](value: i128) -> (res: i128) - requires - value != i128::MIN, - ensures - res as int == if value < 0 { -(value as int) } else { value as int }, -; - -pub assume_specification[ i64::checked_abs ](value: i64) -> (res: Option) - ensures - if value == i64::MIN { - res is None - } else { - res matches Some(abs) && abs as int == if value < 0 { -(value as int) } else { value as int } - }, -; - pub enum NumberView { Integer(int), Float(f64), diff --git a/src/verify/utils.rs b/src/verify/utils.rs index 0a56da2c2..98a2741d2 100644 --- a/src/verify/utils.rs +++ b/src/verify/utils.rs @@ -55,4 +55,8 @@ pub assume_specification[ anyhow::__private::must_use ](error: anyhow::Error) -> (result: anyhow::Error); +// vstd doesn't specify `to_ascii_uppercase`, and callers don't rely on its +// result, so this promises nothing. +pub assume_specification[ ::to_ascii_uppercase ](s: &str) -> (res: String); + } // end verus! From cd41457cf3f8e71d7706da7bf47f266276ebf4cd Mon Sep 17 00:00:00 2001 From: Jay Lorch Date: Sat, 25 Jul 2026 12:34:05 -0700 Subject: [PATCH 16/41] Avoid exposing Number internals with to_f64_lossy_ensures --- src/number.rs | 41 ++++++++------------------ src/verify/number_proofs.rs | 37 +++++++----------------- src/verify/number_specs.rs | 57 ++++++++++--------------------------- 3 files changed, 38 insertions(+), 97 deletions(-) diff --git a/src/number.rs b/src/number.rs index a8810af0f..9d909c483 100644 --- a/src/number.rs +++ b/src/number.rs @@ -191,7 +191,6 @@ impl Number { #[verus_spec(result => ensures self@.to_f64_lossy_ensures(result), - result == self.spec_to_f64_lossy(), )] fn to_f64_lossy(&self) -> f64 { proof! { axiom_f64_ops_deterministic(); } @@ -290,7 +289,7 @@ impl From for Number { #[verus_spec(result => ensures result@ == NumberView::Integer(value as int), - result.spec_to_f64_lossy() == ieee_float_cast::(value), + result@.to_f64_lossy_ensures(ieee_float_cast::(value)), )] fn from(value: u64) -> Self { Number::UInt(value) @@ -939,12 +938,7 @@ impl Number { #[verus_spec(result => ensures match result { - Ok(value) => self@.div_ensures( - rhs@, - self.spec_to_f64_lossy(), - rhs.spec_to_f64_lossy(), - value@, - ), + Ok(value) => self@.div_ensures(rhs@, value@), Err(_) => rhs@.is_zero(), }, )] @@ -952,12 +946,7 @@ impl Number { proof! { axiom_f64_ops_deterministic(); axiom_bigint_obeys_div_rem_spec(); - lemma_div_ensures_cases( - self@, - rhs@, - self.spec_to_f64_lossy(), - rhs.spec_to_f64_lossy(), - ); + lemma_div_ensures_cases(self@, rhs@); lemma_number_primitive_division_facts(&self, rhs); } @@ -1353,13 +1342,10 @@ impl Number { Ok(value) => if e >= 0 { value@ == NumberView::Integer(pow2(e as nat) as int) } else { - exists|denominator: Number| { - &&& #[trigger] denominator@ == NumberView::Integer(pow2((-(e as int)) as nat) as int) - &&& value@ == NumberView::Float( - ieee_float_cast::(1u64) - / denominator.spec_to_f64_lossy() - ) - } + NumberView::Integer(1).div_ensures( + NumberView::Integer(pow2((-(e as int)) as nat) as int), + value@, + ) }, Err(_) => false, }, @@ -1394,15 +1380,12 @@ impl Number { Ok(value) => if e >= 0 { value@ == NumberView::Integer(vstd::arithmetic::power::pow(10, e as nat)) } else { - exists|denominator: Number| { - &&& #[trigger] denominator@ == NumberView::Integer( + NumberView::Integer(1).div_ensures( + NumberView::Integer( vstd::arithmetic::power::pow(10, (-(e as int)) as nat) - ) - &&& value@ == NumberView::Float( - ieee_float_cast::(1u64) - / denominator.spec_to_f64_lossy() - ) - } + ), + value@, + ) }, Err(_) => false, }, diff --git a/src/verify/number_proofs.rs b/src/verify/number_proofs.rs index 5197173cc..0ecab3516 100644 --- a/src/verify/number_proofs.rs +++ b/src/verify/number_proofs.rs @@ -21,12 +21,7 @@ use vstd::arithmetic::mul::{ }; use vstd::std_specs::ops::*; -pub proof fn lemma_div_ensures_cases( - lhs: NumberView, - rhs: NumberView, - lhs_float: f64, - rhs_float: f64, -) +pub proof fn lemma_div_ensures_cases(lhs: NumberView, rhs: NumberView) ensures forall|integer_lhs: int, divisor: int| lhs == NumberView::Integer(integer_lhs) @@ -35,28 +30,18 @@ pub proof fn lemma_div_ensures_cases( && rust_rem(integer_lhs, divisor) == 0 ==> #[trigger] lhs.div_ensures( rhs, - lhs_float, - rhs_float, NumberView::Integer(rust_div(integer_lhs, divisor)), ), - forall|integer_lhs: int, divisor: int| - lhs == NumberView::Integer(integer_lhs) - && rhs == NumberView::Integer(divisor) - && divisor != 0 - && rust_rem(integer_lhs, divisor) != 0 - ==> #[trigger] lhs.div_ensures( - rhs, - lhs_float, - rhs_float, - NumberView::Float(lhs_float / rhs_float), - ), - (lhs is Float || rhs is Float) && !rhs.is_zero() - ==> lhs.div_ensures( - rhs, - lhs_float, - rhs_float, - NumberView::Float(lhs_float / rhs_float), - ), + forall|lhs_float: f64, rhs_float: f64| + lhs is Integer && rhs is Integer && rhs->Integer_0 != 0 && rust_rem( + lhs->Integer_0, + rhs->Integer_0, + ) != 0 && lhs.to_f64_lossy_ensures(lhs_float) && rhs.to_f64_lossy_ensures(rhs_float) + ==> #[trigger] lhs.div_ensures(rhs, NumberView::Float(lhs_float / rhs_float)), + forall|lhs_float: f64, rhs_float: f64| + (lhs is Float || rhs is Float) && !rhs.is_zero() && lhs.to_f64_lossy_ensures(lhs_float) + && rhs.to_f64_lossy_ensures(rhs_float) + ==> #[trigger] lhs.div_ensures(rhs, NumberView::Float(lhs_float / rhs_float)), { reveal(NumberView::is_zero); reveal(NumberView::div_ensures); diff --git a/src/verify/number_specs.rs b/src/verify/number_specs.rs index 462b92dc5..10a329a6b 100644 --- a/src/verify/number_specs.rs +++ b/src/verify/number_specs.rs @@ -187,13 +187,7 @@ impl NumberView { } } - pub open spec fn div_ensures( - self: Self, - rhs: Self, - lhs_float: f64, - rhs_float: f64, - result: Self, - ) -> bool + pub open spec fn div_ensures(self: Self, rhs: Self, result: Self) -> bool { match (self, rhs) { (NumberView::Integer(lhs), NumberView::Integer(divisor)) => { @@ -203,12 +197,20 @@ impl NumberView { vstd::arithmetic::div_mod::rust_div(lhs, divisor), ) } else { - result == NumberView::Float(lhs_float / rhs_float) + exists|lhs_float: f64, rhs_float: f64| { + &&& self.to_f64_lossy_ensures(lhs_float) + &&& rhs.to_f64_lossy_ensures(rhs_float) + &&& result == NumberView::Float(lhs_float / rhs_float) + } } }, (NumberView::Float(_), _) | (NumberView::Integer(_), NumberView::Float(_)) => { &&& !rhs.is_zero() - &&& result == NumberView::Float(lhs_float / rhs_float) + &&& exists|lhs_float: f64, rhs_float: f64| { + &&& self.to_f64_lossy_ensures(lhs_float) + &&& rhs.to_f64_lossy_ensures(rhs_float) + &&& result == NumberView::Float(lhs_float / rhs_float) + } }, } } @@ -290,44 +292,15 @@ impl PartialEqSpecImpl for Number { } } -impl Number { - pub open spec fn spec_to_f64_lossy(&self) -> f64 - { - match *self { - Number::UInt(v) => ieee_float_cast::(v), - Number::Int(v) => ieee_float_cast::(v), - Number::Float(v) => v, - Number::BigInt(v) => { - if let Some(f) = ::spec_to_f64(&v) { - f - } else if v@ < 0 { - spec_f64_neg_infinity() - } else { - spec_f64_infinity() - } - }, - } - } - -} - impl OrdSpecImpl for Number { + // `Number::cmp` is specified directly in terms of `NumberView`, so there's + // no need for a `cmp_spec` that would expose the internal representation. open spec fn obeys_cmp_spec() -> bool { - true + false } - open spec fn cmp_spec(&self, other: &Self) -> Ordering - { - match (self@.to_int(), other@.to_int()) { - (Some(n1), Some(n2)) => n1.cmp_spec(&n2), - _ => { - let f1 = self.spec_to_f64_lossy(); - let f2 = other.spec_to_f64_lossy(); - f1.partial_cmp_spec(&f2).unwrap_or(Ordering::Equal) - }, - } - } + uninterp spec fn cmp_spec(&self, other: &Self) -> Ordering; } } // end verus! From fc925e5d804751f9dcb03147673f12254b0f15b0 Mon Sep 17 00:00:00 2001 From: Jay Lorch Date: Sat, 25 Jul 2026 13:21:05 -0700 Subject: [PATCH 17/41] Remove unnecessary NumberSpec::is_integer --- src/number.rs | 6 ++++-- src/verify/number_specs.rs | 8 -------- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/src/number.rs b/src/number.rs index 9d909c483..3bbc24a27 100644 --- a/src/number.rs +++ b/src/number.rs @@ -289,7 +289,6 @@ impl From for Number { #[verus_spec(result => ensures result@ == NumberView::Integer(value as int), - result@.to_f64_lossy_ensures(ieee_float_cast::(value)), )] fn from(value: u64) -> Self { Number::UInt(value) @@ -1093,7 +1092,10 @@ impl Number { #[verus_spec(result => ensures - result == self@.is_integer(), + result == match self@ { + NumberView::Integer(_) => true, + NumberView::Float(f) => f.is_finite_spec() && spec_f64_fract(f).eq_spec(&0.0f64), + }, )] pub fn is_integer(&self) -> bool { proof! { axiom_f64_obeys_eq_spec(); } diff --git a/src/verify/number_specs.rs b/src/verify/number_specs.rs index 10a329a6b..405c774f9 100644 --- a/src/verify/number_specs.rs +++ b/src/verify/number_specs.rs @@ -93,14 +93,6 @@ impl NumberView { } } - pub open spec fn is_integer(&self) -> bool - { - match *self { - Self::Integer(_) => true, - Self::Float(f) => f.is_finite_spec() && spec_f64_fract(f).eq_spec(&0.0f64), - } - } - pub open spec fn is_zero(&self) -> bool { match *self { From 88b160086c5505b00e191725671ce63d35c20173 Mon Sep 17 00:00:00 2001 From: Jay Lorch Date: Sat, 25 Jul 2026 13:39:03 -0700 Subject: [PATCH 18/41] Remove unnecessary integer_value spec fn --- src/number.rs | 15 ++++++--------- src/verify/number_specs.rs | 23 +++++++++-------------- 2 files changed, 15 insertions(+), 23 deletions(-) diff --git a/src/number.rs b/src/number.rs index 3bbc24a27..8c7bd621e 100644 --- a/src/number.rs +++ b/src/number.rs @@ -877,19 +877,16 @@ impl Number { proof! { axiom_f64_ops_deterministic(); axiom_bigint_obeys_mul_spec(); - reveal(NumberView::integer_value); - reveal(NumberView::mul_ensures); - assert(forall|lhs: int, rhs_value: int| - self@.integer_value() == Some(lhs) - && rhs@.integer_value() == Some(rhs_value) - ==> self@.mul_ensures( - rhs@, - NumberView::Integer(lhs * rhs_value), - )); if let (Number::UInt(lhs), Number::UInt(rhs_value)) = (self, rhs) { assert((*lhs as int) * (*rhs_value as int) <= u128::MAX as int) by(nonlinear_arith); } + // Some cases are handled by an or-pattern that computes the product + // with the operands swapped. + if self@ is Integer && rhs@ is Integer { + assert(self@->Integer_0 * rhs@->Integer_0 == rhs@->Integer_0 + * self@->Integer_0) by(nonlinear_arith); + } } match (self, rhs) { diff --git a/src/verify/number_specs.rs b/src/verify/number_specs.rs index 405c774f9..6c6547948 100644 --- a/src/verify/number_specs.rs +++ b/src/verify/number_specs.rs @@ -85,14 +85,6 @@ pub open spec fn normalize_float(value: f64) -> NumberView } impl NumberView { - pub open spec fn integer_value(&self) -> Option - { - match *self { - Self::Integer(n) => Some(n), - Self::Float(_) => None, - } - } - pub open spec fn is_zero(&self) -> bool { match *self { @@ -130,8 +122,9 @@ impl NumberView { pub open spec fn add_ensures(self: Self, rhs: Self, result: Self) -> bool { - match (self.integer_value(), rhs.integer_value()) { - (Some(lhs), Some(rhs)) => result matches NumberView::Integer(sum) && sum == lhs + rhs, + match (self, rhs) { + (NumberView::Integer(lhs), NumberView::Integer(rhs)) => + result matches NumberView::Integer(sum) && sum == lhs + rhs, _ => exists|lhs_float: f64, rhs_float: f64| { &&& self.to_f64_lossy_ensures(lhs_float) &&& rhs.to_f64_lossy_ensures(rhs_float) @@ -147,8 +140,9 @@ impl NumberView { pub open spec fn sub_ensures(self: Self, rhs: Self, result: Self) -> bool { - match (self.integer_value(), rhs.integer_value()) { - (Some(lhs), Some(rhs)) => result matches NumberView::Integer(diff) && diff == lhs - rhs, + match (self, rhs) { + (NumberView::Integer(lhs), NumberView::Integer(rhs)) => + result matches NumberView::Integer(diff) && diff == lhs - rhs, _ => exists|lhs_float: f64, rhs_float: f64| { &&& self.to_f64_lossy_ensures(lhs_float) &&& rhs.to_f64_lossy_ensures(rhs_float) @@ -164,8 +158,9 @@ impl NumberView { pub open spec fn mul_ensures(self: Self, rhs: Self, result: Self) -> bool { - match (self.integer_value(), rhs.integer_value()) { - (Some(lhs), Some(rhs)) => result matches NumberView::Integer(product) && product == lhs * rhs, + match (self, rhs) { + (NumberView::Integer(lhs), NumberView::Integer(rhs)) => + result matches NumberView::Integer(product) && product == lhs * rhs, _ => exists|lhs_float: f64, rhs_float: f64| { &&& self.to_f64_lossy_ensures(lhs_float) &&& rhs.to_f64_lossy_ensures(rhs_float) From 42a222fd141522e80f26cc8b459df69a41e70755 Mon Sep 17 00:00:00 2001 From: Jay Lorch Date: Sun, 26 Jul 2026 20:11:52 -0700 Subject: [PATCH 19/41] Upgrade to latest Verus --- .github/workflows/verus.yml | 4 ++-- Cargo.lock | 31 +++++++++++++++++++------------ Cargo.toml | 2 +- bindings/ffi/Cargo.lock | 31 +++++++++++++++++++------------ bindings/java/Cargo.lock | 31 +++++++++++++++++++------------ bindings/python/Cargo.lock | 31 +++++++++++++++++++------------ bindings/wasm/Cargo.lock | 31 +++++++++++++++++++------------ 7 files changed, 98 insertions(+), 63 deletions(-) diff --git a/.github/workflows/verus.yml b/.github/workflows/verus.yml index 66c2e4da4..79ede417b 100644 --- a/.github/workflows/verus.yml +++ b/.github/workflows/verus.yml @@ -35,8 +35,8 @@ jobs: shell: bash run: | set -euxo pipefail - asset_url=https://github.com/verus-lang/verus/releases/download/release%2F0.2026.07.12.0b42f4c/verus-0.2026.07.12.0b42f4c-x86-linux.zip - asset_sha256=f6f4f5d08e07d3e1ad721d775bda5ba96b9dd0c73b48fc17f2e071866fbd01c0 + asset_url=https://github.com/verus-lang/verus/releases/download/release%2F0.2026.07.27.31579f0/verus-0.2026.07.27.31579f0-x86-linux.zip + asset_sha256=7a6143e6dcd2db778314ac102c5ceeac1f7f152b028e3428798bff7170212498 test -n "$asset_url" curl -fsSL "$asset_url" -o verus.zip diff --git a/Cargo.lock b/Cargo.lock index 811088b8f..c7ec71586 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -310,6 +310,12 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -1840,16 +1846,17 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "verus_builtin" -version = "0.0.0-2026-05-17-0151" +version = "0.0.0-2026-07-27-0206" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cab9266bfb5cf45a080f425c560b0e0be2bf81194f0e80258990c49c1829d8b9" +checksum = "e70a747c69a08b336573d67a8702993572317ab0f506084f717e6cefd3835f56" [[package]] name = "verus_builtin_macros" -version = "0.0.0-2026-07-12-0122" +version = "0.0.0-2026-07-27-0206" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fb60e3a141ababe618fbdb759f14aa8544f6346a98a44c1e3a7dbe20b1f2892" +checksum = "a3b07917203e0f36da3d29529f511f1ec27a1825529846433b4bc7384d400444" dependencies = [ + "convert_case", "proc-macro2 1.0.107", "quote 1.0.47", "syn 2.0.119", @@ -1860,9 +1867,9 @@ dependencies = [ [[package]] name = "verus_prettyplease" -version = "0.0.0-2026-05-31-0205" +version = "0.0.0-2026-07-27-0206" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ffdc94c89109a67c59646f6b6e71b49de394020f45294247dd716470523245b" +checksum = "028a6b1bedf498a425f5fa733c4a9aada900734c5ac82e4801597ff3ff70fa62" dependencies = [ "proc-macro2 1.0.107", "verus_syn", @@ -1870,9 +1877,9 @@ dependencies = [ [[package]] name = "verus_state_machines_macros" -version = "0.0.0-2026-06-14-0213" +version = "0.0.0-2026-07-27-0206" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6704a9ce586a5027f87933d9a84817411c937804090a9585c28bf335ac37eefc" +checksum = "911e2a65229749eeb48a1f27d2b47a46b4fb43673f918854b065a932c86305b9" dependencies = [ "indexmap 1.9.3", "proc-macro2 1.0.107", @@ -1882,9 +1889,9 @@ dependencies = [ [[package]] name = "verus_syn" -version = "0.0.0-2026-05-31-0205" +version = "0.0.0-2026-07-27-0206" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "030b774330d5ec618691c2cd6fba8bbb13bd538f3884a1143450baec25749706" +checksum = "fdcccb102ae806c65ae59653757ac28ecff328f996159757d17b5c735a7f7850" dependencies = [ "proc-macro2 1.0.107", "quote 1.0.47", @@ -1899,9 +1906,9 @@ checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" [[package]] name = "vstd" -version = "0.0.0-2026-07-12-0122" +version = "0.0.0-2026-07-27-0206" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd0a0d328e1d6382af99a65f71677cf4b9200ec24e09fcafa7b1a3af8c8f5111" +checksum = "3c72419942f28be7f54bca5e8bc1ac207f9efd45e424788905ecb1213881950a" dependencies = [ "verus_builtin", "verus_builtin_macros", diff --git a/Cargo.toml b/Cargo.toml index 91ab00aab..631ebe04e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -144,7 +144,7 @@ postcard = { version = "1.1.3", default-features = false, features = ["alloc"], # Verus-related dependencies. # vstd is enabled via the `verus` feature. In no_std builds only the `alloc` feature is used; # the crate's `std` feature additionally enables `vstd/std` (matching vstd's default features). -vstd = { version = "=0.0.0-2026-07-12-0122", optional = true, default-features = false, features = ["alloc"] } +vstd = { version = "=0.0.0-2026-07-27-0206", optional = true, default-features = false, features = ["alloc"] } # No-op stand-ins for verus_verify/verus_spec/proof, used when the `verus` feature # is disabled so the annotated source still compiles as ordinary Rust. This is a diff --git a/bindings/ffi/Cargo.lock b/bindings/ffi/Cargo.lock index 7bc3ff501..6bec9f998 100644 --- a/bindings/ffi/Cargo.lock +++ b/bindings/ffi/Cargo.lock @@ -262,6 +262,12 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -1544,16 +1550,17 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "verus_builtin" -version = "0.0.0-2026-05-17-0151" +version = "0.0.0-2026-07-27-0206" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cab9266bfb5cf45a080f425c560b0e0be2bf81194f0e80258990c49c1829d8b9" +checksum = "e70a747c69a08b336573d67a8702993572317ab0f506084f717e6cefd3835f56" [[package]] name = "verus_builtin_macros" -version = "0.0.0-2026-07-12-0122" +version = "0.0.0-2026-07-27-0206" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fb60e3a141ababe618fbdb759f14aa8544f6346a98a44c1e3a7dbe20b1f2892" +checksum = "a3b07917203e0f36da3d29529f511f1ec27a1825529846433b4bc7384d400444" dependencies = [ + "convert_case", "proc-macro2", "quote", "syn 2.0.119", @@ -1564,9 +1571,9 @@ dependencies = [ [[package]] name = "verus_prettyplease" -version = "0.0.0-2026-05-31-0205" +version = "0.0.0-2026-07-27-0206" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ffdc94c89109a67c59646f6b6e71b49de394020f45294247dd716470523245b" +checksum = "028a6b1bedf498a425f5fa733c4a9aada900734c5ac82e4801597ff3ff70fa62" dependencies = [ "proc-macro2", "verus_syn", @@ -1574,9 +1581,9 @@ dependencies = [ [[package]] name = "verus_state_machines_macros" -version = "0.0.0-2026-06-14-0213" +version = "0.0.0-2026-07-27-0206" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6704a9ce586a5027f87933d9a84817411c937804090a9585c28bf335ac37eefc" +checksum = "911e2a65229749eeb48a1f27d2b47a46b4fb43673f918854b065a932c86305b9" dependencies = [ "indexmap 1.9.3", "proc-macro2", @@ -1586,9 +1593,9 @@ dependencies = [ [[package]] name = "verus_syn" -version = "0.0.0-2026-05-31-0205" +version = "0.0.0-2026-07-27-0206" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "030b774330d5ec618691c2cd6fba8bbb13bd538f3884a1143450baec25749706" +checksum = "fdcccb102ae806c65ae59653757ac28ecff328f996159757d17b5c735a7f7850" dependencies = [ "proc-macro2", "quote", @@ -1603,9 +1610,9 @@ checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" [[package]] name = "vstd" -version = "0.0.0-2026-07-12-0122" +version = "0.0.0-2026-07-27-0206" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd0a0d328e1d6382af99a65f71677cf4b9200ec24e09fcafa7b1a3af8c8f5111" +checksum = "3c72419942f28be7f54bca5e8bc1ac207f9efd45e424788905ecb1213881950a" dependencies = [ "verus_builtin", "verus_builtin_macros", diff --git a/bindings/java/Cargo.lock b/bindings/java/Cargo.lock index 3e369765b..7d8927aff 100644 --- a/bindings/java/Cargo.lock +++ b/bindings/java/Cargo.lock @@ -176,6 +176,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -1362,16 +1368,17 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "verus_builtin" -version = "0.0.0-2026-05-17-0151" +version = "0.0.0-2026-07-27-0206" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cab9266bfb5cf45a080f425c560b0e0be2bf81194f0e80258990c49c1829d8b9" +checksum = "e70a747c69a08b336573d67a8702993572317ab0f506084f717e6cefd3835f56" [[package]] name = "verus_builtin_macros" -version = "0.0.0-2026-07-12-0122" +version = "0.0.0-2026-07-27-0206" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fb60e3a141ababe618fbdb759f14aa8544f6346a98a44c1e3a7dbe20b1f2892" +checksum = "a3b07917203e0f36da3d29529f511f1ec27a1825529846433b4bc7384d400444" dependencies = [ + "convert_case", "proc-macro2", "quote", "syn 2.0.119", @@ -1382,9 +1389,9 @@ dependencies = [ [[package]] name = "verus_prettyplease" -version = "0.0.0-2026-05-31-0205" +version = "0.0.0-2026-07-27-0206" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ffdc94c89109a67c59646f6b6e71b49de394020f45294247dd716470523245b" +checksum = "028a6b1bedf498a425f5fa733c4a9aada900734c5ac82e4801597ff3ff70fa62" dependencies = [ "proc-macro2", "verus_syn", @@ -1392,9 +1399,9 @@ dependencies = [ [[package]] name = "verus_state_machines_macros" -version = "0.0.0-2026-06-14-0213" +version = "0.0.0-2026-07-27-0206" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6704a9ce586a5027f87933d9a84817411c937804090a9585c28bf335ac37eefc" +checksum = "911e2a65229749eeb48a1f27d2b47a46b4fb43673f918854b065a932c86305b9" dependencies = [ "indexmap 1.9.3", "proc-macro2", @@ -1404,9 +1411,9 @@ dependencies = [ [[package]] name = "verus_syn" -version = "0.0.0-2026-05-31-0205" +version = "0.0.0-2026-07-27-0206" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "030b774330d5ec618691c2cd6fba8bbb13bd538f3884a1143450baec25749706" +checksum = "fdcccb102ae806c65ae59653757ac28ecff328f996159757d17b5c735a7f7850" dependencies = [ "proc-macro2", "quote", @@ -1421,9 +1428,9 @@ checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" [[package]] name = "vstd" -version = "0.0.0-2026-07-12-0122" +version = "0.0.0-2026-07-27-0206" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd0a0d328e1d6382af99a65f71677cf4b9200ec24e09fcafa7b1a3af8c8f5111" +checksum = "3c72419942f28be7f54bca5e8bc1ac207f9efd45e424788905ecb1213881950a" dependencies = [ "verus_builtin", "verus_builtin_macros", diff --git a/bindings/python/Cargo.lock b/bindings/python/Cargo.lock index eaa4acc52..b3a29f1c1 100644 --- a/bindings/python/Cargo.lock +++ b/bindings/python/Cargo.lock @@ -160,6 +160,12 @@ dependencies = [ "thiserror", ] +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -1343,16 +1349,17 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "verus_builtin" -version = "0.0.0-2026-05-17-0151" +version = "0.0.0-2026-07-27-0206" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cab9266bfb5cf45a080f425c560b0e0be2bf81194f0e80258990c49c1829d8b9" +checksum = "e70a747c69a08b336573d67a8702993572317ab0f506084f717e6cefd3835f56" [[package]] name = "verus_builtin_macros" -version = "0.0.0-2026-07-12-0122" +version = "0.0.0-2026-07-27-0206" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fb60e3a141ababe618fbdb759f14aa8544f6346a98a44c1e3a7dbe20b1f2892" +checksum = "a3b07917203e0f36da3d29529f511f1ec27a1825529846433b4bc7384d400444" dependencies = [ + "convert_case", "proc-macro2", "quote", "syn 2.0.119", @@ -1363,9 +1370,9 @@ dependencies = [ [[package]] name = "verus_prettyplease" -version = "0.0.0-2026-05-31-0205" +version = "0.0.0-2026-07-27-0206" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ffdc94c89109a67c59646f6b6e71b49de394020f45294247dd716470523245b" +checksum = "028a6b1bedf498a425f5fa733c4a9aada900734c5ac82e4801597ff3ff70fa62" dependencies = [ "proc-macro2", "verus_syn", @@ -1373,9 +1380,9 @@ dependencies = [ [[package]] name = "verus_state_machines_macros" -version = "0.0.0-2026-06-14-0213" +version = "0.0.0-2026-07-27-0206" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6704a9ce586a5027f87933d9a84817411c937804090a9585c28bf335ac37eefc" +checksum = "911e2a65229749eeb48a1f27d2b47a46b4fb43673f918854b065a932c86305b9" dependencies = [ "indexmap 1.9.3", "proc-macro2", @@ -1385,9 +1392,9 @@ dependencies = [ [[package]] name = "verus_syn" -version = "0.0.0-2026-05-31-0205" +version = "0.0.0-2026-07-27-0206" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "030b774330d5ec618691c2cd6fba8bbb13bd538f3884a1143450baec25749706" +checksum = "fdcccb102ae806c65ae59653757ac28ecff328f996159757d17b5c735a7f7850" dependencies = [ "proc-macro2", "quote", @@ -1402,9 +1409,9 @@ checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" [[package]] name = "vstd" -version = "0.0.0-2026-07-12-0122" +version = "0.0.0-2026-07-27-0206" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd0a0d328e1d6382af99a65f71677cf4b9200ec24e09fcafa7b1a3af8c8f5111" +checksum = "3c72419942f28be7f54bca5e8bc1ac207f9efd45e424788905ecb1213881950a" dependencies = [ "verus_builtin", "verus_builtin_macros", diff --git a/bindings/wasm/Cargo.lock b/bindings/wasm/Cargo.lock index 481c43903..52b805af6 100644 --- a/bindings/wasm/Cargo.lock +++ b/bindings/wasm/Cargo.lock @@ -177,6 +177,12 @@ dependencies = [ "thiserror", ] +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -1340,16 +1346,17 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "verus_builtin" -version = "0.0.0-2026-05-17-0151" +version = "0.0.0-2026-07-27-0206" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cab9266bfb5cf45a080f425c560b0e0be2bf81194f0e80258990c49c1829d8b9" +checksum = "e70a747c69a08b336573d67a8702993572317ab0f506084f717e6cefd3835f56" [[package]] name = "verus_builtin_macros" -version = "0.0.0-2026-07-12-0122" +version = "0.0.0-2026-07-27-0206" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fb60e3a141ababe618fbdb759f14aa8544f6346a98a44c1e3a7dbe20b1f2892" +checksum = "a3b07917203e0f36da3d29529f511f1ec27a1825529846433b4bc7384d400444" dependencies = [ + "convert_case", "proc-macro2", "quote", "syn 2.0.119", @@ -1360,9 +1367,9 @@ dependencies = [ [[package]] name = "verus_prettyplease" -version = "0.0.0-2026-05-31-0205" +version = "0.0.0-2026-07-27-0206" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ffdc94c89109a67c59646f6b6e71b49de394020f45294247dd716470523245b" +checksum = "028a6b1bedf498a425f5fa733c4a9aada900734c5ac82e4801597ff3ff70fa62" dependencies = [ "proc-macro2", "verus_syn", @@ -1370,9 +1377,9 @@ dependencies = [ [[package]] name = "verus_state_machines_macros" -version = "0.0.0-2026-06-14-0213" +version = "0.0.0-2026-07-27-0206" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6704a9ce586a5027f87933d9a84817411c937804090a9585c28bf335ac37eefc" +checksum = "911e2a65229749eeb48a1f27d2b47a46b4fb43673f918854b065a932c86305b9" dependencies = [ "indexmap 1.9.3", "proc-macro2", @@ -1382,9 +1389,9 @@ dependencies = [ [[package]] name = "verus_syn" -version = "0.0.0-2026-05-31-0205" +version = "0.0.0-2026-07-27-0206" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "030b774330d5ec618691c2cd6fba8bbb13bd538f3884a1143450baec25749706" +checksum = "fdcccb102ae806c65ae59653757ac28ecff328f996159757d17b5c735a7f7850" dependencies = [ "proc-macro2", "quote", @@ -1399,9 +1406,9 @@ checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" [[package]] name = "vstd" -version = "0.0.0-2026-07-12-0122" +version = "0.0.0-2026-07-27-0206" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd0a0d328e1d6382af99a65f71677cf4b9200ec24e09fcafa7b1a3af8c8f5111" +checksum = "3c72419942f28be7f54bca5e8bc1ac207f9efd45e424788905ecb1213881950a" dependencies = [ "verus_builtin", "verus_builtin_macros", From 68e64e19442d45df407d84c4b9563633bb297402 Mon Sep 17 00:00:00 2001 From: Jay Lorch Date: Sun, 26 Jul 2026 21:05:28 -0700 Subject: [PATCH 20/41] Leverage recent Verus fixes to verify more --- src/lib.rs | 5 ++ src/number.rs | 77 +++++++++++++++++-------- src/verify/bigint_assumptions.rs | 97 ++++++++++++++++++++++++++++++++ src/verify/f64_assumptions.rs | 10 ++++ 4 files changed, 165 insertions(+), 24 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 7fc6baab5..5102213f0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,6 +10,11 @@ // Verus needs to name it to give it a specification. Verification builds use the // Verus toolchain, so this gate never applies to shipped code. #![cfg_attr(verus_keep_ghost, feature(fmt_arguments_from_str))] +// Loop invariants are attached with `#[verus_spec(invariant ...)]` on the loop +// statement itself. Applying a proc-macro attribute in statement position is +// still unstable, so verification builds opt in. Shipped builds strip the +// attribute via `cfg_attr` and therefore never need this feature. +#![cfg_attr(verus_keep_ghost, feature(proc_macro_hygiene))] // Ensure that all lint names are valid. #![deny(unknown_lints)] // Fail-fast lints: correctness, safety, and API surface diff --git a/src/number.rs b/src/number.rs index 8c7bd621e..88b3e8147 100644 --- a/src/number.rs +++ b/src/number.rs @@ -753,20 +753,21 @@ impl Number { Ok(()) } - // Verus does not yet support overloaded op-assignment operators like `+=`. - #[verus_verify(external_body)] #[verus_spec(result => ensures result matches Ok(value) && self@.add_ensures(rhs@, value@), )] pub fn add(&self, rhs: &Self) -> Result { - if matches!(self, Number::Float(_)) || matches!(rhs, Number::Float(_)) { - return Ok(Number::normalize_float( - self.to_f64_lossy() + rhs.to_f64_lossy(), - )); + proof! { + axiom_f64_ops_deterministic(); + axiom_bigint_obeys_add_spec(); + axiom_bigint_add_assign_req(); } match (self, rhs) { + (Number::Float(_), _) | (_, Number::Float(_)) => Ok(Number::normalize_float( + self.to_f64_lossy() + rhs.to_f64_lossy(), + )), (Number::UInt(a), Number::UInt(b)) => { if let Some(sum) = a.checked_add(*b) { Ok(Number::UInt(sum)) @@ -796,7 +797,6 @@ impl Number { sum += other.to_bigint_owned().unwrap(); Ok(Number::from_bigint_owned(sum)) } - _ => unreachable!(), } } @@ -810,20 +810,21 @@ impl Number { Ok(()) } - // Verus does not yet support overloaded op-assignment operators like `-=`. - #[verus_verify(external_body)] #[verus_spec(result => ensures result matches Ok(value) && self@.sub_ensures(rhs@, value@), )] pub fn sub(&self, rhs: &Self) -> Result { - if matches!(self, Number::Float(_)) || matches!(rhs, Number::Float(_)) { - return Ok(Number::normalize_float( - self.to_f64_lossy() - rhs.to_f64_lossy(), - )); + proof! { + axiom_f64_ops_deterministic(); + axiom_bigint_obeys_sub_spec(); + axiom_bigint_sub_assign_req(); } match (self, rhs) { + (Number::Float(_), _) | (_, Number::Float(_)) => Ok(Number::normalize_float( + self.to_f64_lossy() - rhs.to_f64_lossy(), + )), (Number::UInt(a), Number::UInt(b)) => { if a >= b { Ok(Number::UInt(a - b)) @@ -855,7 +856,6 @@ impl Number { diff -= (**b).clone(); Ok(Number::from_bigint_owned(diff)) } - _ => unreachable!(), } } @@ -1204,8 +1204,6 @@ impl Number { Some(Number::from_bigint_owned(a ^ b)) } - // Verus does not yet support overloaded assigment operators like `<<=`. - #[verus_verify(external_body)] #[verus_spec(result => ensures match (self@.to_int(), rhs@, result) { @@ -1221,14 +1219,13 @@ impl Number { }, )] pub fn lsh(&self, rhs: &Self) -> Option { + proof! { axiom_bigint_shl_assign_req(); } let shift = rhs.as_u32()? as usize; let mut value = self.ensure_integer()?; value <<= shift; Some(Number::from_bigint_owned(value)) } - // Verus does not yet support overloaded op-assignment operators such as `>>=`. - #[verus_verify(external_body)] #[verus_spec(result => ensures match (self@.to_int(), rhs@, result) { @@ -1244,14 +1241,13 @@ impl Number { }, )] pub fn rsh(&self, rhs: &Self) -> Option { + proof! { axiom_bigint_shr_assign_req(); } let shift = rhs.as_u32()? as usize; let mut value = self.ensure_integer()?; value >>= shift; Some(Number::from_bigint_owned(value)) } - // Verus panics while translating overloaded `!` on an external `BigInt`. - #[verus_verify(external_body)] #[verus_spec(result => ensures match (self@.to_int(), result) { @@ -1265,6 +1261,7 @@ impl Number { )] pub fn neg(&self) -> Option { let mut value = self.ensure_integer()?; + proof! { axiom_bigint_not_spec(value); } value = !value; Some(Number::from_bigint_owned(value)) } @@ -1483,29 +1480,35 @@ impl Number { } } -// Verus does not yet support overloaded op-assignment operators such as `<<=`. -#[verus_verify(external_body)] #[verus_spec(result => ensures result@ == NumberView::Integer(pow2(exp as nat) as int), )] fn two_pow_positive(exp: u32) -> Number { if exp < 64 { + proof! { + vstd::arithmetic::power2::lemma2_to64(); + vstd::arithmetic::power2::lemma_pow2_strictly_increases(exp as nat, 64); + vstd::bits::lemma_u64_shl_is_mul(1u64, exp as u64); + } Number::UInt(1u64 << exp) } else { let mut value = BigInt::one(); + proof! { axiom_bigint_shl_assign_req(); } value <<= exp as usize; Number::from_bigint_owned(value) } } -// Verus does not yet support overloaded op-assignment operators such as `*=`. -#[verus_verify(external_body)] #[verus_spec(result => ensures result@ == vstd::arithmetic::power::pow(10, exp as nat), )] fn pow10_bigint(exp: u32) -> BigInt { + proof! { + vstd::arithmetic::power::lemma_pow0(10); + } + if exp == 0 { return BigInt::one(); } @@ -1514,7 +1517,31 @@ fn pow10_bigint(exp: u32) -> BigInt { let mut base = BigInt::from(10u8); let mut e = exp; + #[cfg_attr(verus_keep_ghost, verus_spec( + invariant + result@ * vstd::arithmetic::power::pow(base@, e as nat) + == vstd::arithmetic::power::pow(10, exp as nat), + decreases e, + ))] while e > 0 { + proof! { + axiom_bigint_obeys_mul_spec(); + axiom_bigint_mul_assign_ref_req(); + assert(e & 1 == e % 2) by (bit_vector); + assert(e >> 1 == e / 2) by (bit_vector); + // `pow(base, 2) == base * base`, so squaring `base` halves `e`. + vstd::arithmetic::power::lemma_pow0(base@); + vstd::arithmetic::power::lemma_pow1(base@); + vstd::arithmetic::power::lemma_pow_adds(base@, 1, 1); + vstd::arithmetic::power::lemma_pow_multiplies(base@, 2, (e / 2) as nat); + // Peeling one factor off when `e` is odd. + vstd::arithmetic::power::lemma_pow_adds(base@, 1, (e - e % 2) as nat); + vstd::arithmetic::mul::lemma_mul_is_associative( + result@, + base@, + vstd::arithmetic::power::pow(base@, (e - e % 2) as nat), + ); + } if e & 1 == 1 { result *= &base; } @@ -1524,6 +1551,8 @@ fn pow10_bigint(exp: u32) -> BigInt { e >>= 1; } + proof! { vstd::arithmetic::power::lemma_pow0(base@); } + result } diff --git a/src/verify/bigint_assumptions.rs b/src/verify/bigint_assumptions.rs index 950d2fb22..cfe8cafc2 100644 --- a/src/verify/bigint_assumptions.rs +++ b/src/verify/bigint_assumptions.rs @@ -86,6 +86,47 @@ pub assume_specification[ >::shr_assign ]( (*final(value))@ == (*old(value))@ / (pow2(shift as nat) as int), ; +pub assume_specification[ >::shl_assign ]( + value: &mut BigInt, + shift: usize, +) + ensures + (*final(value))@ == (*old(value))@ * (pow2(shift as nat) as int), +; + +// vstd's op-assignment traits carry an uninterpreted precondition that each +// implementation is free to choose. `num_bigint` imposes no preconditions on +// its op-assignment operators, so they always hold. +pub axiom fn axiom_bigint_shr_assign_req() + ensures + forall|value: BigInt, shift: usize| #[trigger] + >::shr_assign_req(&value, shift), +; + +pub axiom fn axiom_bigint_shl_assign_req() + ensures + forall|value: BigInt, shift: usize| #[trigger] + >::shl_assign_req(&value, shift), +; + +pub axiom fn axiom_bigint_add_assign_req() + ensures + forall|value: BigInt, rhs: BigInt| #[trigger] + >::add_assign_req(&value, rhs), +; + +pub axiom fn axiom_bigint_sub_assign_req() + ensures + forall|value: BigInt, rhs: BigInt| #[trigger] + >::sub_assign_req(&value, rhs), +; + +pub axiom fn axiom_bigint_mul_assign_ref_req() + ensures + forall|value: BigInt, rhs: &BigInt| #[trigger] + >::mul_assign_req(&value, rhs), +; + pub axiom fn axiom_bigint_not_spec(value: BigInt) ensures ::obeys_not_spec(), @@ -181,6 +222,18 @@ pub assume_specification[ >::from ](u: u128) res@ == u, ; +pub assume_specification[ >::from ](u: u8) -> (res: BigInt) + ensures + res@ == u, +; + +// One + +pub assume_specification[ ::one ]() -> (res: BigInt) + ensures + res@ == 1, +; + // Negation pub assume_specification[ ::neg ](x: BigInt) -> (y: BigInt) @@ -190,11 +243,29 @@ pub assume_specification[ ::neg ](x: BigInt) -> (y: Bi // Addition +pub axiom fn axiom_bigint_obeys_add_spec() + ensures + ::obeys_add_spec(), + forall|lhs: BigInt, rhs: BigInt| #[trigger] + ::add_req(lhs, rhs), + forall|lhs: BigInt, rhs: BigInt| #[trigger] + ::add_spec(lhs, rhs)@ + == lhs@ + rhs@, +; + pub assume_specification[ ::add ](x: BigInt, y: BigInt) -> (o: BigInt) ensures o@ == x@ + y@, ; +pub assume_specification[ ::add_assign ]( + value: &mut BigInt, + rhs: BigInt, +) + ensures + (*final(value))@ == (*old(value))@ + rhs@, +; + pub assume_specification<'a>[ >::add ](x: BigInt, y: &BigInt) -> (o: BigInt) ensures o@ == x@ + (*y)@, @@ -307,11 +378,29 @@ pub assume_specification<'a>[ >::add ](x: BigInt // Subtraction +pub axiom fn axiom_bigint_obeys_sub_spec() + ensures + ::obeys_sub_spec(), + forall|lhs: BigInt, rhs: BigInt| #[trigger] + ::sub_req(lhs, rhs), + forall|lhs: BigInt, rhs: BigInt| #[trigger] + ::sub_spec(lhs, rhs)@ + == lhs@ - rhs@, +; + pub assume_specification[ ::sub ](x: BigInt, y: BigInt) -> (o: BigInt) ensures o@ == x@ - y@, ; +pub assume_specification[ ::sub_assign ]( + value: &mut BigInt, + rhs: BigInt, +) + ensures + (*final(value))@ == (*old(value))@ - rhs@, +; + pub assume_specification<'a>[ >::sub ](x: BigInt, y: &BigInt) -> (o: BigInt) ensures o@ == x@ - (*y)@, @@ -439,6 +528,14 @@ pub assume_specification[ ::mul ](x: BigInt, y: BigInt o@ == x@ * y@, ; +pub assume_specification<'a>[ >::mul_assign ]( + value: &mut BigInt, + rhs: &BigInt, +) + ensures + (*final(value))@ == (*old(value))@ * rhs@, +; + pub assume_specification<'a>[ >::mul ](x: BigInt, y: &BigInt) -> (o: BigInt) ensures o@ == x@ * (*y)@, diff --git a/src/verify/f64_assumptions.rs b/src/verify/f64_assumptions.rs index 395c9407a..ce990f576 100644 --- a/src/verify/f64_assumptions.rs +++ b/src/verify/f64_assumptions.rs @@ -47,6 +47,16 @@ pub axiom fn axiom_f64_ops_deterministic() ::obeys_sub_spec(), ::obeys_mul_spec(), ::obeys_div_spec(), + forall|lhs: f64, rhs: f64| #[trigger] + ::add_req(lhs, rhs), + forall|lhs: f64, rhs: f64| #[trigger] + ::add_spec(lhs, rhs) + == lhs.ieee_add(rhs), + forall|lhs: f64, rhs: f64| #[trigger] + ::sub_req(lhs, rhs), + forall|lhs: f64, rhs: f64| #[trigger] + ::sub_spec(lhs, rhs) + == lhs.ieee_sub(rhs), forall|lhs: f64, rhs: f64| #[trigger] ::mul_req(lhs, rhs), forall|lhs: f64, rhs: f64| #[trigger] From 9825b18e17703254796051fcf91a03ac81044063 Mon Sep 17 00:00:00 2001 From: Jay Lorch Date: Mon, 27 Jul 2026 15:51:04 -0700 Subject: [PATCH 21/41] Rebase to upstream/main --- src/number.rs | 39 --------------------------------------- 1 file changed, 39 deletions(-) diff --git a/src/number.rs b/src/number.rs index 88b3e8147..c9bb3fadc 100644 --- a/src/number.rs +++ b/src/number.rs @@ -1054,19 +1054,6 @@ impl Number { }, )] pub fn modulo(self, rhs: &Self) -> Result { -<<<<<<< HEAD - // Conversion fails for a non-integral float, and also for an integral - // one whose magnitude exceeds 2^53, which cannot be represented exactly. - let (a, b) = match (self.to_bigint_owned(), rhs.to_bigint_owned()) { - (Some(a), Some(b)) => (a, b), - _ => bail!("modulo on floating-point number"), - }; - - if b.is_zero() { - bail!("modulo by zero"); - } - -======= proof! { axiom_bigint_obeys_div_rem_spec(); } @@ -1082,7 +1069,6 @@ impl Number { bail!("modulo by zero"); } ->>>>>>> efd56bf (Fix bug in Number::modulo) let rem = a % &b; Ok(Number::from_bigint_owned(rem)) } @@ -1758,29 +1744,4 @@ mod tests { Ok(Number::UInt(value)) if value == 1u64 << 63 )); } - - #[test] - fn modulo_handles_floats_that_are_really_integers() { - // An integral float is a valid operand. - assert!(matches!( - Number::Float(4.0).modulo(&Number::Int(3)), - Ok(Number::UInt(1)) - )); - // `1e300` has no fractional part, but it is too large to convert to an - // integer exactly. This must report an error, not panic. - assert_eq!( - Number::Float(1e300) - .modulo(&Number::Int(3)) - .err() - .map(|e| e.to_string()), - Some("modulo on floating-point number".to_string()) - ); - assert_eq!( - Number::Int(3) - .modulo(&Number::Float(1e300)) - .err() - .map(|e| e.to_string()), - Some("modulo on floating-point number".to_string()) - ); - } } From a23810c8eecd4320116e3b8ffc56304345872b70 Mon Sep 17 00:00:00 2001 From: Jay Lorch Date: Tue, 28 Jul 2026 11:13:39 -0700 Subject: [PATCH 22/41] Make number specs more legible --- src/number.rs | 225 +++++++++++++++++++++++--------------------------- 1 file changed, 103 insertions(+), 122 deletions(-) diff --git a/src/number.rs b/src/number.rs index c9bb3fadc..80e9c792a 100644 --- a/src/number.rs +++ b/src/number.rs @@ -175,9 +175,9 @@ impl Number { match self@ { NumberView::Integer(n) => result matches Some(bi) && bi@ == n, NumberView::Float(f) => - match result { - Some(bi) => float_to_small_int(f) == Some(bi@), - None => float_to_small_int(f) is None, + match float_to_small_int(f) { + Some(i) => result matches Some(bi) && bi@ == i, + None => result is None, }, }, )] @@ -497,13 +497,14 @@ impl Number { } }, NumberView::Float(f) => { - let convertible = f.is_finite_spec() - && f.ieee_ge(0.0f64) - && spec_f64_fract(f).eq_spec(&0.0f64) - && ieee_float_cast::(ieee_float_cast::(f)).eq_spec(&f); - match result { - Some(value) => convertible && value == ieee_float_cast::(f), - None => !convertible, + if f.is_finite_spec() + && f.ieee_ge(0.0f64) + && spec_f64_fract(f).eq_spec(&0.0f64) + && ieee_float_cast::(ieee_float_cast::(f)).eq_spec(&f) { + result matches Some(value) && value == ieee_float_cast::(f) + } + else { + result is None } }, }, @@ -543,12 +544,13 @@ impl Number { } }, NumberView::Float(f) => { - let convertible = f.is_finite_spec() - && spec_f64_fract(f).eq_spec(&0.0f64) - && ieee_float_cast::(ieee_float_cast::(f)).eq_spec(&f); - match result { - Some(value) => convertible && value == ieee_float_cast::(f), - None => !convertible, + if f.is_finite_spec() + && spec_f64_fract(f).eq_spec(&0.0f64) + && ieee_float_cast::(ieee_float_cast::(f)).eq_spec(&f) { + result matches Some(value) && value == ieee_float_cast::(f) + } + else { + result is None } }, }, @@ -585,14 +587,15 @@ impl Number { } }, NumberView::Float(f) => { - let convertible = f.is_finite_spec() - && f.ieee_ge(0.0f64) - && spec_f64_fract(f).eq_spec(&0.0f64) - && f.ieee_le(ieee_float_cast::(u64::MAX)) - && ieee_float_cast::(ieee_float_cast::(f)).eq_spec(&f); - match result { - Some(value) => convertible && value == ieee_float_cast::(f), - None => !convertible, + if f.is_finite_spec() + && f.ieee_ge(0.0f64) + && spec_f64_fract(f).eq_spec(&0.0f64) + && f.ieee_le(ieee_float_cast::(u64::MAX)) + && ieee_float_cast::(ieee_float_cast::(f)).eq_spec(&f) { + result matches Some(value) && value == ieee_float_cast::(f) + } + else { + result is None } }, }, @@ -632,14 +635,15 @@ impl Number { } }, NumberView::Float(f) => { - let convertible = f.is_finite_spec() - && spec_f64_fract(f).eq_spec(&0.0f64) - && f.ieee_ge(ieee_float_cast::(i64::MIN)) - && f.ieee_le(ieee_float_cast::(i64::MAX)) - && ieee_float_cast::(ieee_float_cast::(f)).eq_spec(&f); - match result { - Some(value) => convertible && value == ieee_float_cast::(f), - None => !convertible, + if f.is_finite_spec() + && spec_f64_fract(f).eq_spec(&0.0f64) + && f.ieee_ge(ieee_float_cast::(i64::MIN)) + && f.ieee_le(ieee_float_cast::(i64::MAX)) + && ieee_float_cast::(ieee_float_cast::(f)).eq_spec(&f) { + result matches Some(value) && value == ieee_float_cast::(f) + } + else { + result is None } }, }, @@ -715,9 +719,9 @@ impl Number { match self@ { NumberView::Integer(n) => result matches Some(bi) && bi@ == n, NumberView::Float(f) => { - match result { - Some(bi) => float_to_small_int(f) == Some(bi@), - None => float_to_small_int(f) is None, + match float_to_small_int(f) { + Some(i) => result matches Some(bi) && bi@ == i, + None => result is None, } }, }, @@ -731,9 +735,9 @@ impl Number { match self@ { NumberView::Integer(n) => result matches Ok(bi) && bi@ == n, NumberView::Float(f) => { - match result { - Ok(bi) => float_to_small_int(f) == Some(bi@), - Err(_) => float_to_small_int(f) is None, + match float_to_small_int(f) { + Some(i) => result matches Ok(bi) && bi@ == i, + None => result is Err, } }, }, @@ -1106,14 +1110,11 @@ impl Number { #[verus_spec(result => ensures - match (a@.to_int(), b@.to_int(), result) { - (Some(lhs), Some(rhs), Some((lhs_big, rhs_big))) => { - lhs_big@ == lhs && rhs_big@ == rhs - }, - (Some(_), Some(_), None) => false, - (_, _, Some(_)) => false, - (_, _, None) => true, - }, + match (a@.to_int(), b@.to_int()) { + (Some(lhs), Some(rhs)) => + result matches Some((a, b)) && a@ == lhs && b@ == rhs, + _ => result is None, + } )] #[allow(clippy::if_then_some_else_none)] fn ensure_integers(a: &Number, b: &Number) -> Option<(BigInt, BigInt)> { @@ -1141,13 +1142,11 @@ impl Number { #[verus_spec(result => ensures - match (self@.to_int(), rhs@.to_int(), result) { - (Some(lhs), Some(rhs), Some(value)) => { - value@ == NumberView::Integer(spec_bigint_bitand(lhs, rhs)) - }, - (Some(_), Some(_), None) => false, - (_, _, Some(_)) => false, - (_, _, None) => true, + match (self@.to_int(), rhs@.to_int()) { + (Some(a), Some(b)) => + result matches Some(value) && + value@ == NumberView::Integer(spec_bigint_bitand(a, b)), + _ => result is None, }, )] pub fn and(&self, rhs: &Self) -> Option { @@ -1158,13 +1157,11 @@ impl Number { #[verus_spec(result => ensures - match (self@.to_int(), rhs@.to_int(), result) { - (Some(lhs), Some(rhs), Some(value)) => { - value@ == NumberView::Integer(spec_bigint_bitor(lhs, rhs)) - }, - (Some(_), Some(_), None) => false, - (_, _, Some(_)) => false, - (_, _, None) => true, + match (self@.to_int(), rhs@.to_int()) { + (Some(a), Some(b)) => + result matches Some(value) + && value@ == NumberView::Integer(spec_bigint_bitor(a, b)), + _ => result is None, }, )] pub fn or(&self, rhs: &Self) -> Option { @@ -1175,13 +1172,11 @@ impl Number { #[verus_spec(result => ensures - match (self@.to_int(), rhs@.to_int(), result) { - (Some(lhs), Some(rhs), Some(value)) => { - value@ == NumberView::Integer(spec_bigint_bitxor(lhs, rhs)) - }, - (Some(_), Some(_), None) => false, - (_, _, Some(_)) => false, - (_, _, None) => true, + match (self@.to_int(), rhs@.to_int()) { + (Some(a), Some(b)) => + result matches Some(value) + && value@ == NumberView::Integer(spec_bigint_bitxor(a, b)), + _ => result is None, }, )] pub fn xor(&self, rhs: &Self) -> Option { @@ -1192,16 +1187,17 @@ impl Number { #[verus_spec(result => ensures - match (self@.to_int(), rhs@, result) { - (Some(value), NumberView::Integer(shift), Some(result)) => { - &&& 0 <= shift <= u32::MAX - &&& result@ == NumberView::Integer(value * pow2(shift as nat) as int) - }, - (Some(_), NumberView::Integer(shift), None) => { - !(0 <= shift <= u32::MAX) + match (self@.to_int(), rhs@) { + (Some(value), NumberView::Integer(shift)) => { + if 0 <= shift <= u32::MAX { + result matches Some(r) + && r@ == NumberView::Integer(value * pow2(shift as nat) as int) + } + else { + result is None + } }, - (_, _, Some(_)) => false, - (_, _, None) => true, + _ => result is None, }, )] pub fn lsh(&self, rhs: &Self) -> Option { @@ -1214,16 +1210,17 @@ impl Number { #[verus_spec(result => ensures - match (self@.to_int(), rhs@, result) { - (Some(value), NumberView::Integer(shift), Some(result)) => { - &&& 0 <= shift <= u32::MAX - &&& result@ == NumberView::Integer(value / (pow2(shift as nat) as int)) - }, - (Some(_), NumberView::Integer(shift), None) => { - !(0 <= shift <= u32::MAX) + match (self@.to_int(), rhs@) { + (Some(value), NumberView::Integer(shift)) => { + if 0 <= shift <= u32::MAX { + result matches Some(r) + && r@ == NumberView::Integer(value / pow2(shift as nat) as int) + } + else { + result is None + } }, - (_, _, Some(_)) => false, - (_, _, None) => true, + _ => result is None, }, )] pub fn rsh(&self, rhs: &Self) -> Option { @@ -1236,13 +1233,12 @@ impl Number { #[verus_spec(result => ensures - match (self@.to_int(), result) { - (Some(value), Some(result)) => { - result@ == NumberView::Integer(-value - 1) + match self@.to_int() { + Some(value) => { + result matches Some(r) + && r@ == NumberView::Integer(-value - 1) }, - (Some(_), None) => false, - (None, Some(_)) => false, - (None, None) => true, + None => result is None, }, )] pub fn neg(&self) -> Option { @@ -1320,16 +1316,13 @@ impl Number { #[verus_spec(result => ensures - match result { - Ok(value) => if e >= 0 { - value@ == NumberView::Integer(pow2(e as nat) as int) - } else { - NumberView::Integer(1).div_ensures( - NumberView::Integer(pow2((-(e as int)) as nat) as int), - value@, - ) - }, - Err(_) => false, + result matches Ok(value) && if e >= 0 { + value@ == NumberView::Integer(pow2(e as nat) as int) + } else { + NumberView::Integer(1).div_ensures( + NumberView::Integer(pow2((-(e as int)) as nat) as int), + value@, + ) }, )] pub fn two_pow(e: i32) -> Result { @@ -1358,18 +1351,15 @@ impl Number { #[verus_spec(result => ensures - match result { - Ok(value) => if e >= 0 { - value@ == NumberView::Integer(vstd::arithmetic::power::pow(10, e as nat)) - } else { - NumberView::Integer(1).div_ensures( - NumberView::Integer( - vstd::arithmetic::power::pow(10, (-(e as int)) as nat) - ), - value@, - ) - }, - Err(_) => false, + result matches Ok(value) && if e >= 0 { + value@ == NumberView::Integer(vstd::arithmetic::power::pow(10, e as nat)) + } else { + NumberView::Integer(1).div_ensures( + NumberView::Integer( + vstd::arithmetic::power::pow(10, (-(e as int)) as nat) + ), + value@, + ) }, )] pub fn ten_pow(e: i32) -> Result { @@ -1554,8 +1544,6 @@ fn ten_pow_positive(exp: u32) -> Number { } } -// Verus does not support format! -#[verus_verify(external_body)] fn bigint_to_scientific(value: &BigInt) -> String { let s = value.to_string(); let (sign, digits) = if let Some(rest) = s.strip_prefix('-') { @@ -1572,18 +1560,12 @@ fn bigint_to_scientific(value: &BigInt) -> String { format!("{}{}.{}e{}", sign, &digits[0..1], &digits[1..], exponent) } -#[verus_verify(external_body)] fn parse_scientific_bigint(input: &str) -> Option { let (mantissa, exponent_part) = split_scientific_parts(input)?; let exponent = exponent_part.parse::().ok()?; scientific_parts_to_bigint(mantissa, exponent) } -#[verus_verify(external_body)] -#[verus_spec(result => - ensures - result matches Some((_, exponent)) ==> exponent@.len() > 0, -)] fn split_scientific_parts(input: &str) -> Option<(&str, &str)> { let idx = input.find(['e', 'E'])?; let mantissa = &input[..idx]; @@ -1595,7 +1577,6 @@ fn split_scientific_parts(input: &str) -> Option<(&str, &str)> { } } -#[verus_verify(external_body)] fn scientific_parts_to_bigint(mantissa: &str, exponent: i32) -> Option { let (sign, unsigned) = if let Some(rest) = mantissa.strip_prefix('-') { (-1, rest) From 37b7419fbe5baca49ac4b726f7e505027f24701f Mon Sep 17 00:00:00 2001 From: Jay Lorch Date: Tue, 28 Jul 2026 15:01:36 -0700 Subject: [PATCH 23/41] Clean up number specs --- src/verify/number_specs.rs | 72 ++++++++++++++++++-------------------- 1 file changed, 34 insertions(+), 38 deletions(-) diff --git a/src/verify/number_specs.rs b/src/verify/number_specs.rs index 6c6547948..f35c930ae 100644 --- a/src/verify/number_specs.rs +++ b/src/verify/number_specs.rs @@ -28,7 +28,7 @@ use vstd::std_specs::convert::*; pub assume_specification[ ::clone ](n: &Number) -> (res: Number) ensures - res == n, + res@ == n@, ; pub enum NumberView { @@ -40,7 +40,7 @@ impl View for Number { type V = NumberView; - open spec fn view(&self) -> NumberView + open(crate) spec fn view(&self) -> NumberView { match self { Number::UInt(n) => NumberView::Integer(n as int), @@ -59,15 +59,17 @@ pub open spec fn float_to_small_int(value: f64) -> Option None } else if value >= 0.0 { - if ieee_float_cast::(ieee_float_cast::(value)).eq_spec(&value) { - Some(ieee_float_cast::(value) as int) + let value_as_u64 = ieee_float_cast::(value); + if ieee_float_cast::(value_as_u64).eq_spec(&value) { + Some(value_as_u64 as int) } else { None } } else { - if ieee_float_cast::(ieee_float_cast::(value)).eq_spec(&value) { + let value_as_i64 = ieee_float_cast::(value); + if ieee_float_cast::(value_as_i64).eq_spec(&value) { Some(ieee_float_cast::(value) as int) } else { @@ -125,14 +127,12 @@ impl NumberView { match (self, rhs) { (NumberView::Integer(lhs), NumberView::Integer(rhs)) => result matches NumberView::Integer(sum) && sum == lhs + rhs, - _ => exists|lhs_float: f64, rhs_float: f64| { - &&& self.to_f64_lossy_ensures(lhs_float) - &&& rhs.to_f64_lossy_ensures(rhs_float) - &&& match result { - NumberView::Integer(sum) => float_to_small_int(lhs_float + rhs_float) == Some(sum), - NumberView::Float(sum) => { - float_to_small_int(lhs_float + rhs_float) is None && sum == lhs_float + rhs_float - }, + _ => exists|a: f64, b: f64| { + &&& self.to_f64_lossy_ensures(a) + &&& rhs.to_f64_lossy_ensures(b) + &&& match float_to_small_int(a + b) { + Some(sum) => result == NumberView::Integer(sum), + None => result == NumberView::Float(a + b), } }, } @@ -143,14 +143,12 @@ impl NumberView { match (self, rhs) { (NumberView::Integer(lhs), NumberView::Integer(rhs)) => result matches NumberView::Integer(diff) && diff == lhs - rhs, - _ => exists|lhs_float: f64, rhs_float: f64| { - &&& self.to_f64_lossy_ensures(lhs_float) - &&& rhs.to_f64_lossy_ensures(rhs_float) - &&& match result { - NumberView::Integer(diff) => float_to_small_int(lhs_float - rhs_float) == Some(diff), - NumberView::Float(diff) => { - float_to_small_int(lhs_float - rhs_float) is None && diff == lhs_float - rhs_float - }, + _ => exists|a: f64, b: f64| { + &&& self.to_f64_lossy_ensures(a) + &&& rhs.to_f64_lossy_ensures(b) + &&& match float_to_small_int(a - b) { + Some(diff) => result == NumberView::Integer(diff), + None => result == NumberView::Float(a - b) } }, } @@ -161,14 +159,12 @@ impl NumberView { match (self, rhs) { (NumberView::Integer(lhs), NumberView::Integer(rhs)) => result matches NumberView::Integer(product) && product == lhs * rhs, - _ => exists|lhs_float: f64, rhs_float: f64| { - &&& self.to_f64_lossy_ensures(lhs_float) - &&& rhs.to_f64_lossy_ensures(rhs_float) - &&& match result { - NumberView::Integer(product) => float_to_small_int(lhs_float * rhs_float) == Some(product), - NumberView::Float(product) => { - float_to_small_int(lhs_float * rhs_float) is None && product == lhs_float * rhs_float - }, + _ => exists|a: f64, b: f64| { + &&& self.to_f64_lossy_ensures(a) + &&& rhs.to_f64_lossy_ensures(b) + &&& match float_to_small_int(a * b) { + Some(product) => result == NumberView::Integer(product), + None => result == NumberView::Float(a * b) } }, } @@ -184,19 +180,19 @@ impl NumberView { vstd::arithmetic::div_mod::rust_div(lhs, divisor), ) } else { - exists|lhs_float: f64, rhs_float: f64| { - &&& self.to_f64_lossy_ensures(lhs_float) - &&& rhs.to_f64_lossy_ensures(rhs_float) - &&& result == NumberView::Float(lhs_float / rhs_float) + exists|a: f64, b: f64| { + &&& self.to_f64_lossy_ensures(a) + &&& rhs.to_f64_lossy_ensures(b) + &&& result == NumberView::Float(a / b) } } }, - (NumberView::Float(_), _) | (NumberView::Integer(_), NumberView::Float(_)) => { + _ => { &&& !rhs.is_zero() - &&& exists|lhs_float: f64, rhs_float: f64| { - &&& self.to_f64_lossy_ensures(lhs_float) - &&& rhs.to_f64_lossy_ensures(rhs_float) - &&& result == NumberView::Float(lhs_float / rhs_float) + &&& exists|a: f64, b: f64| { + &&& self.to_f64_lossy_ensures(a) + &&& rhs.to_f64_lossy_ensures(b) + &&& result == NumberView::Float(a / b) } }, } From 27586fa096be46f7b5c58b5c08c25045454bfbf8 Mon Sep 17 00:00:00 2001 From: Jay Lorch Date: Tue, 28 Jul 2026 15:14:13 -0700 Subject: [PATCH 24/41] Update Verus skill file --- .github/skills/verus-verification/SKILL.md | 40 +++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/.github/skills/verus-verification/SKILL.md b/.github/skills/verus-verification/SKILL.md index 5e8b5653b..203c6b9c6 100644 --- a/.github/skills/verus-verification/SKILL.md +++ b/.github/skills/verus-verification/SKILL.md @@ -55,10 +55,18 @@ honest trusted boundary. 5. **Reuse existing semantic models.** - Search `src/verify/` before adding an uninterpreted spec function. - Prefer established models such as `pow2`, `NumberView`, - `spec_to_f64_lossy`, and BigInt view/spec traits. + `to_f64_lossy_ensures`, and BigInt view/spec traits. - If the same mathematical value can have representation-dependent runtime behavior, quantify over the concrete modeled value rather than pretending the view alone determines the result. + - When a view deliberately merges concrete variants, use a relational + postcondition for representation-sensitive operations. For example, + `NumberView::Integer` merges `Int`, `UInt`, and `BigInt`, whose lossy float + conversions and boundary behavior need not be a function of the view alone. + - Propagate that relation through callers with existential result witnesses. + Do not recover hidden representation by existentially inventing a concrete + `Number` whose view matches; that leaks internals and may choose a witness + unrelated to the executable receiver. 6. **Minimize and explain trust.** - Use `external_body` only at the smallest unsupported boundary. @@ -100,6 +108,16 @@ The contract should answer: For arithmetic returning `Result`, avoid vague contracts such as only `result is Ok` when the exact value is knowable. +Write contracts around semantic inputs first, then state the result with +`result matches ...`, `result is None`, or `result is Err`. This is usually +clearer than matching every input/result tuple and separately excluding each +impossible result variant. + +If an abstract view erases representation but the API result depends on it, +allow the honest overlap in the postcondition and explain the boundary. For +example, at `+/-2^53`, primitive and BigInt-backed `Number` values with the same +view can legitimately differ between `Some` and `None` in an exact-float API. + For BigInt operators, provide exact operator models and prove the caller against them. For division producing a float, model the exact lossy conversions used by the executable code. @@ -166,6 +184,13 @@ large. Prove extreme paths, but do not execute resource-heavy regression tests unless the cost is acceptable and intentional. Test the conversion and a smaller representative behavior instead. +For signed division and remainder, model Rust semantics with `rust_div` and +`rust_rem`; mathematical `/` and `%` do not capture truncation toward zero for +all negative inputs. Bridge primitive operator specs such as `RemSpec` to those +models with focused lemmas. Handle `MIN / -1` before either `/` or `%`, because +both machine operations overflow, and prove the exact quotient fits before +connecting a mathematical result to `checked_div` or a narrowing cast. + ### 6. Use Verification Attributes Deliberately - `#[verus_verify]` on an `impl` applies to all methods in that impl. @@ -180,6 +205,19 @@ representative behavior instead. Before diagnosing missing internal markers or macro bugs, inspect braces and attributes. Confirm the method is actually inside the annotated impl. +### 7. Preserve Production Macros + +Do not replace `bail!`, `anyhow!`, or formatting in the executable body merely +to make translation easier. Inspect the macro expansion and specify the +smallest unsupported pieces. For `anyhow!`, this may mean narrow specifications +for `Arguments::from_str`, `format_err`, and `must_use`; if verified callers +only rely on taking the error branch, those assumptions need not promise +anything about the error value. + +After a Verus upgrade, retry the original macro and previously externalized +bodies. Translation support changes, so stale shims and `external_body` +annotations should not become permanent trusted surface by inertia. + ## Diagnosing Verus Failures ### Translation or Compiler Failure From 11f8f79ad6fcf61026cb5bdb7945af65985aced7 Mon Sep 17 00:00:00 2001 From: Jay Lorch Date: Tue, 28 Jul 2026 15:28:27 -0700 Subject: [PATCH 25/41] Remove unused verus_format macro --- src/verify/utils.rs | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/src/verify/utils.rs b/src/verify/utils.rs index 98a2741d2..0c6cad858 100644 --- a/src/verify/utils.rs +++ b/src/verify/utils.rs @@ -5,29 +5,6 @@ use vstd::prelude::*; verus! { -#[verifier::external_body] -pub fn verus_format_helper() -> String -{ - format!("who cares") -} - -macro_rules! verus_format { - ( $( $tt0:tt )* ) => { - { - #[cfg(not(verus_keep_ghost))] - { format!($($tt0)*) } - #[cfg(verus_keep_ghost)] - { verus_format_helper() } - } - } -} - -#[allow(dead_code)] -fn my_test_verus_format(fcn: &'static str, x: u32) -> String -{ - verus_format!("The parameters are `{fcn}` and `{x}`") -} - #[verifier::external_type_specification] #[verifier::external_body] pub struct ExAnyhowError(anyhow::Error); From b672f91bd13741d25990d9e37450eb833d353892 Mon Sep 17 00:00:00 2001 From: Jay Lorch Date: Tue, 28 Jul 2026 16:30:49 -0700 Subject: [PATCH 26/41] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Signed-off-by: Jay Lorch --- src/number.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/number.rs b/src/number.rs index 80e9c792a..cbe9bb8ea 100644 --- a/src/number.rs +++ b/src/number.rs @@ -145,7 +145,7 @@ impl Number { axiom_f64_obeys_partial_cmp_spec(); axiom_f64_ops_deterministic(); axiom_f64_comparisons_match_ieee(); - } + }; if !value.is_finite() || value.fract() != 0.0 { return None; From b76cfa42033b97c6d9cc1e800b850030b3047ae9 Mon Sep 17 00:00:00 2001 From: Jay Lorch Date: Tue, 28 Jul 2026 16:31:54 -0700 Subject: [PATCH 27/41] Simplify license --- verus-shim/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/verus-shim/Cargo.toml b/verus-shim/Cargo.toml index a7d83a94d..1a647602f 100644 --- a/verus-shim/Cargo.toml +++ b/verus-shim/Cargo.toml @@ -6,7 +6,7 @@ name = "regorus-verus-shim" description = "No-op stand-ins for Verus's verus_verify/verus_spec/proof macros, used when the `verus` feature is disabled so annotated source still compiles as ordinary Rust." version = "0.0.0" edition = "2021" -license = "MIT AND Apache-2.0 AND BSD-3-Clause" +license = "MIT" repository = "https://github.com/microsoft/regorus" publish = false From 8cad3e9922c4046b831bb1aad79ea8e981e1b990 Mon Sep 17 00:00:00 2001 From: Jay Lorch Date: Tue, 28 Jul 2026 16:42:27 -0700 Subject: [PATCH 28/41] Undo unnecessary semicolon Copilot recommended --- src/number.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/number.rs b/src/number.rs index cbe9bb8ea..80e9c792a 100644 --- a/src/number.rs +++ b/src/number.rs @@ -145,7 +145,7 @@ impl Number { axiom_f64_obeys_partial_cmp_spec(); axiom_f64_ops_deterministic(); axiom_f64_comparisons_match_ieee(); - }; + } if !value.is_finite() || value.fract() != 0.0 { return None; From 3e020e7400029dbd66ad4b410ddd7c51bfbe84d0 Mon Sep 17 00:00:00 2001 From: Jay Lorch Date: Sun, 2 Aug 2026 15:14:07 -0700 Subject: [PATCH 29/41] Address some comments --- bindings/ruby/Cargo.lock | 58 ++++++++++++++++++++++++---------------- src/number.rs | 3 +-- 2 files changed, 36 insertions(+), 25 deletions(-) diff --git a/bindings/ruby/Cargo.lock b/bindings/ruby/Cargo.lock index 3c7ebc2de..47eb2fe2b 100644 --- a/bindings/ruby/Cargo.lock +++ b/bindings/ruby/Cargo.lock @@ -121,9 +121,9 @@ checksum = "175812e0be2bccb6abe50bb8d566126198344f707e304f45c648fd8f2cc0365e" [[package]] name = "cc" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c89588d05638b5b4594a3348a2d6c20277e43a7f5c5202b05cc56888475a47b8" +checksum = "5add81bb678e6cb321aff7fa0dc7689ad82b112dbc032cea19f91d6b8e3582b9" dependencies = [ "find-msvc-tools", "shlex 2.0.1", @@ -180,15 +180,21 @@ dependencies = [ [[package]] name = "clang-sys" -version = "1.8.1" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +checksum = "157a8ba7b480713b56f4c09fd13fc3e0a22a5dfab8097ba61cbc5feef950788a" dependencies = [ "glob", "libc", "libloading", ] +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -212,20 +218,20 @@ checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8" [[package]] name = "displaydoc" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" +checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.3", ] [[package]] name = "either" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" +checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d" [[package]] name = "email_address" @@ -537,9 +543,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.12.0" +version = "2.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" +checksum = "6a756c3fac73139e83f14c2d742155dd2b78d3ee56597b419a0579b7bdd6dd78" [[package]] name = "itertools" @@ -1063,6 +1069,7 @@ dependencies = [ "rand", "regex", "regorus-mimalloc", + "regorus-verus-shim", "semver", "serde", "serde_json", @@ -1088,6 +1095,10 @@ dependencies = [ "cc", ] +[[package]] +name = "regorus-verus-shim" +version = "0.0.0" + [[package]] name = "regorusrb" version = "0.11.0" @@ -1404,16 +1415,17 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "verus_builtin" -version = "0.0.0-2026-05-17-0151" +version = "0.0.0-2026-07-27-0206" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cab9266bfb5cf45a080f425c560b0e0be2bf81194f0e80258990c49c1829d8b9" +checksum = "e70a747c69a08b336573d67a8702993572317ab0f506084f717e6cefd3835f56" [[package]] name = "verus_builtin_macros" -version = "0.0.0-2026-07-12-0122" +version = "0.0.0-2026-07-27-0206" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fb60e3a141ababe618fbdb759f14aa8544f6346a98a44c1e3a7dbe20b1f2892" +checksum = "a3b07917203e0f36da3d29529f511f1ec27a1825529846433b4bc7384d400444" dependencies = [ + "convert_case", "proc-macro2", "quote", "syn 2.0.119", @@ -1424,9 +1436,9 @@ dependencies = [ [[package]] name = "verus_prettyplease" -version = "0.0.0-2026-05-31-0205" +version = "0.0.0-2026-07-27-0206" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ffdc94c89109a67c59646f6b6e71b49de394020f45294247dd716470523245b" +checksum = "028a6b1bedf498a425f5fa733c4a9aada900734c5ac82e4801597ff3ff70fa62" dependencies = [ "proc-macro2", "verus_syn", @@ -1434,9 +1446,9 @@ dependencies = [ [[package]] name = "verus_state_machines_macros" -version = "0.0.0-2026-06-14-0213" +version = "0.0.0-2026-07-27-0206" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6704a9ce586a5027f87933d9a84817411c937804090a9585c28bf335ac37eefc" +checksum = "911e2a65229749eeb48a1f27d2b47a46b4fb43673f918854b065a932c86305b9" dependencies = [ "indexmap 1.9.3", "proc-macro2", @@ -1446,9 +1458,9 @@ dependencies = [ [[package]] name = "verus_syn" -version = "0.0.0-2026-05-31-0205" +version = "0.0.0-2026-07-27-0206" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "030b774330d5ec618691c2cd6fba8bbb13bd538f3884a1143450baec25749706" +checksum = "fdcccb102ae806c65ae59653757ac28ecff328f996159757d17b5c735a7f7850" dependencies = [ "proc-macro2", "quote", @@ -1463,9 +1475,9 @@ checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" [[package]] name = "vstd" -version = "0.0.0-2026-07-12-0122" +version = "0.0.0-2026-07-27-0206" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd0a0d328e1d6382af99a65f71677cf4b9200ec24e09fcafa7b1a3af8c8f5111" +checksum = "3c72419942f28be7f54bca5e8bc1ac207f9efd45e424788905ecb1213881950a" dependencies = [ "verus_builtin", "verus_builtin_macros", diff --git a/src/number.rs b/src/number.rs index 80e9c792a..d89cd236b 100644 --- a/src/number.rs +++ b/src/number.rs @@ -1710,8 +1710,7 @@ mod tests { } #[test] - fn ten_pow_uses_unsigned_minimum_exponent_magnitude() { - assert_eq!((-(i32::MIN as i64)) as u32, 1u32 << 31); + fn ten_pow_computes_negative_exponent() { assert!(matches!( Number::ten_pow(-3), Ok(Number::Float(value)) if value == 0.001 From 013348cd09fe565e2a4bf0989de60586bff2f5a8 Mon Sep 17 00:00:00 2001 From: Jay Lorch Date: Mon, 3 Aug 2026 11:47:10 -0700 Subject: [PATCH 30/41] Use latest Verus version --- .github/skills/verus-verification/SKILL.md | 10 +++++ .github/workflows/verus.yml | 4 +- Cargo.lock | 50 ++++++++-------------- Cargo.toml | 2 +- bindings/ffi/Cargo.lock | 50 ++++++++-------------- bindings/java/Cargo.lock | 50 ++++++++-------------- bindings/python/Cargo.lock | 50 ++++++++-------------- bindings/wasm/Cargo.lock | 50 ++++++++-------------- src/verify/number_proofs.rs | 19 ++++---- 9 files changed, 108 insertions(+), 177 deletions(-) diff --git a/.github/skills/verus-verification/SKILL.md b/.github/skills/verus-verification/SKILL.md index 203c6b9c6..f9c1d6aa8 100644 --- a/.github/skills/verus-verification/SKILL.md +++ b/.github/skills/verus-verification/SKILL.md @@ -220,6 +220,16 @@ annotations should not become permanent trusted surface by inertia. ## Diagnosing Verus Failures +### Trigger Failure + +Before repairing or replacing a rejected trigger, check whether the quantifier +is semantically necessary. If its bound variables merely name fields of fixed +arguments through equalities such as `lhs == NumberView::Integer(integer_lhs)`, +match on those arguments and state the branch-specific condition directly. This +preserves the contract while removing the quantifier, its trigger, and needless +solver instantiation. Use a natural or artificial trigger only when the contract +genuinely ranges over multiple values that are not determined by fixed inputs. + ### Translation or Compiler Failure 1. Reduce to the exact operator, type, attribute, and impl context. diff --git a/.github/workflows/verus.yml b/.github/workflows/verus.yml index 79ede417b..def4829ee 100644 --- a/.github/workflows/verus.yml +++ b/.github/workflows/verus.yml @@ -35,8 +35,8 @@ jobs: shell: bash run: | set -euxo pipefail - asset_url=https://github.com/verus-lang/verus/releases/download/release%2F0.2026.07.27.31579f0/verus-0.2026.07.27.31579f0-x86-linux.zip - asset_sha256=7a6143e6dcd2db778314ac102c5ceeac1f7f152b028e3428798bff7170212498 + asset_url=https://github.com/verus-lang/verus/releases/download/release%2F0.2026.08.02.b677dd5/verus-0.2026.08.02.b677dd5-x86-linux.zip + asset_sha256=4c769256e888ee84bde85aae44d95c46bccbb8cf70e1d09f537b0d05fe965dee test -n "$asset_url" curl -fsSL "$asset_url" -o verus.zip diff --git a/Cargo.lock b/Cargo.lock index c7ec71586..6c057a480 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -620,12 +620,6 @@ dependencies = [ "zerocopy", ] -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - [[package]] name = "hashbrown" version = "0.14.5" @@ -808,16 +802,6 @@ dependencies = [ "icu_properties", ] -[[package]] -name = "indexmap" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" -dependencies = [ - "autocfg", - "hashbrown 0.12.3", -] - [[package]] name = "indexmap" version = "2.14.0" @@ -1419,7 +1403,7 @@ dependencies = [ "globset", "hashbrown 0.17.1", "icu_casemap", - "indexmap 2.14.0", + "indexmap", "ipnet", "jsonschema", "lazy_static", @@ -1548,7 +1532,7 @@ version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap 2.14.0", + "indexmap", "itoa", "ryu", "serde", @@ -1742,7 +1726,7 @@ version = "0.25.13+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6975367e4d2ef766d86af01ffad14b622fecc8d4357a998fbc4deb6e9bacaf9b" dependencies = [ - "indexmap 2.14.0", + "indexmap", "toml_datetime", "toml_parser", "toml_writer", @@ -1846,15 +1830,15 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "verus_builtin" -version = "0.0.0-2026-07-27-0206" +version = "0.0.0-2026-08-02-0125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e70a747c69a08b336573d67a8702993572317ab0f506084f717e6cefd3835f56" +checksum = "c27a0e580576f364e98925a61618089591e5bdea6e1ccf048b1d56ab3dfada70" [[package]] name = "verus_builtin_macros" -version = "0.0.0-2026-07-27-0206" +version = "0.0.0-2026-08-02-0125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3b07917203e0f36da3d29529f511f1ec27a1825529846433b4bc7384d400444" +checksum = "eabf0883a6e62f1db1538f96607e500dd396ada513329c3f486a14de5611d55b" dependencies = [ "convert_case", "proc-macro2 1.0.107", @@ -1867,9 +1851,9 @@ dependencies = [ [[package]] name = "verus_prettyplease" -version = "0.0.0-2026-07-27-0206" +version = "0.0.0-2026-08-02-0125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "028a6b1bedf498a425f5fa733c4a9aada900734c5ac82e4801597ff3ff70fa62" +checksum = "d88628e583fb019eac36690ca2500b158f0221df6be2bae1e7c2a35dd6536bfc" dependencies = [ "proc-macro2 1.0.107", "verus_syn", @@ -1877,11 +1861,11 @@ dependencies = [ [[package]] name = "verus_state_machines_macros" -version = "0.0.0-2026-07-27-0206" +version = "0.0.0-2026-08-02-0125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "911e2a65229749eeb48a1f27d2b47a46b4fb43673f918854b065a932c86305b9" +checksum = "93f77ff8d121edb1bf2651335769a80a2f611da8573c3b498434a66b3162805f" dependencies = [ - "indexmap 1.9.3", + "indexmap", "proc-macro2 1.0.107", "quote 1.0.47", "verus_syn", @@ -1889,9 +1873,9 @@ dependencies = [ [[package]] name = "verus_syn" -version = "0.0.0-2026-07-27-0206" +version = "0.0.0-2026-08-02-0125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdcccb102ae806c65ae59653757ac28ecff328f996159757d17b5c735a7f7850" +checksum = "f17237ea6d267e457d53ce36f55b315fc9edd26eb88c765dd95e035c0b415869" dependencies = [ "proc-macro2 1.0.107", "quote 1.0.47", @@ -1906,9 +1890,9 @@ checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" [[package]] name = "vstd" -version = "0.0.0-2026-07-27-0206" +version = "0.0.0-2026-08-02-0125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c72419942f28be7f54bca5e8bc1ac207f9efd45e424788905ecb1213881950a" +checksum = "25040241d96eb88dfc6a75c289dc0ef84845ffff051d8e595cc55908d5c06a45" dependencies = [ "verus_builtin", "verus_builtin_macros", @@ -2228,7 +2212,7 @@ checksum = "2d04a6b5381502aa6087c94c669499eb1602eb9c5e8198e534de571f7154809b" dependencies = [ "crc32fast", "flate2", - "indexmap 2.14.0", + "indexmap", "memchr", "typed-path", "zopfli", diff --git a/Cargo.toml b/Cargo.toml index 631ebe04e..c134ad26f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -144,7 +144,7 @@ postcard = { version = "1.1.3", default-features = false, features = ["alloc"], # Verus-related dependencies. # vstd is enabled via the `verus` feature. In no_std builds only the `alloc` feature is used; # the crate's `std` feature additionally enables `vstd/std` (matching vstd's default features). -vstd = { version = "=0.0.0-2026-07-27-0206", optional = true, default-features = false, features = ["alloc"] } +vstd = { version = "=0.0.0-2026-08-02-0125", optional = true, default-features = false, features = ["alloc"] } # No-op stand-ins for verus_verify/verus_spec/proof, used when the `verus` feature # is disabled so the annotated source still compiles as ordinary Rust. This is a diff --git a/bindings/ffi/Cargo.lock b/bindings/ffi/Cargo.lock index 6bec9f998..ee8a4ab51 100644 --- a/bindings/ffi/Cargo.lock +++ b/bindings/ffi/Cargo.lock @@ -159,7 +159,7 @@ checksum = "2ecb53484c9c167ba674026b656d8a27d7657a58e6066aa902bfb1a4aa00ae20" dependencies = [ "clap", "heck", - "indexmap 2.14.0", + "indexmap", "log", "proc-macro2", "quote", @@ -478,12 +478,6 @@ dependencies = [ "regex-syntax", ] -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - [[package]] name = "hashbrown" version = "0.14.5" @@ -660,16 +654,6 @@ dependencies = [ "icu_properties", ] -[[package]] -name = "indexmap" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" -dependencies = [ - "autocfg", - "hashbrown 0.12.3", -] - [[package]] name = "indexmap" version = "2.14.0" @@ -1145,7 +1129,7 @@ dependencies = [ "globset", "hashbrown 0.17.1", "icu_casemap", - "indexmap 2.14.0", + "indexmap", "ipnet", "jsonschema", "lazy_static", @@ -1294,7 +1278,7 @@ version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap 2.14.0", + "indexmap", "itoa", "ryu", "serde", @@ -1447,7 +1431,7 @@ version = "0.9.12+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" dependencies = [ - "indexmap 2.14.0", + "indexmap", "serde_core", "serde_spanned", "toml_datetime", @@ -1550,15 +1534,15 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "verus_builtin" -version = "0.0.0-2026-07-27-0206" +version = "0.0.0-2026-08-02-0125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e70a747c69a08b336573d67a8702993572317ab0f506084f717e6cefd3835f56" +checksum = "c27a0e580576f364e98925a61618089591e5bdea6e1ccf048b1d56ab3dfada70" [[package]] name = "verus_builtin_macros" -version = "0.0.0-2026-07-27-0206" +version = "0.0.0-2026-08-02-0125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3b07917203e0f36da3d29529f511f1ec27a1825529846433b4bc7384d400444" +checksum = "eabf0883a6e62f1db1538f96607e500dd396ada513329c3f486a14de5611d55b" dependencies = [ "convert_case", "proc-macro2", @@ -1571,9 +1555,9 @@ dependencies = [ [[package]] name = "verus_prettyplease" -version = "0.0.0-2026-07-27-0206" +version = "0.0.0-2026-08-02-0125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "028a6b1bedf498a425f5fa733c4a9aada900734c5ac82e4801597ff3ff70fa62" +checksum = "d88628e583fb019eac36690ca2500b158f0221df6be2bae1e7c2a35dd6536bfc" dependencies = [ "proc-macro2", "verus_syn", @@ -1581,11 +1565,11 @@ dependencies = [ [[package]] name = "verus_state_machines_macros" -version = "0.0.0-2026-07-27-0206" +version = "0.0.0-2026-08-02-0125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "911e2a65229749eeb48a1f27d2b47a46b4fb43673f918854b065a932c86305b9" +checksum = "93f77ff8d121edb1bf2651335769a80a2f611da8573c3b498434a66b3162805f" dependencies = [ - "indexmap 1.9.3", + "indexmap", "proc-macro2", "quote", "verus_syn", @@ -1593,9 +1577,9 @@ dependencies = [ [[package]] name = "verus_syn" -version = "0.0.0-2026-07-27-0206" +version = "0.0.0-2026-08-02-0125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdcccb102ae806c65ae59653757ac28ecff328f996159757d17b5c735a7f7850" +checksum = "f17237ea6d267e457d53ce36f55b315fc9edd26eb88c765dd95e035c0b415869" dependencies = [ "proc-macro2", "quote", @@ -1610,9 +1594,9 @@ checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" [[package]] name = "vstd" -version = "0.0.0-2026-07-27-0206" +version = "0.0.0-2026-08-02-0125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c72419942f28be7f54bca5e8bc1ac207f9efd45e424788905ecb1213881950a" +checksum = "25040241d96eb88dfc6a75c289dc0ef84845ffff051d8e595cc55908d5c06a45" dependencies = [ "verus_builtin", "verus_builtin_macros", diff --git a/bindings/java/Cargo.lock b/bindings/java/Cargo.lock index 7d8927aff..2e8e2a516 100644 --- a/bindings/java/Cargo.lock +++ b/bindings/java/Cargo.lock @@ -356,12 +356,6 @@ dependencies = [ "regex-syntax", ] -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - [[package]] name = "hashbrown" version = "0.17.1" @@ -506,16 +500,6 @@ dependencies = [ "icu_properties", ] -[[package]] -name = "indexmap" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" -dependencies = [ - "autocfg", - "hashbrown 0.12.3", -] - [[package]] name = "indexmap" version = "2.14.0" @@ -523,7 +507,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", - "hashbrown 0.17.1", + "hashbrown", "serde", "serde_core", ] @@ -971,7 +955,7 @@ dependencies = [ "ahash", "fluent-uri", "getrandom 0.3.4", - "hashbrown 0.17.1", + "hashbrown", "itoa", "micromap", "parking_lot", @@ -1017,7 +1001,7 @@ dependencies = [ "chrono-tz", "data-encoding", "globset", - "indexmap 2.14.0", + "indexmap", "ipnet", "jsonschema", "lazy_static", @@ -1161,7 +1145,7 @@ version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap 2.14.0", + "indexmap", "itoa", "ryu", "serde", @@ -1368,15 +1352,15 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "verus_builtin" -version = "0.0.0-2026-07-27-0206" +version = "0.0.0-2026-08-02-0125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e70a747c69a08b336573d67a8702993572317ab0f506084f717e6cefd3835f56" +checksum = "c27a0e580576f364e98925a61618089591e5bdea6e1ccf048b1d56ab3dfada70" [[package]] name = "verus_builtin_macros" -version = "0.0.0-2026-07-27-0206" +version = "0.0.0-2026-08-02-0125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3b07917203e0f36da3d29529f511f1ec27a1825529846433b4bc7384d400444" +checksum = "eabf0883a6e62f1db1538f96607e500dd396ada513329c3f486a14de5611d55b" dependencies = [ "convert_case", "proc-macro2", @@ -1389,9 +1373,9 @@ dependencies = [ [[package]] name = "verus_prettyplease" -version = "0.0.0-2026-07-27-0206" +version = "0.0.0-2026-08-02-0125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "028a6b1bedf498a425f5fa733c4a9aada900734c5ac82e4801597ff3ff70fa62" +checksum = "d88628e583fb019eac36690ca2500b158f0221df6be2bae1e7c2a35dd6536bfc" dependencies = [ "proc-macro2", "verus_syn", @@ -1399,11 +1383,11 @@ dependencies = [ [[package]] name = "verus_state_machines_macros" -version = "0.0.0-2026-07-27-0206" +version = "0.0.0-2026-08-02-0125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "911e2a65229749eeb48a1f27d2b47a46b4fb43673f918854b065a932c86305b9" +checksum = "93f77ff8d121edb1bf2651335769a80a2f611da8573c3b498434a66b3162805f" dependencies = [ - "indexmap 1.9.3", + "indexmap", "proc-macro2", "quote", "verus_syn", @@ -1411,9 +1395,9 @@ dependencies = [ [[package]] name = "verus_syn" -version = "0.0.0-2026-07-27-0206" +version = "0.0.0-2026-08-02-0125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdcccb102ae806c65ae59653757ac28ecff328f996159757d17b5c735a7f7850" +checksum = "f17237ea6d267e457d53ce36f55b315fc9edd26eb88c765dd95e035c0b415869" dependencies = [ "proc-macro2", "quote", @@ -1428,9 +1412,9 @@ checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" [[package]] name = "vstd" -version = "0.0.0-2026-07-27-0206" +version = "0.0.0-2026-08-02-0125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c72419942f28be7f54bca5e8bc1ac207f9efd45e424788905ecb1213881950a" +checksum = "25040241d96eb88dfc6a75c289dc0ef84845ffff051d8e595cc55908d5c06a45" dependencies = [ "verus_builtin", "verus_builtin_macros", diff --git a/bindings/python/Cargo.lock b/bindings/python/Cargo.lock index b3a29f1c1..e6cb7d257 100644 --- a/bindings/python/Cargo.lock +++ b/bindings/python/Cargo.lock @@ -340,12 +340,6 @@ dependencies = [ "regex-syntax", ] -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - [[package]] name = "hashbrown" version = "0.17.1" @@ -490,16 +484,6 @@ dependencies = [ "icu_properties", ] -[[package]] -name = "indexmap" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" -dependencies = [ - "autocfg", - "hashbrown 0.12.3", -] - [[package]] name = "indexmap" version = "2.14.0" @@ -507,7 +491,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", - "hashbrown 0.17.1", + "hashbrown", "serde", "serde_core", ] @@ -979,7 +963,7 @@ dependencies = [ "ahash", "fluent-uri", "getrandom 0.3.4", - "hashbrown 0.17.1", + "hashbrown", "itoa", "micromap", "parking_lot", @@ -1025,7 +1009,7 @@ dependencies = [ "chrono-tz", "data-encoding", "globset", - "indexmap 2.14.0", + "indexmap", "ipnet", "jsonschema", "lazy_static", @@ -1152,7 +1136,7 @@ version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap 2.14.0", + "indexmap", "itoa", "ryu", "serde", @@ -1349,15 +1333,15 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "verus_builtin" -version = "0.0.0-2026-07-27-0206" +version = "0.0.0-2026-08-02-0125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e70a747c69a08b336573d67a8702993572317ab0f506084f717e6cefd3835f56" +checksum = "c27a0e580576f364e98925a61618089591e5bdea6e1ccf048b1d56ab3dfada70" [[package]] name = "verus_builtin_macros" -version = "0.0.0-2026-07-27-0206" +version = "0.0.0-2026-08-02-0125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3b07917203e0f36da3d29529f511f1ec27a1825529846433b4bc7384d400444" +checksum = "eabf0883a6e62f1db1538f96607e500dd396ada513329c3f486a14de5611d55b" dependencies = [ "convert_case", "proc-macro2", @@ -1370,9 +1354,9 @@ dependencies = [ [[package]] name = "verus_prettyplease" -version = "0.0.0-2026-07-27-0206" +version = "0.0.0-2026-08-02-0125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "028a6b1bedf498a425f5fa733c4a9aada900734c5ac82e4801597ff3ff70fa62" +checksum = "d88628e583fb019eac36690ca2500b158f0221df6be2bae1e7c2a35dd6536bfc" dependencies = [ "proc-macro2", "verus_syn", @@ -1380,11 +1364,11 @@ dependencies = [ [[package]] name = "verus_state_machines_macros" -version = "0.0.0-2026-07-27-0206" +version = "0.0.0-2026-08-02-0125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "911e2a65229749eeb48a1f27d2b47a46b4fb43673f918854b065a932c86305b9" +checksum = "93f77ff8d121edb1bf2651335769a80a2f611da8573c3b498434a66b3162805f" dependencies = [ - "indexmap 1.9.3", + "indexmap", "proc-macro2", "quote", "verus_syn", @@ -1392,9 +1376,9 @@ dependencies = [ [[package]] name = "verus_syn" -version = "0.0.0-2026-07-27-0206" +version = "0.0.0-2026-08-02-0125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdcccb102ae806c65ae59653757ac28ecff328f996159757d17b5c735a7f7850" +checksum = "f17237ea6d267e457d53ce36f55b315fc9edd26eb88c765dd95e035c0b415869" dependencies = [ "proc-macro2", "quote", @@ -1409,9 +1393,9 @@ checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" [[package]] name = "vstd" -version = "0.0.0-2026-07-27-0206" +version = "0.0.0-2026-08-02-0125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c72419942f28be7f54bca5e8bc1ac207f9efd45e424788905ecb1213881950a" +checksum = "25040241d96eb88dfc6a75c289dc0ef84845ffff051d8e595cc55908d5c06a45" dependencies = [ "verus_builtin", "verus_builtin_macros", diff --git a/bindings/wasm/Cargo.lock b/bindings/wasm/Cargo.lock index 52b805af6..5baf06b2c 100644 --- a/bindings/wasm/Cargo.lock +++ b/bindings/wasm/Cargo.lock @@ -372,12 +372,6 @@ dependencies = [ "regex-syntax", ] -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - [[package]] name = "hashbrown" version = "0.17.1" @@ -522,16 +516,6 @@ dependencies = [ "icu_properties", ] -[[package]] -name = "indexmap" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" -dependencies = [ - "autocfg", - "hashbrown 0.12.3", -] - [[package]] name = "indexmap" version = "2.14.0" @@ -539,7 +523,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", - "hashbrown 0.17.1", + "hashbrown", "serde", "serde_core", ] @@ -970,7 +954,7 @@ dependencies = [ "ahash", "fluent-uri", "getrandom 0.3.4", - "hashbrown 0.17.1", + "hashbrown", "itoa", "micromap", "parking_lot", @@ -1016,7 +1000,7 @@ dependencies = [ "chrono-tz", "data-encoding", "globset", - "indexmap 2.14.0", + "indexmap", "ipnet", "jsonschema", "lazy_static", @@ -1153,7 +1137,7 @@ version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap 2.14.0", + "indexmap", "itoa", "ryu", "serde", @@ -1346,15 +1330,15 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "verus_builtin" -version = "0.0.0-2026-07-27-0206" +version = "0.0.0-2026-08-02-0125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e70a747c69a08b336573d67a8702993572317ab0f506084f717e6cefd3835f56" +checksum = "c27a0e580576f364e98925a61618089591e5bdea6e1ccf048b1d56ab3dfada70" [[package]] name = "verus_builtin_macros" -version = "0.0.0-2026-07-27-0206" +version = "0.0.0-2026-08-02-0125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3b07917203e0f36da3d29529f511f1ec27a1825529846433b4bc7384d400444" +checksum = "eabf0883a6e62f1db1538f96607e500dd396ada513329c3f486a14de5611d55b" dependencies = [ "convert_case", "proc-macro2", @@ -1367,9 +1351,9 @@ dependencies = [ [[package]] name = "verus_prettyplease" -version = "0.0.0-2026-07-27-0206" +version = "0.0.0-2026-08-02-0125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "028a6b1bedf498a425f5fa733c4a9aada900734c5ac82e4801597ff3ff70fa62" +checksum = "d88628e583fb019eac36690ca2500b158f0221df6be2bae1e7c2a35dd6536bfc" dependencies = [ "proc-macro2", "verus_syn", @@ -1377,11 +1361,11 @@ dependencies = [ [[package]] name = "verus_state_machines_macros" -version = "0.0.0-2026-07-27-0206" +version = "0.0.0-2026-08-02-0125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "911e2a65229749eeb48a1f27d2b47a46b4fb43673f918854b065a932c86305b9" +checksum = "93f77ff8d121edb1bf2651335769a80a2f611da8573c3b498434a66b3162805f" dependencies = [ - "indexmap 1.9.3", + "indexmap", "proc-macro2", "quote", "verus_syn", @@ -1389,9 +1373,9 @@ dependencies = [ [[package]] name = "verus_syn" -version = "0.0.0-2026-07-27-0206" +version = "0.0.0-2026-08-02-0125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdcccb102ae806c65ae59653757ac28ecff328f996159757d17b5c735a7f7850" +checksum = "f17237ea6d267e457d53ce36f55b315fc9edd26eb88c765dd95e035c0b415869" dependencies = [ "proc-macro2", "quote", @@ -1406,9 +1390,9 @@ checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" [[package]] name = "vstd" -version = "0.0.0-2026-07-27-0206" +version = "0.0.0-2026-08-02-0125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c72419942f28be7f54bca5e8bc1ac207f9efd45e424788905ecb1213881950a" +checksum = "25040241d96eb88dfc6a75c289dc0ef84845ffff051d8e595cc55908d5c06a45" dependencies = [ "verus_builtin", "verus_builtin_macros", diff --git a/src/verify/number_proofs.rs b/src/verify/number_proofs.rs index 0ecab3516..41a5578af 100644 --- a/src/verify/number_proofs.rs +++ b/src/verify/number_proofs.rs @@ -23,15 +23,16 @@ use vstd::std_specs::ops::*; pub proof fn lemma_div_ensures_cases(lhs: NumberView, rhs: NumberView) ensures - forall|integer_lhs: int, divisor: int| - lhs == NumberView::Integer(integer_lhs) - && rhs == NumberView::Integer(divisor) - && divisor != 0 - && rust_rem(integer_lhs, divisor) == 0 - ==> #[trigger] lhs.div_ensures( - rhs, - NumberView::Integer(rust_div(integer_lhs, divisor)), - ), + match (lhs, rhs) { + (NumberView::Integer(integer_lhs), NumberView::Integer(divisor)) => { + divisor != 0 && rust_rem(integer_lhs, divisor) == 0 + ==> lhs.div_ensures( + rhs, + NumberView::Integer(rust_div(integer_lhs, divisor)), + ) + }, + _ => true, + }, forall|lhs_float: f64, rhs_float: f64| lhs is Integer && rhs is Integer && rhs->Integer_0 != 0 && rust_rem( lhs->Integer_0, From e1da5a839ab5d10988ac2552b27977cff9e3092e Mon Sep 17 00:00:00 2001 From: Jay Lorch Date: Tue, 4 Aug 2026 10:48:02 -0700 Subject: [PATCH 31/41] Avoid meaningless cmp_spec on int --- src/number.rs | 7 ++++++- src/verify/bigint_assumptions.rs | 12 ++++++++++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/number.rs b/src/number.rs index d89cd236b..505398547 100644 --- a/src/number.rs +++ b/src/number.rs @@ -455,7 +455,12 @@ impl Ord for Number { #[verus_spec(result => ensures match (self@.to_int(), other@.to_int()) { - (Some(n1), Some(n2)) => result == n1.cmp_spec(&n2), + (Some(n1), Some(n2)) => + match result { + Ordering::Less => n1 < n2, + Ordering::Greater => n1 > n2, + Ordering::Equal => n1 == n2, + }, _ => exists|f1: f64, f2: f64| #![trigger self@.to_f64_lossy_ensures(f1), other@.to_f64_lossy_ensures(f2)] { &&& self@.to_f64_lossy_ensures(f1) &&& other@.to_f64_lossy_ensures(f2) diff --git a/src/verify/bigint_assumptions.rs b/src/verify/bigint_assumptions.rs index cfe8cafc2..f1941766a 100644 --- a/src/verify/bigint_assumptions.rs +++ b/src/verify/bigint_assumptions.rs @@ -192,12 +192,20 @@ pub assume_specification[ ::eq ](x: &BigInt, y: pub axiom fn axiom_bigint_obeys_cmp_spec() ensures ::obeys_cmp_spec(), - forall|b1: &BigInt, b2: &BigInt| b1.cmp_spec(b2) == b1@.cmp_spec(&b2@), + forall|b1: &BigInt, b2: &BigInt| match #[trigger] b1.cmp_spec(b2) { + Ordering::Less => b1@ < b2@, + Ordering::Greater => b1@ > b2@, + Ordering::Equal => b1@ == b2@, + }, ; pub assume_specification[ ::cmp ](x: &BigInt, y: &BigInt) -> (res: Ordering) ensures - res == x@.cmp_spec(&y@), + match res { + Ordering::Less => x@ < y@, + Ordering::Greater => x@ > y@, + Ordering::Equal => x@ == y@, + }, ; // From From 790cc3dc3bcb532c25582ccdfe099856d98bf9a3 Mon Sep 17 00:00:00 2001 From: Jay Lorch Date: Tue, 4 Aug 2026 11:05:24 -0700 Subject: [PATCH 32/41] Remove test that allocates lots of memory --- src/number.rs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/number.rs b/src/number.rs index 505398547..0457bf52d 100644 --- a/src/number.rs +++ b/src/number.rs @@ -1706,14 +1706,6 @@ mod tests { ); } - #[test] - fn two_pow_computes_minimum_exponent() { - assert!(matches!( - Number::two_pow(i32::MIN), - Ok(Number::Float(value)) if value == 0.0 - )); - } - #[test] fn ten_pow_computes_negative_exponent() { assert!(matches!( From 26711e773cea3c3b7fd58bdeb1afb119c4019806 Mon Sep 17 00:00:00 2001 From: Jay Lorch Date: Wed, 5 Aug 2026 18:06:55 -0700 Subject: [PATCH 33/41] Use grounded specs for BigInt bit ops --- src/verify/bigint_assumptions.rs | 125 ++++++++++++++++++++++++++++++- 1 file changed, 122 insertions(+), 3 deletions(-) diff --git a/src/verify/bigint_assumptions.rs b/src/verify/bigint_assumptions.rs index f1941766a..a42166506 100644 --- a/src/verify/bigint_assumptions.rs +++ b/src/verify/bigint_assumptions.rs @@ -42,7 +42,46 @@ impl BigIntAdditionalSpecFns for BigInt { uninterp spec fn view(&self) -> int; } -pub uninterp spec fn spec_bigint_bitand(lhs: int, rhs: int) -> int; +// BitAnd + +pub open spec fn spec_bigint_bitand(lhs: int, rhs: int) -> int + decreases + if lhs >= 0 { lhs } else { -lhs - 1 }, + if rhs >= 0 { rhs } else { -rhs - 1 } +{ + let lsb: int = if (lhs % 2 == 1) && (rhs % 2 == 1) { 1int } else { 0int }; + if (lhs == 0 || lhs == -1) && (rhs == 0 || rhs == -1) { + -lsb + } + else { + let lhs_shifted: int = if lhs >= 0 { lhs / 2 } else { -((-lhs - 1) / 2) - 1 }; + let rhs_shifted: int = if rhs >= 0 { rhs / 2 } else { -((-rhs - 1) / 2) - 1 }; + spec_bigint_bitand(lhs_shifted, rhs_shifted) * 2 + lsb + } +} + +proof fn lemma_test_spec_bigint_bitand() +{ + // Testing examples from https://docs.rs/num-bigint/latest/src/num_bigint/bigint/bits.rs.html + + // Documentation for bitand_pos_neg + assert(spec_bigint_bitand(1, -0xff) == 1) by (compute); + assert(spec_bigint_bitand(0xff, -1) == 0xff) by (compute); + + // Documentation for bitand_neg_pos + assert(spec_bigint_bitand(-1, 0xff) == 0xff) by (compute); + assert(spec_bigint_bitand(-0xff, 1) == 1) by (compute); + + // Documentation for bitand_neg_neg + assert(spec_bigint_bitand(-1, -0xff) == -0xff) by (compute); + assert(spec_bigint_bitand(-0xff, -1) == -0xff) by (compute); + assert(spec_bigint_bitand(-0xff, -0xfe) == -0x100) by (compute); + + // Extra examples + assert(spec_bigint_bitand(-27, -9) == -27) by (compute); + assert(spec_bigint_bitand(27, 9) == 9) by (compute); + assert(spec_bigint_bitand(5, 3) == 1) by (compute); +} pub axiom fn axiom_bigint_obeys_bitand_spec() ensures @@ -54,7 +93,46 @@ pub axiom fn axiom_bigint_obeys_bitand_spec() == spec_bigint_bitand(lhs@, rhs@), ; -pub uninterp spec fn spec_bigint_bitor(lhs: int, rhs: int) -> int; +// BitOr + +pub open spec fn spec_bigint_bitor(lhs: int, rhs: int) -> int + decreases + if lhs >= 0 { lhs } else { -lhs - 1 }, + if rhs >= 0 { rhs } else { -rhs - 1 } +{ + let lsb: int = if (lhs % 2 == 1) || (rhs % 2 == 1) { 1int } else { 0int }; + if (lhs == 0 || lhs == -1) && (rhs == 0 || rhs == -1) { + -lsb + } + else { + let lhs_shifted: int = if lhs >= 0 { lhs / 2 } else { -((-lhs - 1) / 2) - 1 }; + let rhs_shifted: int = if rhs >= 0 { rhs / 2 } else { -((-rhs - 1) / 2) - 1 }; + spec_bigint_bitor(lhs_shifted, rhs_shifted) * 2 + lsb + } +} + +proof fn lemma_test_spec_bigint_bitor() +{ + // Testing examples from https://docs.rs/num-bigint/latest/src/num_bigint/bigint/bits.rs.html + + // Documentation for bitor_pos_neg + assert(spec_bigint_bitor(1, -0xff) == -0xff) by (compute); + assert(spec_bigint_bitor(0xff, -1) == -1) by (compute); + + // Documentation for bitor_neg_pos + assert(spec_bigint_bitor(-1, 0xff) == -1) by (compute); + assert(spec_bigint_bitor(-0xff, 1) == -0xff) by (compute); + + // Documentation for bitor_neg_neg + assert(spec_bigint_bitor(-1, -0xff) == -1) by (compute); + assert(spec_bigint_bitor(-0xff, -1) == -1) by (compute); + + // Extra examples + assert(spec_bigint_bitor(-0xff, -0xfe) == -0xfd) by (compute); + assert(spec_bigint_bitor(-27, -9) == -9) by (compute); + assert(spec_bigint_bitor(27, 9) == 27) by (compute); + assert(spec_bigint_bitor(5, 3) == 7) by (compute); +} pub axiom fn axiom_bigint_obeys_bitor_spec() ensures @@ -66,7 +144,46 @@ pub axiom fn axiom_bigint_obeys_bitor_spec() == spec_bigint_bitor(lhs@, rhs@), ; -pub uninterp spec fn spec_bigint_bitxor(lhs: int, rhs: int) -> int; +// BitXor + +pub open spec fn spec_bigint_bitxor(lhs: int, rhs: int) -> int + decreases + if lhs >= 0 { lhs } else { -lhs - 1 }, + if rhs >= 0 { rhs } else { -rhs - 1 } +{ + let lsb: int = if (lhs % 2 == 1) != (rhs % 2 == 1) { 1int } else { 0int }; + if (lhs == 0 || lhs == -1) && (rhs == 0 || rhs == -1) { + -lsb + } + else { + let lhs_shifted: int = if lhs >= 0 { lhs / 2 } else { -((-lhs - 1) / 2) - 1 }; + let rhs_shifted: int = if rhs >= 0 { rhs / 2 } else { -((-rhs - 1) / 2) - 1 }; + spec_bigint_bitxor(lhs_shifted, rhs_shifted) * 2 + lsb + } +} + +proof fn lemma_test_spec_bigint_bitxor() +{ + // Testing examples from https://docs.rs/num-bigint/latest/src/num_bigint/bigint/bits.rs.html + + // Documentation for bitxor_pos_neg + assert(spec_bigint_bitxor(1, -0xff) == -0x100) by (compute); + assert(spec_bigint_bitxor(0xff, -1) == -0x100) by (compute); + + // Documentation for bitxor_neg_pos + assert(spec_bigint_bitxor(-1, 0xff) == -0x100) by (compute); + assert(spec_bigint_bitxor(-0xff, 1) == -0x100) by (compute); + + // Documentation for bitxor_neg_neg + assert(spec_bigint_bitxor(-1, -0xff) == 0xfe) by (compute); + assert(spec_bigint_bitxor(-0xff, -1) == 0xfe) by (compute); + + // Extra examples + assert(spec_bigint_bitxor(-0xff, -0xfe) == 3) by (compute); + assert(spec_bigint_bitxor(-27, -9) == 18) by (compute); + assert(spec_bigint_bitxor(27, 9) == 18) by (compute); + assert(spec_bigint_bitxor(5, 3) == 6) by (compute); +} pub axiom fn axiom_bigint_obeys_bitxor_spec() ensures @@ -78,6 +195,8 @@ pub axiom fn axiom_bigint_obeys_bitxor_spec() == spec_bigint_bitxor(lhs@, rhs@), ; +// Shift + pub assume_specification[ >::shr_assign ]( value: &mut BigInt, shift: usize, From aad28022c31734800f2b513f201448fe13989f27 Mon Sep 17 00:00:00 2001 From: Jay Lorch Date: Thu, 6 Aug 2026 10:53:44 -0700 Subject: [PATCH 34/41] Prove BigInt bit-op specs equivalent to ops on i16 --- src/verify/bigint_assumptions.rs | 247 +++++++++++++++++++++++++------ 1 file changed, 203 insertions(+), 44 deletions(-) diff --git a/src/verify/bigint_assumptions.rs b/src/verify/bigint_assumptions.rs index a42166506..d82c07d8e 100644 --- a/src/verify/bigint_assumptions.rs +++ b/src/verify/bigint_assumptions.rs @@ -46,41 +46,94 @@ impl BigIntAdditionalSpecFns for BigInt { pub open spec fn spec_bigint_bitand(lhs: int, rhs: int) -> int decreases - if lhs >= 0 { lhs } else { -lhs - 1 }, - if rhs >= 0 { rhs } else { -rhs - 1 } + if lhs >= 0 { lhs } else { -(lhs + 1) }, + if rhs >= 0 { rhs } else { -(rhs + 1) } { let lsb: int = if (lhs % 2 == 1) && (rhs % 2 == 1) { 1int } else { 0int }; if (lhs == 0 || lhs == -1) && (rhs == 0 || rhs == -1) { -lsb } else { - let lhs_shifted: int = if lhs >= 0 { lhs / 2 } else { -((-lhs - 1) / 2) - 1 }; - let rhs_shifted: int = if rhs >= 0 { rhs / 2 } else { -((-rhs - 1) / 2) - 1 }; + let lhs_shifted: int = if lhs >= 0 { lhs / 2 } else { -((-(lhs + 1)) / 2) - 1 }; + let rhs_shifted: int = if rhs >= 0 { rhs / 2 } else { -((-(rhs + 1)) / 2) - 1 }; spec_bigint_bitand(lhs_shifted, rhs_shifted) * 2 + lsb } } -proof fn lemma_test_spec_bigint_bitand() +// To help demonstrate that the spec for `spec_bigint_bitand` is +// valid, prove that it's equivalent to `&` for arbitrary `i16` +// values. (Using 16 bits is enough for high confidence, and doesn't +// tax the bit-vector solver.) +proof fn lemma_test_spec_bigint_bitand_for_i16(lhs: i16, rhs: i16) + ensures + spec_bigint_bitand(lhs as int, rhs as int) == (lhs & rhs) as int, + decreases + if lhs >= 0 { lhs as int } else { -(lhs + 1) }, + if rhs >= 0 { rhs as int } else { -(rhs + 1) } { - // Testing examples from https://docs.rs/num-bigint/latest/src/num_bigint/bigint/bits.rs.html + let lsb: i16 = if (lhs % 2 == 1) && (rhs % 2 == 1) { 1i16 } else { 0i16 }; + if (lhs == 0 || lhs == -1) && (rhs == 0 || rhs == -1) { + assert(-lsb == lhs & rhs) by (bit_vector) + requires + lhs == 0 || lhs == -1, + rhs == 0 || rhs == -1, + lsb == if (lhs % 2 == 1) && (rhs % 2 == 1) { 1i16 } else { 0i16 }, + ; + } + else { + let lhs_shifted: i16 = + if lhs >= 0 { + (lhs / 2) as i16 + } + else { + -(((((-((lhs + 1) as i16) as i16) / 2) as i16) + 1) as i16) as i16 + }; + let rhs_shifted: i16 = + if rhs >= 0 { + (rhs / 2) as i16 + } + else { + -(((((-((rhs + 1) as i16) as i16) / 2) as i16) + 1) as i16) as i16 + }; + + lemma_test_spec_bigint_bitand_for_i16(lhs_shifted, rhs_shifted); + assert((lhs_shifted & rhs_shifted) * 2 + lsb == lhs & rhs) by (bit_vector) + requires + lhs >= 0 ==> lhs_shifted == (lhs / 2) as i16, + lhs < 0 ==> lhs_shifted == -(((((-((lhs + 1) as i16) as i16) / 2) as i16) + 1) as i16) as i16, + rhs >= 0 ==> rhs_shifted == (rhs / 2) as i16, + rhs < 0 ==> rhs_shifted == -(((((-((rhs + 1) as i16) as i16) / 2) as i16) + 1) as i16) as i16, + lsb == if (lhs % 2 == 1) && (rhs % 2 == 1) { 1i16 } else { 0i16 }, + ; + } +} - // Documentation for bitand_pos_neg +// To help demonstrate that the spec for `spec_bigint_bitand` +// corresponds to what's implemented by the BigInt library, prove that +// its results match examples given in comments at: +// https://docs.rs/num-bigint/latest/src/num_bigint/bigint/bits.rs.html +proof fn lemma_test_spec_bigint_bitand_with_examples() + ensures + // From documentation for bitand_pos_neg: + spec_bigint_bitand(1, -0xff) == 1, + spec_bigint_bitand(0xff, -1) == 0xff, + // From documentation for bitand_neg_pos: + spec_bigint_bitand(-1, 0xff) == 0xff, + spec_bigint_bitand(-0xff, 1) == 1, + // From documentation for bitand_neg_neg: + spec_bigint_bitand(-1, -0xff) == -0xff, + spec_bigint_bitand(-0xff, -1) == -0xff, + spec_bigint_bitand(-0xff, -0xfe) == -0x100, +{ assert(spec_bigint_bitand(1, -0xff) == 1) by (compute); assert(spec_bigint_bitand(0xff, -1) == 0xff) by (compute); - // Documentation for bitand_neg_pos assert(spec_bigint_bitand(-1, 0xff) == 0xff) by (compute); assert(spec_bigint_bitand(-0xff, 1) == 1) by (compute); - // Documentation for bitand_neg_neg assert(spec_bigint_bitand(-1, -0xff) == -0xff) by (compute); assert(spec_bigint_bitand(-0xff, -1) == -0xff) by (compute); assert(spec_bigint_bitand(-0xff, -0xfe) == -0x100) by (compute); - - // Extra examples - assert(spec_bigint_bitand(-27, -9) == -27) by (compute); - assert(spec_bigint_bitand(27, 9) == 9) by (compute); - assert(spec_bigint_bitand(5, 3) == 1) by (compute); } pub axiom fn axiom_bigint_obeys_bitand_spec() @@ -97,41 +150,94 @@ pub axiom fn axiom_bigint_obeys_bitand_spec() pub open spec fn spec_bigint_bitor(lhs: int, rhs: int) -> int decreases - if lhs >= 0 { lhs } else { -lhs - 1 }, - if rhs >= 0 { rhs } else { -rhs - 1 } + if lhs >= 0 { lhs } else { -(lhs + 1) }, + if rhs >= 0 { rhs } else { -(rhs + 1) } { let lsb: int = if (lhs % 2 == 1) || (rhs % 2 == 1) { 1int } else { 0int }; if (lhs == 0 || lhs == -1) && (rhs == 0 || rhs == -1) { -lsb } else { - let lhs_shifted: int = if lhs >= 0 { lhs / 2 } else { -((-lhs - 1) / 2) - 1 }; - let rhs_shifted: int = if rhs >= 0 { rhs / 2 } else { -((-rhs - 1) / 2) - 1 }; + let lhs_shifted: int = if lhs >= 0 { lhs / 2 } else { -((-(lhs + 1)) / 2) - 1 }; + let rhs_shifted: int = if rhs >= 0 { rhs / 2 } else { -((-(rhs + 1)) / 2) - 1 }; spec_bigint_bitor(lhs_shifted, rhs_shifted) * 2 + lsb } } -proof fn lemma_test_spec_bigint_bitor() +// To help demonstrate that the spec for `spec_bigint_bitor` is +// valid, prove that it's equivalent to `|` for arbitrary `i16` +// values. (Using 16 bits is enough for high confidence, and doesn't +// tax the bit-vector solver.) +proof fn lemma_test_spec_bigint_bitor_for_i16(lhs: i16, rhs: i16) + ensures + spec_bigint_bitor(lhs as int, rhs as int) == (lhs | rhs) as int, + decreases + if lhs >= 0 { lhs as int } else { -(lhs + 1) }, + if rhs >= 0 { rhs as int } else { -(rhs + 1) } { - // Testing examples from https://docs.rs/num-bigint/latest/src/num_bigint/bigint/bits.rs.html + let lsb: i16 = if (lhs % 2 == 1) || (rhs % 2 == 1) { 1i16 } else { 0i16 }; + if (lhs == 0 || lhs == -1) && (rhs == 0 || rhs == -1) { + assert(-lsb == lhs | rhs) by (bit_vector) + requires + lhs == 0 || lhs == -1, + rhs == 0 || rhs == -1, + lsb == if (lhs % 2 == 1) || (rhs % 2 == 1) { 1i16 } else { 0i16 }, + ; + } + else { + let lhs_shifted: i16 = + if lhs >= 0 { + (lhs / 2) as i16 + } + else { + -(((((-((lhs + 1) as i16) as i16) / 2) as i16) + 1) as i16) as i16 + }; + let rhs_shifted: i16 = + if rhs >= 0 { + (rhs / 2) as i16 + } + else { + -(((((-((rhs + 1) as i16) as i16) / 2) as i16) + 1) as i16) as i16 + }; + + lemma_test_spec_bigint_bitor_for_i16(lhs_shifted, rhs_shifted); + assert((lhs_shifted | rhs_shifted) * 2 + lsb == lhs | rhs) by (bit_vector) + requires + lhs >= 0 ==> lhs_shifted == (lhs / 2) as i16, + lhs < 0 ==> lhs_shifted == -(((((-((lhs + 1) as i16) as i16) / 2) as i16) + 1) as i16) as i16, + rhs >= 0 ==> rhs_shifted == (rhs / 2) as i16, + rhs < 0 ==> rhs_shifted == -(((((-((rhs + 1) as i16) as i16) / 2) as i16) + 1) as i16) as i16, + lsb == if (lhs % 2 == 1) || (rhs % 2 == 1) { 1i16 } else { 0i16 }, + ; + } +} - // Documentation for bitor_pos_neg +// To help demonstrate that the spec for `spec_bigint_bitor` +// corresponds to what's implemented by the BigInt library, prove that +// its results match examples given in comments at: +// https://docs.rs/num-bigint/latest/src/num_bigint/bigint/bits.rs.html +proof fn lemma_test_spec_bigint_bitor_with_examples() + ensures + // From documentation for bitor_pos_neg: + spec_bigint_bitor(1, -0xff) == -0xff, + spec_bigint_bitor(0xff, -1) == -1, + + // From documentation for bitor_neg_pos: + spec_bigint_bitor(-1, 0xff) == -1, + spec_bigint_bitor(-0xff, 1) == -0xff, + + // From documentation for bitor_neg_neg: + spec_bigint_bitor(-1, -0xff) == -1, + spec_bigint_bitor(-0xff, -1) == -1, +{ assert(spec_bigint_bitor(1, -0xff) == -0xff) by (compute); assert(spec_bigint_bitor(0xff, -1) == -1) by (compute); - // Documentation for bitor_neg_pos assert(spec_bigint_bitor(-1, 0xff) == -1) by (compute); assert(spec_bigint_bitor(-0xff, 1) == -0xff) by (compute); - // Documentation for bitor_neg_neg assert(spec_bigint_bitor(-1, -0xff) == -1) by (compute); assert(spec_bigint_bitor(-0xff, -1) == -1) by (compute); - - // Extra examples - assert(spec_bigint_bitor(-0xff, -0xfe) == -0xfd) by (compute); - assert(spec_bigint_bitor(-27, -9) == -9) by (compute); - assert(spec_bigint_bitor(27, 9) == 27) by (compute); - assert(spec_bigint_bitor(5, 3) == 7) by (compute); } pub axiom fn axiom_bigint_obeys_bitor_spec() @@ -148,41 +254,94 @@ pub axiom fn axiom_bigint_obeys_bitor_spec() pub open spec fn spec_bigint_bitxor(lhs: int, rhs: int) -> int decreases - if lhs >= 0 { lhs } else { -lhs - 1 }, - if rhs >= 0 { rhs } else { -rhs - 1 } + if lhs >= 0 { lhs } else { -(lhs + 1) }, + if rhs >= 0 { rhs } else { -(rhs + 1) } { let lsb: int = if (lhs % 2 == 1) != (rhs % 2 == 1) { 1int } else { 0int }; if (lhs == 0 || lhs == -1) && (rhs == 0 || rhs == -1) { -lsb } else { - let lhs_shifted: int = if lhs >= 0 { lhs / 2 } else { -((-lhs - 1) / 2) - 1 }; - let rhs_shifted: int = if rhs >= 0 { rhs / 2 } else { -((-rhs - 1) / 2) - 1 }; + let lhs_shifted: int = if lhs >= 0 { lhs / 2 } else { -((-(lhs + 1)) / 2) - 1 }; + let rhs_shifted: int = if rhs >= 0 { rhs / 2 } else { -((-(rhs + 1)) / 2) - 1 }; spec_bigint_bitxor(lhs_shifted, rhs_shifted) * 2 + lsb } } -proof fn lemma_test_spec_bigint_bitxor() +// To help demonstrate that the spec for `spec_bigint_bitxor` is +// valid, prove that it's equivalent to `^` for arbitrary `i16` +// values. (Using 16 bits is enough for high confidence, and doesn't +// tax the bit-vector solver.) +proof fn lemma_test_spec_bigint_bitxor_for_i16(lhs: i16, rhs: i16) + ensures + spec_bigint_bitxor(lhs as int, rhs as int) == (lhs ^ rhs) as int, + decreases + if lhs >= 0 { lhs as int } else { -(lhs + 1) }, + if rhs >= 0 { rhs as int } else { -(rhs + 1) } { - // Testing examples from https://docs.rs/num-bigint/latest/src/num_bigint/bigint/bits.rs.html + let lsb: i16 = if (lhs % 2 == 1) != (rhs % 2 == 1) { 1i16 } else { 0i16 }; + if (lhs == 0 || lhs == -1) && (rhs == 0 || rhs == -1) { + assert(-lsb == lhs ^ rhs) by (bit_vector) + requires + lhs == 0 || lhs == -1, + rhs == 0 || rhs == -1, + lsb == if (lhs % 2 == 1) != (rhs % 2 == 1) { 1i16 } else { 0i16 }, + ; + } + else { + let lhs_shifted: i16 = + if lhs >= 0 { + (lhs / 2) as i16 + } + else { + -(((((-((lhs + 1) as i16) as i16) / 2) as i16) + 1) as i16) as i16 + }; + let rhs_shifted: i16 = + if rhs >= 0 { + (rhs / 2) as i16 + } + else { + -(((((-((rhs + 1) as i16) as i16) / 2) as i16) + 1) as i16) as i16 + }; + + lemma_test_spec_bigint_bitxor_for_i16(lhs_shifted, rhs_shifted); + assert((lhs_shifted ^ rhs_shifted) * 2 + lsb == lhs ^ rhs) by (bit_vector) + requires + lhs >= 0 ==> lhs_shifted == (lhs / 2) as i16, + lhs < 0 ==> lhs_shifted == -(((((-((lhs + 1) as i16) as i16) / 2) as i16) + 1) as i16) as i16, + rhs >= 0 ==> rhs_shifted == (rhs / 2) as i16, + rhs < 0 ==> rhs_shifted == -(((((-((rhs + 1) as i16) as i16) / 2) as i16) + 1) as i16) as i16, + lsb == if (lhs % 2 == 1) != (rhs % 2 == 1) { 1i16 } else { 0i16 }, + ; + } +} + +// To help demonstrate that the spec for `spec_bigint_bitxor` +// corresponds to what's implemented by the BigInt library, prove that +// its results match examples given in comments at: +// https://docs.rs/num-bigint/latest/src/num_bigint/bigint/bits.rs.html +proof fn lemma_test_spec_bigint_bitxor_with_examples() + ensures + // From documentation for bitxor_pos_neg: + spec_bigint_bitxor(1, -0xff) == -0x100, + spec_bigint_bitxor(0xff, -1) == -0x100, + + // From documentation for bitxor_neg_pos: + spec_bigint_bitxor(-1, 0xff) == -0x100, + spec_bigint_bitxor(-0xff, 1) == -0x100, - // Documentation for bitxor_pos_neg + // From documentation for bitxor_neg_neg: + spec_bigint_bitxor(-1, -0xff) == 0xfe, + spec_bigint_bitxor(-0xff, -1) == 0xfe, +{ assert(spec_bigint_bitxor(1, -0xff) == -0x100) by (compute); assert(spec_bigint_bitxor(0xff, -1) == -0x100) by (compute); - // Documentation for bitxor_neg_pos assert(spec_bigint_bitxor(-1, 0xff) == -0x100) by (compute); assert(spec_bigint_bitxor(-0xff, 1) == -0x100) by (compute); - // Documentation for bitxor_neg_neg assert(spec_bigint_bitxor(-1, -0xff) == 0xfe) by (compute); assert(spec_bigint_bitxor(-0xff, -1) == 0xfe) by (compute); - - // Extra examples - assert(spec_bigint_bitxor(-0xff, -0xfe) == 3) by (compute); - assert(spec_bigint_bitxor(-27, -9) == 18) by (compute); - assert(spec_bigint_bitxor(27, 9) == 18) by (compute); - assert(spec_bigint_bitxor(5, 3) == 6) by (compute); } pub axiom fn axiom_bigint_obeys_bitxor_spec() From 9a1a90b1fa7f0e84c457be40a9e867bc048474cd Mon Sep 17 00:00:00 2001 From: Jay Lorch Date: Thu, 6 Aug 2026 11:27:53 -0700 Subject: [PATCH 35/41] Simplify BigInt bit op specs --- src/verify/bigint_assumptions.rs | 81 ++++---------------------------- 1 file changed, 9 insertions(+), 72 deletions(-) diff --git a/src/verify/bigint_assumptions.rs b/src/verify/bigint_assumptions.rs index d82c07d8e..eca189b2f 100644 --- a/src/verify/bigint_assumptions.rs +++ b/src/verify/bigint_assumptions.rs @@ -54,9 +54,7 @@ pub open spec fn spec_bigint_bitand(lhs: int, rhs: int) -> int -lsb } else { - let lhs_shifted: int = if lhs >= 0 { lhs / 2 } else { -((-(lhs + 1)) / 2) - 1 }; - let rhs_shifted: int = if rhs >= 0 { rhs / 2 } else { -((-(rhs + 1)) / 2) - 1 }; - spec_bigint_bitand(lhs_shifted, rhs_shifted) * 2 + lsb + spec_bigint_bitand(lhs / 2, rhs / 2) * 2 + lsb } } @@ -81,28 +79,9 @@ proof fn lemma_test_spec_bigint_bitand_for_i16(lhs: i16, rhs: i16) ; } else { - let lhs_shifted: i16 = - if lhs >= 0 { - (lhs / 2) as i16 - } - else { - -(((((-((lhs + 1) as i16) as i16) / 2) as i16) + 1) as i16) as i16 - }; - let rhs_shifted: i16 = - if rhs >= 0 { - (rhs / 2) as i16 - } - else { - -(((((-((rhs + 1) as i16) as i16) / 2) as i16) + 1) as i16) as i16 - }; - - lemma_test_spec_bigint_bitand_for_i16(lhs_shifted, rhs_shifted); - assert((lhs_shifted & rhs_shifted) * 2 + lsb == lhs & rhs) by (bit_vector) + lemma_test_spec_bigint_bitand_for_i16((lhs / 2) as i16, (rhs / 2) as i16); + assert(((lhs / 2) as i16 & (rhs / 2) as i16) * 2 + lsb == lhs & rhs) by (bit_vector) requires - lhs >= 0 ==> lhs_shifted == (lhs / 2) as i16, - lhs < 0 ==> lhs_shifted == -(((((-((lhs + 1) as i16) as i16) / 2) as i16) + 1) as i16) as i16, - rhs >= 0 ==> rhs_shifted == (rhs / 2) as i16, - rhs < 0 ==> rhs_shifted == -(((((-((rhs + 1) as i16) as i16) / 2) as i16) + 1) as i16) as i16, lsb == if (lhs % 2 == 1) && (rhs % 2 == 1) { 1i16 } else { 0i16 }, ; } @@ -158,9 +137,7 @@ pub open spec fn spec_bigint_bitor(lhs: int, rhs: int) -> int -lsb } else { - let lhs_shifted: int = if lhs >= 0 { lhs / 2 } else { -((-(lhs + 1)) / 2) - 1 }; - let rhs_shifted: int = if rhs >= 0 { rhs / 2 } else { -((-(rhs + 1)) / 2) - 1 }; - spec_bigint_bitor(lhs_shifted, rhs_shifted) * 2 + lsb + spec_bigint_bitor(lhs / 2, rhs / 2) * 2 + lsb } } @@ -185,28 +162,9 @@ proof fn lemma_test_spec_bigint_bitor_for_i16(lhs: i16, rhs: i16) ; } else { - let lhs_shifted: i16 = - if lhs >= 0 { - (lhs / 2) as i16 - } - else { - -(((((-((lhs + 1) as i16) as i16) / 2) as i16) + 1) as i16) as i16 - }; - let rhs_shifted: i16 = - if rhs >= 0 { - (rhs / 2) as i16 - } - else { - -(((((-((rhs + 1) as i16) as i16) / 2) as i16) + 1) as i16) as i16 - }; - - lemma_test_spec_bigint_bitor_for_i16(lhs_shifted, rhs_shifted); - assert((lhs_shifted | rhs_shifted) * 2 + lsb == lhs | rhs) by (bit_vector) + lemma_test_spec_bigint_bitor_for_i16((lhs / 2) as i16, (rhs / 2) as i16); + assert(((lhs / 2) as i16 | (rhs / 2) as i16) * 2 + lsb == lhs | rhs) by (bit_vector) requires - lhs >= 0 ==> lhs_shifted == (lhs / 2) as i16, - lhs < 0 ==> lhs_shifted == -(((((-((lhs + 1) as i16) as i16) / 2) as i16) + 1) as i16) as i16, - rhs >= 0 ==> rhs_shifted == (rhs / 2) as i16, - rhs < 0 ==> rhs_shifted == -(((((-((rhs + 1) as i16) as i16) / 2) as i16) + 1) as i16) as i16, lsb == if (lhs % 2 == 1) || (rhs % 2 == 1) { 1i16 } else { 0i16 }, ; } @@ -262,9 +220,7 @@ pub open spec fn spec_bigint_bitxor(lhs: int, rhs: int) -> int -lsb } else { - let lhs_shifted: int = if lhs >= 0 { lhs / 2 } else { -((-(lhs + 1)) / 2) - 1 }; - let rhs_shifted: int = if rhs >= 0 { rhs / 2 } else { -((-(rhs + 1)) / 2) - 1 }; - spec_bigint_bitxor(lhs_shifted, rhs_shifted) * 2 + lsb + spec_bigint_bitxor(lhs / 2, rhs / 2) * 2 + lsb } } @@ -289,28 +245,9 @@ proof fn lemma_test_spec_bigint_bitxor_for_i16(lhs: i16, rhs: i16) ; } else { - let lhs_shifted: i16 = - if lhs >= 0 { - (lhs / 2) as i16 - } - else { - -(((((-((lhs + 1) as i16) as i16) / 2) as i16) + 1) as i16) as i16 - }; - let rhs_shifted: i16 = - if rhs >= 0 { - (rhs / 2) as i16 - } - else { - -(((((-((rhs + 1) as i16) as i16) / 2) as i16) + 1) as i16) as i16 - }; - - lemma_test_spec_bigint_bitxor_for_i16(lhs_shifted, rhs_shifted); - assert((lhs_shifted ^ rhs_shifted) * 2 + lsb == lhs ^ rhs) by (bit_vector) + lemma_test_spec_bigint_bitxor_for_i16((lhs / 2) as i16, (rhs / 2) as i16); + assert(((lhs / 2) as i16 ^ (rhs / 2) as i16) * 2 + lsb == lhs ^ rhs) by (bit_vector) requires - lhs >= 0 ==> lhs_shifted == (lhs / 2) as i16, - lhs < 0 ==> lhs_shifted == -(((((-((lhs + 1) as i16) as i16) / 2) as i16) + 1) as i16) as i16, - rhs >= 0 ==> rhs_shifted == (rhs / 2) as i16, - rhs < 0 ==> rhs_shifted == -(((((-((rhs + 1) as i16) as i16) / 2) as i16) + 1) as i16) as i16, lsb == if (lhs % 2 == 1) != (rhs % 2 == 1) { 1i16 } else { 0i16 }, ; } From f17124f3ac088aa258ba4379c57800186b0b8700 Mon Sep 17 00:00:00 2001 From: Jay Lorch Date: Fri, 7 Aug 2026 17:59:40 -0700 Subject: [PATCH 36/41] Improve consistency and documentation of BigInt assumptions --- src/number.rs | 12 +- src/verify/bigint_assumptions.rs | 733 ++++++++----------------------- 2 files changed, 201 insertions(+), 544 deletions(-) diff --git a/src/number.rs b/src/number.rs index 0457bf52d..3f0d66e87 100644 --- a/src/number.rs +++ b/src/number.rs @@ -770,7 +770,7 @@ impl Number { proof! { axiom_f64_ops_deterministic(); axiom_bigint_obeys_add_spec(); - axiom_bigint_add_assign_req(); + axiom_bigint_obeys_add_assign_spec(); } match (self, rhs) { @@ -827,7 +827,7 @@ impl Number { proof! { axiom_f64_ops_deterministic(); axiom_bigint_obeys_sub_spec(); - axiom_bigint_sub_assign_req(); + axiom_bigint_obeys_sub_assign_spec(); } match (self, rhs) { @@ -1206,7 +1206,7 @@ impl Number { }, )] pub fn lsh(&self, rhs: &Self) -> Option { - proof! { axiom_bigint_shl_assign_req(); } + proof! { axiom_bigint_obeys_shl_assign_spec(); } let shift = rhs.as_u32()? as usize; let mut value = self.ensure_integer()?; value <<= shift; @@ -1229,7 +1229,7 @@ impl Number { }, )] pub fn rsh(&self, rhs: &Self) -> Option { - proof! { axiom_bigint_shr_assign_req(); } + proof! { axiom_bigint_obeys_shr_assign_spec(); } let shift = rhs.as_u32()? as usize; let mut value = self.ensure_integer()?; value >>= shift; @@ -1475,7 +1475,7 @@ fn two_pow_positive(exp: u32) -> Number { Number::UInt(1u64 << exp) } else { let mut value = BigInt::one(); - proof! { axiom_bigint_shl_assign_req(); } + proof! { axiom_bigint_obeys_shl_assign_spec(); } value <<= exp as usize; Number::from_bigint_owned(value) } @@ -1507,7 +1507,7 @@ fn pow10_bigint(exp: u32) -> BigInt { while e > 0 { proof! { axiom_bigint_obeys_mul_spec(); - axiom_bigint_mul_assign_ref_req(); + axiom_bigint_obeys_mul_assign_ref_spec(); assert(e & 1 == e % 2) by (bit_vector); assert(e >> 1 == e / 2) by (bit_vector); // `pow(base, 2) == base * base`, so squaring `base` halves `e`. diff --git a/src/verify/bigint_assumptions.rs b/src/verify/bigint_assumptions.rs index eca189b2f..37878c7de 100644 --- a/src/verify/bigint_assumptions.rs +++ b/src/verify/bigint_assumptions.rs @@ -29,10 +29,7 @@ use vstd::std_specs::cmp::OrdSpec; #[verifier::external_body] pub struct ExNumBigInt(num_bigint::BigInt); -pub assume_specification[ ::clone ](n: &BigInt) -> (res: BigInt) - ensures - res == n, -; +/// A `BigInt` is abstracted as an `int`. pub trait BigIntAdditionalSpecFns { spec fn view(&self) -> int; @@ -42,8 +39,125 @@ impl BigIntAdditionalSpecFns for BigInt { uninterp spec fn view(&self) -> int; } -// BitAnd +/// Semantics for BigInt::Clone + +pub assume_specification[ ::clone ](n: &BigInt) -> (res: BigInt) + ensures + res == n, +; + +/// Addition + +// This axiom says that, for any pair of `BigInt`s `a` and `b`, +// `(a + b)@ == a@ + b@. +pub axiom fn axiom_bigint_obeys_add_spec() + ensures + ::obeys_add_spec(), + forall|lhs: BigInt, rhs: BigInt| #[trigger] + ::add_req(lhs, rhs), + forall|lhs: BigInt, rhs: BigInt| #[trigger] + ::add_spec(lhs, rhs)@ + == lhs@ + rhs@, +; + +// This axiom says that, for any pair of `BigInt`s `a` and `b`, +// `a += b` causes the resulting `a@` to be the old value of `a@` plus `b@`. +pub axiom fn axiom_bigint_obeys_add_assign_spec() + ensures + >::obeys_add_assign_spec(), + forall|value: BigInt, rhs: BigInt| #[trigger] + >::add_assign_req(&value, rhs), + forall|value: BigInt, rhs: BigInt| #[trigger] + >::add_assign_spec(&value, rhs)@ == + value@ + rhs@, +; + +/// Subtraction +// This axiom says that, for any pair of `BigInt`s `a` and `b`, +// `(a - b)@ == a@ - b@. +pub axiom fn axiom_bigint_obeys_sub_spec() + ensures + ::obeys_sub_spec(), + forall|lhs: BigInt, rhs: BigInt| #[trigger] + ::sub_req(lhs, rhs), + forall|lhs: BigInt, rhs: BigInt| #[trigger] + ::sub_spec(lhs, rhs)@ + == lhs@ - rhs@, +; + +// This axiom says that, for any pair of `BigInt`s `a` and `b`, +// `a -= b` causes the resulting `a@` to be the old value of `a@` minus `b@`. +pub axiom fn axiom_bigint_obeys_sub_assign_spec() + ensures + >::obeys_sub_assign_spec(), + forall|value: BigInt, rhs: BigInt| #[trigger] + >::sub_assign_req(&value, rhs), + forall|value: BigInt, rhs: BigInt| #[trigger] + >::sub_assign_spec(&value, rhs)@ == + value@ - rhs@, +; + +/// Multiplication + +// This axiom says that, for any pair of `BigInt`s `a` and `b`, +// `(a * b)@ == a@ * b@. +pub axiom fn axiom_bigint_obeys_mul_spec() + ensures + ::obeys_mul_spec(), + forall|lhs: BigInt, rhs: BigInt| #[trigger] + ::mul_req(lhs, rhs), + forall|lhs: BigInt, rhs: BigInt| #[trigger] + ::mul_spec(lhs, rhs)@ + == lhs@ * rhs@, +; + +// This axiom says that, for any pair of `BigInt`s `a` and `b`, +// `a *= b` causes the resulting `a@` to be the old value of `a@` times `b@`. +pub axiom fn axiom_bigint_obeys_mul_assign_spec() + ensures + >::obeys_mul_assign_spec(), + forall|value: BigInt, rhs: BigInt| #[trigger] + >::mul_assign_req(&value, rhs), + forall|value: BigInt, rhs: BigInt| #[trigger] + >::mul_assign_spec(&value, rhs)@ == + value@ * rhs@, +; + +pub axiom fn axiom_bigint_obeys_mul_assign_ref_spec() + ensures + >::obeys_mul_assign_spec(), + forall|value: BigInt, rhs: &BigInt| #[trigger] + >::mul_assign_req(&value, rhs), + forall|value: BigInt, rhs: &BigInt| #[trigger] + >::mul_assign_spec(&value, rhs)@ == + value@ * (*rhs)@, +; + +/// Division + +// This axiom says that, for any pair of `BigInt`s `a` and `b`, +// `(a / b)@ == rust_div(a@, b@), and `(a % b)@ == rust_rem(a@, b@)`. +pub axiom fn axiom_bigint_obeys_div_rem_spec() + ensures + ::obeys_div_spec(), + ::obeys_rem_spec(), + forall|lhs: BigInt, rhs: BigInt| rhs@ != 0 ==> #[trigger] + ::div_req(lhs, rhs), + forall|lhs: BigInt, rhs: BigInt| rhs@ != 0 ==> #[trigger] + ::rem_req(lhs, rhs), + forall|lhs: BigInt, rhs: BigInt| rhs@ != 0 ==> #[trigger] + ::div_spec(lhs, rhs)@ + == rust_div(lhs@, rhs@), + forall|lhs: BigInt, rhs: BigInt| rhs@ != 0 ==> #[trigger] + ::rem_spec(lhs, rhs)@ + == rust_rem(lhs@, rhs@), +; + +/// Bitwise AND + +// This function describes the result of performing a bitwise AND +// operation on two unbounded-precision integers. pub open spec fn spec_bigint_bitand(lhs: int, rhs: int) -> int decreases if lhs >= 0 { lhs } else { -(lhs + 1) }, @@ -115,6 +229,10 @@ proof fn lemma_test_spec_bigint_bitand_with_examples() assert(spec_bigint_bitand(-0xff, -0xfe) == -0x100) by (compute); } +// This axiom says that, for any pair of `BigInt`s `a` and `b`, +// (a & b)@ == spec_bigint_bitand(a@, b@). It's justified by the +// lemmas above named `lemma_test_spec_bigint_bitand_for_i16` +// and `lemma_test_spec_bigint_bitand_with_examples`. pub axiom fn axiom_bigint_obeys_bitand_spec() ensures ::obeys_bitand_spec(), @@ -125,8 +243,10 @@ pub axiom fn axiom_bigint_obeys_bitand_spec() == spec_bigint_bitand(lhs@, rhs@), ; -// BitOr +/// Bitwise OR +// This function describes the result of performing a bitwise OR +// operation on two unbounded-precision integers. pub open spec fn spec_bigint_bitor(lhs: int, rhs: int) -> int decreases if lhs >= 0 { lhs } else { -(lhs + 1) }, @@ -198,6 +318,10 @@ proof fn lemma_test_spec_bigint_bitor_with_examples() assert(spec_bigint_bitor(-0xff, -1) == -1) by (compute); } +// This axiom says that, for any pair of `BigInt`s `a` and `b`, +// (a | b)@ == spec_bigint_bitor(a@, b@). It's justified by the +// lemmas above named `lemma_test_spec_bigint_bitor_for_i16` +// and `lemma_test_spec_bigint_bitor_with_examples`. pub axiom fn axiom_bigint_obeys_bitor_spec() ensures ::obeys_bitor_spec(), @@ -208,8 +332,10 @@ pub axiom fn axiom_bigint_obeys_bitor_spec() == spec_bigint_bitor(lhs@, rhs@), ; -// BitXor +/// Bitwise XOR +// This function describes the result of performing a bitwise XOR +// operation on two unbounded-precision integers. pub open spec fn spec_bigint_bitxor(lhs: int, rhs: int) -> int decreases if lhs >= 0 { lhs } else { -(lhs + 1) }, @@ -281,6 +407,10 @@ proof fn lemma_test_spec_bigint_bitxor_with_examples() assert(spec_bigint_bitxor(-0xff, -1) == 0xfe) by (compute); } +// This axiom says that, for any pair of `BigInt`s `a` and `b`, +// (a | b)@ == spec_bigint_bitxor(a@, b@). It's justified by the +// lemmas above named `lemma_test_spec_bigint_bitxor_for_i16` +// and `lemma_test_spec_bigint_bitxor_with_examples`. pub axiom fn axiom_bigint_obeys_bitxor_spec() ensures ::obeys_bitxor_spec(), @@ -291,66 +421,71 @@ pub axiom fn axiom_bigint_obeys_bitxor_spec() == spec_bigint_bitxor(lhs@, rhs@), ; -// Shift +/// Bitwise NOT -pub assume_specification[ >::shr_assign ]( - value: &mut BigInt, - shift: usize, -) +// This axiom says that, for `BigInt` `a`, `(~a)@ == -a@ - 1`. +// This corresponds to twos-complement bitwise negation. +pub axiom fn axiom_bigint_not_spec(value: BigInt) ensures - (*final(value))@ == (*old(value))@ / (pow2(shift as nat) as int), + ::obeys_not_spec(), + ::not_req(value), + ::not_spec(value)@ == -(value@) - 1, ; -pub assume_specification[ >::shl_assign ]( - value: &mut BigInt, - shift: usize, -) - ensures - (*final(value))@ == (*old(value))@ * (pow2(shift as nat) as int), -; +/// Bitwise shifting -// vstd's op-assignment traits carry an uninterpreted precondition that each -// implementation is free to choose. `num_bigint` imposes no preconditions on -// its op-assignment operators, so they always hold. -pub axiom fn axiom_bigint_shr_assign_req() +// This axiom says that BigInt supports the expected semantics for +// core::ops::ShrAssign, i.e., `>>=`. That is, for any BigInt 'a' and +// any shift amount `u: usize`, `a >>= u` causes the resulting `a@` to +// be the old `a@` shifted right by `u` bits. +pub axiom fn axiom_bigint_obeys_shr_assign_spec() ensures + >::obeys_shr_assign_spec(), forall|value: BigInt, shift: usize| #[trigger] >::shr_assign_req(&value, shift), + forall|value: BigInt, shift: usize| #[trigger] + >::shr_assign_spec(&value, shift)@ == + value@ / pow2(shift as nat) as int, ; -pub axiom fn axiom_bigint_shl_assign_req() +// This axiom says that BigInt supports the expected semantics for +// core::ops::ShlAssign, i.e., `<<=`. That is, for any BigInt 'a' and +// any shift amount `u: usize`, `a <<= u` causes the resulting `a@` to +// be the old `a@` shifted left by `u` bits. +pub axiom fn axiom_bigint_obeys_shl_assign_spec() ensures + >::obeys_shl_assign_spec(), forall|value: BigInt, shift: usize| #[trigger] >::shl_assign_req(&value, shift), + forall|value: BigInt, shift: usize| #[trigger] + >::shl_assign_spec(&value, shift)@ == + value@ * pow2(shift as nat) as int, ; -pub axiom fn axiom_bigint_add_assign_req() - ensures - forall|value: BigInt, rhs: BigInt| #[trigger] - >::add_assign_req(&value, rhs), -; +/// Unary operations -pub axiom fn axiom_bigint_sub_assign_req() +// We assume that `x.is_zero()` gives the same result as `x@ == 0`. +pub assume_specification[ ::is_zero ](x: &BigInt) -> (res: bool) ensures - forall|value: BigInt, rhs: BigInt| #[trigger] - >::sub_assign_req(&value, rhs), + res == (x@ == 0), ; -pub axiom fn axiom_bigint_mul_assign_ref_req() +// We assume that `x.is_negative()` gives the same result as `x@ < 0`. +pub assume_specification[ ::is_negative ](x: &BigInt) -> (res: bool) ensures - forall|value: BigInt, rhs: &BigInt| #[trigger] - >::mul_assign_req(&value, rhs), + res == (x@ < 0), ; -pub axiom fn axiom_bigint_not_spec(value: BigInt) +// We assume that `x.abs()` produces a `BigInt` `y` such that `y@ == abs(x@)`. +pub assume_specification[ ::abs ](x: &BigInt) -> (res: BigInt) ensures - ::obeys_not_spec(), - ::not_req(value), - ::not_spec(value)@ == -(value@) - 1, + res@ == if x@ < 0 { -x@ } else { x@ }, ; -// Conditions - +// This is the specification for what `BigInt::bits` produces. +// According to the documentation +// (https://docs.rs/num-bigint/latest/num_bigint/struct.BigInt.html#method.bits), +// it's the fewest bits necessary to express its value, not including the sign. pub open spec fn bigint_bits_ensures(value: int, bits: nat) -> bool { &&& -(pow2(bits) as int) < value < pow2(bits) @@ -363,47 +498,38 @@ pub assume_specification[ BigInt::bits ](x: &BigInt) -> (res: u64) bigint_bits_ensures(x@, res as nat), ; -pub assume_specification[ ::is_zero ](x: &BigInt) -> (res: bool) - ensures - res == (x@ == 0), -; +/// Negation -pub assume_specification[ ::is_negative ](x: &BigInt) -> (res: bool) +// This axiom says that, for `BigInt` `a`, `(-a)@ == -a@`. +pub axiom fn axiom_bigint_neg_spec(value: BigInt) ensures - res == (x@ < 0), + ::obeys_neg_spec(), + ::neg_req(value), + ::neg_spec(value)@ == -value@, ; -pub assume_specification[ ::abs ](x: &BigInt) -> (res: BigInt) - ensures - res@ == if x@ < 0 { -x@ } else { x@ }, -; - -// Formatting +/// Formatting // Nothing is promised about the rendered digits; callers only need this to be // callable from verified code. pub assume_specification[ BigInt::to_str_radix ](x: &BigInt, radix: u32) -> (res: alloc::string::String); -// PartialEq +/// Equality +// This axiom says that if we execute `a == b` for two `BigInt`s `a` and `b`, +// the result is equal to `a@ == b@`. pub axiom fn axiom_bigint_obeys_eq_spec() ensures ::obeys_eq_spec(), + forall|a: BigInt, b: BigInt| #[trigger] + ::eq_spec(&a, &b) == (a@ == b@), ; -pub axiom fn axiom_bigint_obeys_partial_cmp_spec() - ensures - ::obeys_partial_cmp_spec(), -; - -pub assume_specification[ ::eq ](x: &BigInt, y: &BigInt) -> (res: bool) - ensures - res == (x@ == y@), -; - -// Ord +/// Comparison +// This axiom says that comparison operators on `BigInt`s return what would +// result from comparing their views. pub axiom fn axiom_bigint_obeys_cmp_spec() ensures ::obeys_cmp_spec(), @@ -414,16 +540,7 @@ pub axiom fn axiom_bigint_obeys_cmp_spec() }, ; -pub assume_specification[ ::cmp ](x: &BigInt, y: &BigInt) -> (res: Ordering) - ensures - match res { - Ordering::Less => x@ < y@, - Ordering::Greater => x@ > y@, - Ordering::Equal => x@ == y@, - }, -; - -// From +/// From pub assume_specification[ >::from ](i: i64) -> (res: BigInt) ensures @@ -448,475 +565,15 @@ pub assume_specification[ >::from ](u: u128) pub assume_specification[ >::from ](u: u8) -> (res: BigInt) ensures res@ == u, -; +; -// One +/// BigInt::one pub assume_specification[ ::one ]() -> (res: BigInt) ensures res@ == 1, ; -// Negation - -pub assume_specification[ ::neg ](x: BigInt) -> (y: BigInt) - ensures - y@ == -x@, -; - -// Addition - -pub axiom fn axiom_bigint_obeys_add_spec() - ensures - ::obeys_add_spec(), - forall|lhs: BigInt, rhs: BigInt| #[trigger] - ::add_req(lhs, rhs), - forall|lhs: BigInt, rhs: BigInt| #[trigger] - ::add_spec(lhs, rhs)@ - == lhs@ + rhs@, -; - -pub assume_specification[ ::add ](x: BigInt, y: BigInt) -> (o: BigInt) - ensures - o@ == x@ + y@, -; - -pub assume_specification[ ::add_assign ]( - value: &mut BigInt, - rhs: BigInt, -) - ensures - (*final(value))@ == (*old(value))@ + rhs@, -; - -pub assume_specification<'a>[ >::add ](x: BigInt, y: &BigInt) -> (o: BigInt) - ensures - o@ == x@ + (*y)@, -; - -pub assume_specification<'a, 'b>[ <&BigInt as core::ops::Add<&BigInt>>::add ](x: &'b BigInt, y: &BigInt) -> (o: BigInt) - ensures - o@ == (*x)@ + (*y)@, -; - -pub assume_specification[ >::add ](x: BigInt, y: u8) -> (o: BigInt) - ensures - o@ == x@ + y, -; - -pub assume_specification[ >::add ](x: BigInt, y: u16) -> (o: BigInt) - ensures - o@ == x@ + y, -; - -pub assume_specification[ >::add ](x: BigInt, y: u32) -> (o: BigInt) - ensures - o@ == x@ + y, -; - -pub assume_specification[ >::add ](x: BigInt, y: u64) -> (o: BigInt) - ensures - o@ == x@ + y, -; - -pub assume_specification[ >::add ](x: BigInt, y: u128) -> (o: BigInt) - ensures - o@ == x@ + y, -; - -pub assume_specification[ >::add ](x: BigInt, y: i8) -> (o: BigInt) - ensures - o@ == x@ + y, -; - -pub assume_specification[ >::add ](x: BigInt, y: i16) -> (o: BigInt) - ensures - o@ == x@ + y, -; - -pub assume_specification[ >::add ](x: BigInt, y: i32) -> (o: BigInt) - ensures - o@ == x@ + y, -; - -pub assume_specification[ >::add ](x: BigInt, y: i64) -> (o: BigInt) - ensures - o@ == x@ + y, -; - -pub assume_specification[ >::add ](x: BigInt, y: i128) -> (o: BigInt) - ensures - o@ == x@ + y, -; - -pub assume_specification<'a>[ >::add ](x: BigInt, y: &u8) -> (o: BigInt) - ensures - o@ == x@ + *y, -; - -pub assume_specification<'a>[ >::add ](x: BigInt, y: &u16) -> (o: BigInt) - ensures - o@ == x@ + *y, -; - -pub assume_specification<'a>[ >::add ](x: BigInt, y: &u32) -> (o: BigInt) - ensures - o@ == x@ + *y, -; - -pub assume_specification<'a>[ >::add ](x: BigInt, y: &u64) -> (o: BigInt) - ensures - o@ == x@ + *y, -; - -pub assume_specification<'a>[ >::add ](x: BigInt, y: &u128) -> (o: BigInt) - ensures - o@ == x@ + *y, -; - -pub assume_specification<'a>[ >::add ](x: BigInt, y: &i8) -> (o: BigInt) - ensures - o@ == x@ + *y, -; - -pub assume_specification<'a>[ >::add ](x: BigInt, y: &i16) -> (o: BigInt) - ensures - o@ == x@ + *y, -; - -pub assume_specification<'a>[ >::add ](x: BigInt, y: &i32) -> (o: BigInt) - ensures - o@ == x@ + *y, -; - -pub assume_specification<'a>[ >::add ](x: BigInt, y: &i64) -> (o: BigInt) - ensures - o@ == x@ + *y, -; - -pub assume_specification<'a>[ >::add ](x: BigInt, y: &i128) -> (o: BigInt) - ensures - o@ == x@ + *y, -; - -// Subtraction - -pub axiom fn axiom_bigint_obeys_sub_spec() - ensures - ::obeys_sub_spec(), - forall|lhs: BigInt, rhs: BigInt| #[trigger] - ::sub_req(lhs, rhs), - forall|lhs: BigInt, rhs: BigInt| #[trigger] - ::sub_spec(lhs, rhs)@ - == lhs@ - rhs@, -; - -pub assume_specification[ ::sub ](x: BigInt, y: BigInt) -> (o: BigInt) - ensures - o@ == x@ - y@, -; - -pub assume_specification[ ::sub_assign ]( - value: &mut BigInt, - rhs: BigInt, -) - ensures - (*final(value))@ == (*old(value))@ - rhs@, -; - -pub assume_specification<'a>[ >::sub ](x: BigInt, y: &BigInt) -> (o: BigInt) - ensures - o@ == x@ - (*y)@, -; - -pub assume_specification<'a, 'b>[ <&BigInt as core::ops::Sub<&BigInt>>::sub ](x: &'b BigInt, y: &BigInt) -> (o: BigInt) - ensures - o@ == (*x)@ - (*y)@, -; - -pub assume_specification[ >::sub ](x: BigInt, y: u8) -> (o: BigInt) - ensures - o@ == x@ - y, -; - -pub assume_specification[ >::sub ](x: BigInt, y: u16) -> (o: BigInt) - ensures - o@ == x@ - y, -; - -pub assume_specification[ >::sub ](x: BigInt, y: u32) -> (o: BigInt) - ensures - o@ == x@ - y, -; - -pub assume_specification[ >::sub ](x: BigInt, y: u64) -> (o: BigInt) - ensures - o@ == x@ - y, -; - -pub assume_specification[ >::sub ](x: BigInt, y: u128) -> (o: BigInt) - ensures - o@ == x@ - y, -; - -pub assume_specification[ >::sub ](x: BigInt, y: i8) -> (o: BigInt) - ensures - o@ == x@ - y, -; - -pub assume_specification[ >::sub ](x: BigInt, y: i16) -> (o: BigInt) - ensures - o@ == x@ - y, -; - -pub assume_specification[ >::sub ](x: BigInt, y: i32) -> (o: BigInt) - ensures - o@ == x@ - y, -; - -pub assume_specification[ >::sub ](x: BigInt, y: i64) -> (o: BigInt) - ensures - o@ == x@ - y, -; - -pub assume_specification[ >::sub ](x: BigInt, y: i128) -> (o: BigInt) - ensures - o@ == x@ - y, -; - -pub assume_specification<'a>[ >::sub ](x: BigInt, y: &u8) -> (o: BigInt) - ensures - o@ == x@ - *y, -; - -pub assume_specification<'a>[ >::sub ](x: BigInt, y: &u16) -> (o: BigInt) - ensures - o@ == x@ - *y, -; - -pub assume_specification<'a>[ >::sub ](x: BigInt, y: &u32) -> (o: BigInt) - ensures - o@ == x@ - *y, -; - -pub assume_specification<'a>[ >::sub ](x: BigInt, y: &u64) -> (o: BigInt) - ensures - o@ == x@ - *y, -; - -pub assume_specification<'a>[ >::sub ](x: BigInt, y: &u128) -> (o: BigInt) - ensures - o@ == x@ - *y, -; - -pub assume_specification<'a>[ >::sub ](x: BigInt, y: &i8) -> (o: BigInt) - ensures - o@ == x@ - *y, -; - -pub assume_specification<'a>[ >::sub ](x: BigInt, y: &i16) -> (o: BigInt) - ensures - o@ == x@ - *y, -; - -pub assume_specification<'a>[ >::sub ](x: BigInt, y: &i32) -> (o: BigInt) - ensures - o@ == x@ - *y, -; - -pub assume_specification<'a>[ >::sub ](x: BigInt, y: &i64) -> (o: BigInt) - ensures - o@ == x@ - *y, -; - -pub assume_specification<'a>[ >::sub ](x: BigInt, y: &i128) -> (o: BigInt) - ensures - o@ == x@ - *y, -; - -// Multiplication - -pub axiom fn axiom_bigint_obeys_mul_spec() - ensures - ::obeys_mul_spec(), - forall|lhs: BigInt, rhs: BigInt| #[trigger] - ::mul_req(lhs, rhs), - forall|lhs: BigInt, rhs: BigInt| #[trigger] - ::mul_spec(lhs, rhs)@ - == lhs@ * rhs@, -; - -pub assume_specification[ ::mul ](x: BigInt, y: BigInt) -> (o: BigInt) - ensures - o@ == x@ * y@, -; - -pub assume_specification<'a>[ >::mul_assign ]( - value: &mut BigInt, - rhs: &BigInt, -) - ensures - (*final(value))@ == (*old(value))@ * rhs@, -; - -pub assume_specification<'a>[ >::mul ](x: BigInt, y: &BigInt) -> (o: BigInt) - ensures - o@ == x@ * (*y)@, -; - -pub assume_specification<'a, 'b>[ <&BigInt as core::ops::Mul<&BigInt>>::mul ](x: &'b BigInt, y: &BigInt) -> (o: BigInt) - ensures - o@ == (*x)@ * (*y)@, -; - -pub assume_specification[ >::mul ](x: BigInt, y: u8) -> (o: BigInt) - ensures - o@ == x@ * y, -; - -pub assume_specification[ >::mul ](x: BigInt, y: u16) -> (o: BigInt) - ensures - o@ == x@ * y, -; - -pub assume_specification[ >::mul ](x: BigInt, y: u32) -> (o: BigInt) - ensures - o@ == x@ * y, -; - -pub assume_specification[ >::mul ](x: BigInt, y: u64) -> (o: BigInt) - ensures - o@ == x@ * y, -; - -pub assume_specification[ >::mul ](x: BigInt, y: u128) -> (o: BigInt) - ensures - o@ == x@ * y, -; - -pub assume_specification[ >::mul ](x: BigInt, y: i8) -> (o: BigInt) - ensures - o@ == x@ * y, -; - -pub assume_specification[ >::mul ](x: BigInt, y: i16) -> (o: BigInt) - ensures - o@ == x@ * y, -; - -pub assume_specification[ >::mul ](x: BigInt, y: i32) -> (o: BigInt) - ensures - o@ == x@ * y, -; - -pub assume_specification[ >::mul ](x: BigInt, y: i64) -> (o: BigInt) - ensures - o@ == x@ * y, -; - -pub assume_specification[ >::mul ](x: BigInt, y: i128) -> (o: BigInt) - ensures - o@ == x@ * y, -; - -pub assume_specification<'a>[ >::mul ](x: BigInt, y: &u8) -> (o: BigInt) - ensures - o@ == x@ * *y, -; - -// Division - -pub axiom fn axiom_bigint_obeys_div_rem_spec() - ensures - ::obeys_div_spec(), - ::obeys_rem_spec(), - forall|lhs: BigInt, rhs: BigInt| rhs@ != 0 ==> #[trigger] - ::div_req(lhs, rhs), - forall|lhs: BigInt, rhs: BigInt| rhs@ != 0 ==> #[trigger] - ::rem_req(lhs, rhs), - forall|lhs: BigInt, rhs: BigInt| rhs@ != 0 ==> #[trigger] - ::div_spec(lhs, rhs)@ - == rust_div(lhs@, rhs@), - forall|lhs: BigInt, rhs: BigInt| rhs@ != 0 ==> #[trigger] - ::rem_spec(lhs, rhs)@ - == rust_rem(lhs@, rhs@), -; - -pub assume_specification[ ::div ](x: BigInt, y: BigInt) -> (o: BigInt) - ensures - y@ != 0 ==> o@ == rust_div(x@, y@), -; - -pub assume_specification<'a>[ >::div ](x: BigInt, y: &BigInt) -> (o: BigInt) - ensures - y@ != 0 ==> o@ == rust_div(x@, (*y)@), -; - -pub assume_specification<'a, 'b>[ <&BigInt as core::ops::Div<&BigInt>>::div ](x: &'b BigInt, y: &BigInt) -> (o: BigInt) - ensures - y@ != 0 ==> o@ == rust_div((*x)@, (*y)@), -; - -pub assume_specification<'a, 'b>[ <&BigInt as core::ops::Rem<&BigInt>>::rem ](x: &'b BigInt, y: &BigInt) -> (o: BigInt) - ensures - y@ != 0 ==> o@ == rust_rem((*x)@, (*y)@), -; - -pub assume_specification[ >::div ](x: BigInt, y: u8) -> (o: BigInt) - ensures - y != 0 ==> o@ == rust_div(x@, y as int), -; - -pub assume_specification[ >::div ](x: BigInt, y: u16) -> (o: BigInt) - ensures - y != 0 ==> o@ == rust_div(x@, y as int), -; - -pub assume_specification[ >::div ](x: BigInt, y: u32) -> (o: BigInt) - ensures - y != 0 ==> o@ == rust_div(x@, y as int), -; - -pub assume_specification[ >::div ](x: BigInt, y: u64) -> (o: BigInt) - ensures - y != 0 ==> o@ == rust_div(x@, y as int), -; - -pub assume_specification[ >::div ](x: BigInt, y: u128) -> (o: BigInt) - ensures - y != 0 ==> o@ == rust_div(x@, y as int), -; - -pub assume_specification[ >::div ](x: BigInt, y: i8) -> (o: BigInt) - ensures - y != 0 ==> o@ == rust_div(x@, y as int), -; - -pub assume_specification[ >::div ](x: BigInt, y: i16) -> (o: BigInt) - ensures - y != 0 ==> o@ == rust_div(x@, y as int), -; - -pub assume_specification[ >::div ](x: BigInt, y: i32) -> (o: BigInt) - ensures - y != 0 ==> o@ == rust_div(x@, y as int), -; - -pub assume_specification[ >::div ](x: BigInt, y: i64) -> (o: BigInt) - ensures - y != 0 ==> o@ == rust_div(x@, y as int), -; - -pub assume_specification[ >::div ](x: BigInt, y: i128) -> (o: BigInt) - ensures - y != 0 ==> o@ == rust_div(x@, y as int), -; - -pub assume_specification<'a>[ >::div ](x: BigInt, y: &u8) -> (o: BigInt) - ensures - *y != 0 ==> o@ == rust_div(x@, *y as int), -; - // Verus's encoding of ToPrimitive relies on an unstable feature // `sized_hierarchy`, so we can only talk about it when verifying. From 715982b73e45171debfe4f4f27f2901add7bd55e Mon Sep 17 00:00:00 2001 From: Jay Lorch Date: Wed, 12 Aug 2026 10:15:54 -0700 Subject: [PATCH 37/41] Clarify BigInt assumptions, upgrade to latest Verus --- .github/workflows/verus.yml | 4 +- Cargo.lock | 16 +-- Cargo.toml | 2 +- bindings/ffi/Cargo.lock | 16 +-- bindings/java/Cargo.lock | 16 +-- bindings/python/Cargo.lock | 16 +-- bindings/wasm/Cargo.lock | 16 +-- src/number.rs | 1 - src/verify/bigint_assumptions.rs | 239 ++++++++----------------------- src/verify/number_specs.rs | 2 +- 10 files changed, 105 insertions(+), 223 deletions(-) diff --git a/.github/workflows/verus.yml b/.github/workflows/verus.yml index def4829ee..ddaedf9ac 100644 --- a/.github/workflows/verus.yml +++ b/.github/workflows/verus.yml @@ -35,8 +35,8 @@ jobs: shell: bash run: | set -euxo pipefail - asset_url=https://github.com/verus-lang/verus/releases/download/release%2F0.2026.08.02.b677dd5/verus-0.2026.08.02.b677dd5-x86-linux.zip - asset_sha256=4c769256e888ee84bde85aae44d95c46bccbb8cf70e1d09f537b0d05fe965dee + asset_url=https://github.com/verus-lang/verus/releases/download/release%2F0.2026.08.09.92f466f/verus-0.2026.08.09.92f466f-x86-linux.zip + asset_sha256=2f5a41c553f424aacdd732339e9d125563716a0b003c27730f75d6f81a282cef test -n "$asset_url" curl -fsSL "$asset_url" -o verus.zip diff --git a/Cargo.lock b/Cargo.lock index 6c057a480..efa8feec5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1830,15 +1830,15 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "verus_builtin" -version = "0.0.0-2026-08-02-0125" +version = "0.0.0-2026-08-09-0044" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c27a0e580576f364e98925a61618089591e5bdea6e1ccf048b1d56ab3dfada70" +checksum = "f5f2985ebd252c492375e32f2bfcff6e6a04523009a0d624785d3468f3729158" [[package]] name = "verus_builtin_macros" -version = "0.0.0-2026-08-02-0125" +version = "0.0.0-2026-08-09-0044" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eabf0883a6e62f1db1538f96607e500dd396ada513329c3f486a14de5611d55b" +checksum = "57091b5661094900006f07621654a455ecf8ab40efc22981cb39b773ff7bc21c" dependencies = [ "convert_case", "proc-macro2 1.0.107", @@ -1851,9 +1851,9 @@ dependencies = [ [[package]] name = "verus_prettyplease" -version = "0.0.0-2026-08-02-0125" +version = "0.0.0-2026-08-09-0044" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d88628e583fb019eac36690ca2500b158f0221df6be2bae1e7c2a35dd6536bfc" +checksum = "51fc115de5fb3806bc362060bb683024660ed2bc13c1183d1f01d464e4537139" dependencies = [ "proc-macro2 1.0.107", "verus_syn", @@ -1890,9 +1890,9 @@ checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" [[package]] name = "vstd" -version = "0.0.0-2026-08-02-0125" +version = "0.0.0-2026-08-09-0044" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25040241d96eb88dfc6a75c289dc0ef84845ffff051d8e595cc55908d5c06a45" +checksum = "512e38f177f75c473a03121bf0934a276468f78fea84c073f979d6670e862705" dependencies = [ "verus_builtin", "verus_builtin_macros", diff --git a/Cargo.toml b/Cargo.toml index c134ad26f..95c2ae256 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -144,7 +144,7 @@ postcard = { version = "1.1.3", default-features = false, features = ["alloc"], # Verus-related dependencies. # vstd is enabled via the `verus` feature. In no_std builds only the `alloc` feature is used; # the crate's `std` feature additionally enables `vstd/std` (matching vstd's default features). -vstd = { version = "=0.0.0-2026-08-02-0125", optional = true, default-features = false, features = ["alloc"] } +vstd = { version = "=0.0.0-2026-08-09-0044", optional = true, default-features = false, features = ["alloc"] } # No-op stand-ins for verus_verify/verus_spec/proof, used when the `verus` feature # is disabled so the annotated source still compiles as ordinary Rust. This is a diff --git a/bindings/ffi/Cargo.lock b/bindings/ffi/Cargo.lock index ee8a4ab51..ac0b97bf4 100644 --- a/bindings/ffi/Cargo.lock +++ b/bindings/ffi/Cargo.lock @@ -1534,15 +1534,15 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "verus_builtin" -version = "0.0.0-2026-08-02-0125" +version = "0.0.0-2026-08-09-0044" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c27a0e580576f364e98925a61618089591e5bdea6e1ccf048b1d56ab3dfada70" +checksum = "f5f2985ebd252c492375e32f2bfcff6e6a04523009a0d624785d3468f3729158" [[package]] name = "verus_builtin_macros" -version = "0.0.0-2026-08-02-0125" +version = "0.0.0-2026-08-09-0044" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eabf0883a6e62f1db1538f96607e500dd396ada513329c3f486a14de5611d55b" +checksum = "57091b5661094900006f07621654a455ecf8ab40efc22981cb39b773ff7bc21c" dependencies = [ "convert_case", "proc-macro2", @@ -1555,9 +1555,9 @@ dependencies = [ [[package]] name = "verus_prettyplease" -version = "0.0.0-2026-08-02-0125" +version = "0.0.0-2026-08-09-0044" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d88628e583fb019eac36690ca2500b158f0221df6be2bae1e7c2a35dd6536bfc" +checksum = "51fc115de5fb3806bc362060bb683024660ed2bc13c1183d1f01d464e4537139" dependencies = [ "proc-macro2", "verus_syn", @@ -1594,9 +1594,9 @@ checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" [[package]] name = "vstd" -version = "0.0.0-2026-08-02-0125" +version = "0.0.0-2026-08-09-0044" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25040241d96eb88dfc6a75c289dc0ef84845ffff051d8e595cc55908d5c06a45" +checksum = "512e38f177f75c473a03121bf0934a276468f78fea84c073f979d6670e862705" dependencies = [ "verus_builtin", "verus_builtin_macros", diff --git a/bindings/java/Cargo.lock b/bindings/java/Cargo.lock index 2e8e2a516..97035d637 100644 --- a/bindings/java/Cargo.lock +++ b/bindings/java/Cargo.lock @@ -1352,15 +1352,15 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "verus_builtin" -version = "0.0.0-2026-08-02-0125" +version = "0.0.0-2026-08-09-0044" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c27a0e580576f364e98925a61618089591e5bdea6e1ccf048b1d56ab3dfada70" +checksum = "f5f2985ebd252c492375e32f2bfcff6e6a04523009a0d624785d3468f3729158" [[package]] name = "verus_builtin_macros" -version = "0.0.0-2026-08-02-0125" +version = "0.0.0-2026-08-09-0044" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eabf0883a6e62f1db1538f96607e500dd396ada513329c3f486a14de5611d55b" +checksum = "57091b5661094900006f07621654a455ecf8ab40efc22981cb39b773ff7bc21c" dependencies = [ "convert_case", "proc-macro2", @@ -1373,9 +1373,9 @@ dependencies = [ [[package]] name = "verus_prettyplease" -version = "0.0.0-2026-08-02-0125" +version = "0.0.0-2026-08-09-0044" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d88628e583fb019eac36690ca2500b158f0221df6be2bae1e7c2a35dd6536bfc" +checksum = "51fc115de5fb3806bc362060bb683024660ed2bc13c1183d1f01d464e4537139" dependencies = [ "proc-macro2", "verus_syn", @@ -1412,9 +1412,9 @@ checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" [[package]] name = "vstd" -version = "0.0.0-2026-08-02-0125" +version = "0.0.0-2026-08-09-0044" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25040241d96eb88dfc6a75c289dc0ef84845ffff051d8e595cc55908d5c06a45" +checksum = "512e38f177f75c473a03121bf0934a276468f78fea84c073f979d6670e862705" dependencies = [ "verus_builtin", "verus_builtin_macros", diff --git a/bindings/python/Cargo.lock b/bindings/python/Cargo.lock index e6cb7d257..fc0da7e74 100644 --- a/bindings/python/Cargo.lock +++ b/bindings/python/Cargo.lock @@ -1333,15 +1333,15 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "verus_builtin" -version = "0.0.0-2026-08-02-0125" +version = "0.0.0-2026-08-09-0044" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c27a0e580576f364e98925a61618089591e5bdea6e1ccf048b1d56ab3dfada70" +checksum = "f5f2985ebd252c492375e32f2bfcff6e6a04523009a0d624785d3468f3729158" [[package]] name = "verus_builtin_macros" -version = "0.0.0-2026-08-02-0125" +version = "0.0.0-2026-08-09-0044" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eabf0883a6e62f1db1538f96607e500dd396ada513329c3f486a14de5611d55b" +checksum = "57091b5661094900006f07621654a455ecf8ab40efc22981cb39b773ff7bc21c" dependencies = [ "convert_case", "proc-macro2", @@ -1354,9 +1354,9 @@ dependencies = [ [[package]] name = "verus_prettyplease" -version = "0.0.0-2026-08-02-0125" +version = "0.0.0-2026-08-09-0044" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d88628e583fb019eac36690ca2500b158f0221df6be2bae1e7c2a35dd6536bfc" +checksum = "51fc115de5fb3806bc362060bb683024660ed2bc13c1183d1f01d464e4537139" dependencies = [ "proc-macro2", "verus_syn", @@ -1393,9 +1393,9 @@ checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" [[package]] name = "vstd" -version = "0.0.0-2026-08-02-0125" +version = "0.0.0-2026-08-09-0044" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25040241d96eb88dfc6a75c289dc0ef84845ffff051d8e595cc55908d5c06a45" +checksum = "512e38f177f75c473a03121bf0934a276468f78fea84c073f979d6670e862705" dependencies = [ "verus_builtin", "verus_builtin_macros", diff --git a/bindings/wasm/Cargo.lock b/bindings/wasm/Cargo.lock index 5baf06b2c..55c08ed6a 100644 --- a/bindings/wasm/Cargo.lock +++ b/bindings/wasm/Cargo.lock @@ -1330,15 +1330,15 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "verus_builtin" -version = "0.0.0-2026-08-02-0125" +version = "0.0.0-2026-08-09-0044" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c27a0e580576f364e98925a61618089591e5bdea6e1ccf048b1d56ab3dfada70" +checksum = "f5f2985ebd252c492375e32f2bfcff6e6a04523009a0d624785d3468f3729158" [[package]] name = "verus_builtin_macros" -version = "0.0.0-2026-08-02-0125" +version = "0.0.0-2026-08-09-0044" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eabf0883a6e62f1db1538f96607e500dd396ada513329c3f486a14de5611d55b" +checksum = "57091b5661094900006f07621654a455ecf8ab40efc22981cb39b773ff7bc21c" dependencies = [ "convert_case", "proc-macro2", @@ -1351,9 +1351,9 @@ dependencies = [ [[package]] name = "verus_prettyplease" -version = "0.0.0-2026-08-02-0125" +version = "0.0.0-2026-08-09-0044" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d88628e583fb019eac36690ca2500b158f0221df6be2bae1e7c2a35dd6536bfc" +checksum = "51fc115de5fb3806bc362060bb683024660ed2bc13c1183d1f01d464e4537139" dependencies = [ "proc-macro2", "verus_syn", @@ -1390,9 +1390,9 @@ checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" [[package]] name = "vstd" -version = "0.0.0-2026-08-02-0125" +version = "0.0.0-2026-08-09-0044" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25040241d96eb88dfc6a75c289dc0ef84845ffff051d8e595cc55908d5c06a45" +checksum = "512e38f177f75c473a03121bf0934a276468f78fea84c073f979d6670e862705" dependencies = [ "verus_builtin", "verus_builtin_macros", diff --git a/src/number.rs b/src/number.rs index 3f0d66e87..073f3b662 100644 --- a/src/number.rs +++ b/src/number.rs @@ -701,7 +701,6 @@ impl Number { proof! { axiom_f64_ops_deterministic(); axiom_f64_safe_integer_casts(); - axiom_safe_bigints_to_f64(); lemma_bigint_bits_le_53(); } match self { diff --git a/src/verify/bigint_assumptions.rs b/src/verify/bigint_assumptions.rs index 37878c7de..383807da1 100644 --- a/src/verify/bigint_assumptions.rs +++ b/src/verify/bigint_assumptions.rs @@ -41,6 +41,7 @@ impl BigIntAdditionalSpecFns for BigInt { /// Semantics for BigInt::Clone +// We assume that `a.clone()` has the same view as `BigInt` `a`. pub assume_specification[ ::clone ](n: &BigInt) -> (res: BigInt) ensures res == n, @@ -542,43 +543,54 @@ pub axiom fn axiom_bigint_obeys_cmp_spec() /// From +// We assume that `BigInt::from(i)` where `i` has type `i64` produces a `BigInt` +// whose view equals `i`. pub assume_specification[ >::from ](i: i64) -> (res: BigInt) ensures res@ == i, ; +// We assume that `BigInt::from(i)` where `i` has type `i128` produces a `BigInt` +// whose view equals `i`. pub assume_specification[ >::from ](i: i128) -> (res: BigInt) ensures res@ == i, ; +// We assume that `BigInt::from(u)` where `u` has type `u64` produces a `BigInt` +// whose view equals `u`. pub assume_specification[ >::from ](u: u64) -> (res: BigInt) ensures res@ == u, ; +// We assume that `BigInt::from(u)` where `u` has type `u128` produces a `BigInt` +// whose view equals `u`. pub assume_specification[ >::from ](u: u128) -> (res: BigInt) ensures res@ == u, ; +// We assume that `BigInt::from(u)` where `u` has type `u8` produces a `BigInt` +// whose view equals `u`. pub assume_specification[ >::from ](u: u8) -> (res: BigInt) ensures res@ == u, -; +; /// BigInt::one +// We assume that `BigInt::one` produces a value whose view is 1. pub assume_specification[ ::one ]() -> (res: BigInt) ensures res@ == 1, ; -// Verus's encoding of ToPrimitive relies on an unstable feature -// `sized_hierarchy`, so we can only talk about it when verifying. - // ToPrimitive +// Verus does not support `assume_specification` for provided trait methods, so +// `to_u32` needs a minimal external trait specification. BigInt's overridden +// conversion methods are specified directly below. #[verifier::external_trait_specification] #[verifier::external_trait_extension(ToPrimitiveSpec via ToPrimitiveSpecImpl)] pub trait ExToPrimitive { @@ -588,121 +600,6 @@ pub trait ExToPrimitive { spec fn spec_to_int(&self) -> Option; - fn to_isize(&self) -> (res: Option) - ensures - Self::obeys_to_primitive_spec() ==> - match (self.spec_to_int(), res) { - (None, None) => true, - (None, Some(_)) => false, - (Some(n1), Some(n2)) => n1 == n2, - (Some(n), None) => !(isize::MIN <= n <= isize::MAX), - }, - default_ensures - true, - ; - - fn to_i8(&self) -> (res: Option) - ensures - Self::obeys_to_primitive_spec() ==> - match (self.spec_to_int(), res) { - (None, None) => true, - (None, Some(_)) => false, - (Some(n1), Some(n2)) => n1 == n2, - (Some(n), None) => !(i8::MIN <= n <= i8::MAX), - }, - default_ensures - true, - ; - - fn to_i16(&self) -> (res: Option) - ensures - Self::obeys_to_primitive_spec() ==> - match (self.spec_to_int(), res) { - (None, None) => true, - (None, Some(_)) => false, - (Some(n1), Some(n2)) => n1 == n2, - (Some(n), None) => !(i16::MIN <= n <= i16::MAX), - }, - default_ensures - true, - ; - - fn to_i32(&self) -> (res: Option) - ensures - Self::obeys_to_primitive_spec() ==> - match (self.spec_to_int(), res) { - (None, None) => true, - (None, Some(_)) => false, - (Some(n1), Some(n2)) => n1 == n2, - (Some(n), None) => !(i32::MIN <= n <= i32::MAX), - }, - default_ensures - true, - ; - - fn to_i64(&self) -> (res: Option) - ensures - Self::obeys_to_primitive_spec() ==> - match (self.spec_to_int(), res) { - (None, None) => true, - (None, Some(_)) => false, - (Some(n1), Some(n2)) => n1 == n2, - (Some(n), None) => !(i64::MIN <= n <= i64::MAX), - }, - ; - - fn to_i128(&self) -> (res: Option) - ensures - Self::obeys_to_primitive_spec() ==> - match (self.spec_to_int(), res) { - (None, None) => true, - (None, Some(_)) => false, - (Some(n1), Some(n2)) => n1 == n2, - (Some(n), None) => !(i128::MIN <= n <= i128::MAX), - }, - default_ensures - true, - ; - - fn to_usize(&self) -> (res: Option) - ensures - Self::obeys_to_primitive_spec() ==> - match (self.spec_to_int(), res) { - (None, None) => true, - (None, Some(_)) => false, - (Some(n1), Some(n2)) => n1 == n2, - (Some(n), None) => !(usize::MIN <= n <= usize::MAX), - }, - default_ensures - true, - ; - - fn to_u8(&self) -> (res: Option) - ensures - Self::obeys_to_primitive_spec() ==> - match (self.spec_to_int(), res) { - (None, None) => true, - (None, Some(_)) => false, - (Some(n1), Some(n2)) => n1 == n2, - (Some(n), None) => !(u8::MIN <= n <= u8::MAX), - }, - default_ensures - true, - ; - - fn to_u16(&self) -> (res: Option) - ensures - Self::obeys_to_primitive_spec() ==> - match (self.spec_to_int(), res) { - (None, None) => true, - (None, Some(_)) => false, - (Some(n1), Some(n2)) => n1 == n2, - (Some(n), None) => !(u16::MIN <= n <= u16::MAX), - }, - default_ensures - true, - ; - fn to_u32(&self) -> (res: Option) ensures Self::obeys_to_primitive_spec() ==> @@ -715,52 +612,9 @@ pub trait ExToPrimitive { default_ensures true, ; - - fn to_u64(&self) -> (res: Option) - ensures - Self::obeys_to_primitive_spec() ==> - match (self.spec_to_int(), res) { - (None, None) => true, - (None, Some(_)) => false, - (Some(n1), Some(n2)) => n1 == n2, - (Some(n), None) => !(u64::MIN <= n <= u64::MAX), - }, - ; - - fn to_u128(&self) -> (res: Option) - ensures - Self::obeys_to_primitive_spec() ==> - match (self.spec_to_int(), res) { - (None, None) => true, - (None, Some(_)) => false, - (Some(n1), Some(n2)) => n1 == n2, - (Some(n), None) => !(u128::MIN <= n <= u128::MAX), - }, - default_ensures - true, - ; - - spec fn spec_to_f32(&self) -> Option; - - fn to_f32(&self) -> (res: Option) - ensures - Self::obeys_to_primitive_spec() ==> res == self.spec_to_f32(), - default_ensures - true, - ; - - spec fn spec_to_f64(&self) -> Option; - - fn to_f64(&self) -> (res: Option) - ensures - Self::obeys_to_primitive_spec() ==> res == self.spec_to_f64(), - default_ensures - true, - ; } -impl ToPrimitiveSpecImpl for num_bigint::BigInt -{ +impl ToPrimitiveSpecImpl for BigInt { open spec fn obeys_to_primitive_spec() -> bool { true @@ -770,28 +624,57 @@ impl ToPrimitiveSpecImpl for num_bigint::BigInt { Some(self@) } +} - uninterp spec fn spec_to_f32(&self) -> Option; +// We assume that `b.to_i64()` where `b` is of type `BigInt` produces `Some(i)` +// if its view `i` is in the range of an `i64` and `None` otherwise. +pub assume_specification[ ::to_i64 ](x: &BigInt) -> (res: Option) + ensures + match res { + Some(value) => x@ == value, + None => !(i64::MIN <= x@ <= i64::MAX), + }, +; - uninterp spec fn spec_to_f64(&self) -> Option; -} +// We assume that `b.to_i128()` where `b` is of type `BigInt` produces `Some(i)` +// if its view `i` is in the range of an `i128` and `None` otherwise. +pub assume_specification[ ::to_i128 ](x: &BigInt) -> (res: Option) + ensures + match res { + Some(value) => x@ == value, + None => !(i128::MIN <= x@ <= i128::MAX), + }, +; -pub axiom fn axiom_safe_bigints_to_f64() +// We assume that `b.to_u64()` where `b` is of type `BigInt` produces `Some(u)` +// if its view `u` is in the range of a `u64` and `None` otherwise. +pub assume_specification[ ::to_u64 ](x: &BigInt) -> (res: Option) ensures - forall|x: &BigInt| { - -9_007_199_254_740_992 < x@ < 9_007_199_254_740_992 ==> - ::spec_to_f64(x) is Some + match res { + Some(value) => x@ == value, + None => !(u64::MIN <= x@ <= u64::MAX), }, ; -// These are the methods of ToPrimitive that BigInt implements because there is no default in ToPrimitive -pub assume_specification[ ::to_i64 ](x: &BigInt) -> (res: Option); -pub assume_specification[ ::to_u64 ](x: &BigInt) -> (res: Option); +// We assume that `b.to_u128()` where `b` is of type `BigInt` produces `Some(u)` +// if its view `u` is in the range of a `u128` and `None` otherwise. +pub assume_specification[ ::to_u128 ](x: &BigInt) -> (res: Option) + ensures + match res { + Some(value) => x@ == value, + None => !(u128::MIN <= x@ <= u128::MAX), + }, +; + +pub uninterp spec fn spec_bigint_to_f64(x: &BigInt) -> Option; -// These are the methods of ToPrimitive that BigInt overrides the defaults for because they'd otherwise be wrong -pub assume_specification[ ::to_i128 ](x: &BigInt) -> (res: Option); -pub assume_specification[ ::to_u128 ](x: &BigInt) -> (res: Option); -pub assume_specification[ ::to_f32 ](x: &BigInt) -> (res: Option); -pub assume_specification[ ::to_f64 ](x: &BigInt) -> (res: Option); +// We define the value returned by `b.to_f64()` as `spec_bigint_to_f64(&b)`. +// We assume that this is `Some` if `b` is in the range -2^53 to 2^53 inclusive. +// (It may be `Some` elsewhere; we don't assume either way.) +pub assume_specification[ ::to_f64 ](x: &BigInt) -> (res: Option) + ensures + res == spec_bigint_to_f64(x), + -9_007_199_254_740_992 <= x@ <= 9_007_199_254_740_992 ==> res is Some, +; } // end verus! diff --git a/src/verify/number_specs.rs b/src/verify/number_specs.rs index f35c930ae..cb1bec4a0 100644 --- a/src/verify/number_specs.rs +++ b/src/verify/number_specs.rs @@ -112,7 +112,7 @@ impl NumberView { ||| i64::MIN <= v <= i64::MAX && f == ieee_float_cast::(v as i64) ||| exists|bi: BigInt| { &&& bi@ == v - &&& match #[trigger] super::bigint_assumptions::ToPrimitiveSpec::spec_to_f64(&bi) { + &&& match #[trigger] super::bigint_assumptions::spec_bigint_to_f64(&bi) { Some(x) => f == x, None => f == if v < 0 { spec_f64_neg_infinity() } else { spec_f64_infinity() } } From fbbd1b6382ddc25092224e26bb3f435f5b81cfe2 Mon Sep 17 00:00:00 2001 From: Jay Lorch Date: Wed, 12 Aug 2026 10:54:46 -0700 Subject: [PATCH 38/41] Update bindings --- Cargo.lock | 4 +-- Cargo.toml | 2 +- bindings/ffi/Cargo.lock | 4 +-- bindings/java/Cargo.lock | 4 +-- bindings/python/Cargo.lock | 4 +-- bindings/ruby/Cargo.lock | 54 ++++++++++++++------------------------ bindings/wasm/Cargo.lock | 4 +-- 7 files changed, 30 insertions(+), 46 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index efa8feec5..236834524 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -942,9 +942,9 @@ checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" [[package]] name = "lru" -version = "0.18.1" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b6180140927ee907000b0aa540091f6ea512ead4447c92b8fc35bc72788a5a6" +checksum = "5d2f2f9b4ba7e6b24d95e7e899329d35be83bcded72c8540cdd5368932d1d90a" [[package]] name = "memchr" diff --git a/Cargo.toml b/Cargo.toml index 95c2ae256..6fb241bcd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -134,7 +134,7 @@ rand = { version = "0.10.0", default-features = false, features = ["thread_rng"] # Causes the project to link with the Spectre-mitigated CRT and libs. msvc_spectre_libs = { version = "0.1", features = ["error"], optional = true } dashmap = { version = "6.1", default-features = false, optional = true } -lru = { version = "0.18", default-features = false, optional = true } +lru = { version = "0.18.2", default-features = false, optional = true } mimalloc = { package = "regorus-mimalloc", path = "mimalloc", version = "2.2.7", optional = true } # rvm related deps diff --git a/bindings/ffi/Cargo.lock b/bindings/ffi/Cargo.lock index ac0b97bf4..44ee103c7 100644 --- a/bindings/ffi/Cargo.lock +++ b/bindings/ffi/Cargo.lock @@ -788,9 +788,9 @@ checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" [[package]] name = "lru" -version = "0.18.1" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b6180140927ee907000b0aa540091f6ea512ead4447c92b8fc35bc72788a5a6" +checksum = "5d2f2f9b4ba7e6b24d95e7e899329d35be83bcded72c8540cdd5368932d1d90a" [[package]] name = "memchr" diff --git a/bindings/java/Cargo.lock b/bindings/java/Cargo.lock index 97035d637..ed0b874a0 100644 --- a/bindings/java/Cargo.lock +++ b/bindings/java/Cargo.lock @@ -671,9 +671,9 @@ checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" [[package]] name = "lru" -version = "0.18.1" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b6180140927ee907000b0aa540091f6ea512ead4447c92b8fc35bc72788a5a6" +checksum = "5d2f2f9b4ba7e6b24d95e7e899329d35be83bcded72c8540cdd5368932d1d90a" [[package]] name = "memchr" diff --git a/bindings/python/Cargo.lock b/bindings/python/Cargo.lock index fc0da7e74..277812834 100644 --- a/bindings/python/Cargo.lock +++ b/bindings/python/Cargo.lock @@ -606,9 +606,9 @@ checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" [[package]] name = "lru" -version = "0.18.1" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b6180140927ee907000b0aa540091f6ea512ead4447c92b8fc35bc72788a5a6" +checksum = "5d2f2f9b4ba7e6b24d95e7e899329d35be83bcded72c8540cdd5368932d1d90a" [[package]] name = "memchr" diff --git a/bindings/ruby/Cargo.lock b/bindings/ruby/Cargo.lock index 47eb2fe2b..44223f0cc 100644 --- a/bindings/ruby/Cargo.lock +++ b/bindings/ruby/Cargo.lock @@ -369,12 +369,6 @@ dependencies = [ "regex-syntax", ] -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - [[package]] name = "hashbrown" version = "0.17.1" @@ -519,16 +513,6 @@ dependencies = [ "icu_properties", ] -[[package]] -name = "indexmap" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" -dependencies = [ - "autocfg", - "hashbrown 0.12.3", -] - [[package]] name = "indexmap" version = "2.14.0" @@ -536,7 +520,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", - "hashbrown 0.17.1", + "hashbrown", "serde", "serde_core", ] @@ -670,9 +654,9 @@ checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" [[package]] name = "lru" -version = "0.18.1" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b6180140927ee907000b0aa540091f6ea512ead4447c92b8fc35bc72788a5a6" +checksum = "5d2f2f9b4ba7e6b24d95e7e899329d35be83bcded72c8540cdd5368932d1d90a" [[package]] name = "magnus" @@ -1011,7 +995,7 @@ dependencies = [ "ahash", "fluent-uri", "getrandom 0.3.4", - "hashbrown 0.17.1", + "hashbrown", "itoa", "micromap", "parking_lot", @@ -1057,7 +1041,7 @@ dependencies = [ "chrono-tz", "data-encoding", "globset", - "indexmap 2.14.0", + "indexmap", "ipnet", "jsonschema", "lazy_static", @@ -1206,7 +1190,7 @@ version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap 2.14.0", + "indexmap", "itoa", "ryu", "serde", @@ -1415,15 +1399,15 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "verus_builtin" -version = "0.0.0-2026-07-27-0206" +version = "0.0.0-2026-08-09-0044" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e70a747c69a08b336573d67a8702993572317ab0f506084f717e6cefd3835f56" +checksum = "f5f2985ebd252c492375e32f2bfcff6e6a04523009a0d624785d3468f3729158" [[package]] name = "verus_builtin_macros" -version = "0.0.0-2026-07-27-0206" +version = "0.0.0-2026-08-09-0044" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3b07917203e0f36da3d29529f511f1ec27a1825529846433b4bc7384d400444" +checksum = "57091b5661094900006f07621654a455ecf8ab40efc22981cb39b773ff7bc21c" dependencies = [ "convert_case", "proc-macro2", @@ -1436,9 +1420,9 @@ dependencies = [ [[package]] name = "verus_prettyplease" -version = "0.0.0-2026-07-27-0206" +version = "0.0.0-2026-08-09-0044" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "028a6b1bedf498a425f5fa733c4a9aada900734c5ac82e4801597ff3ff70fa62" +checksum = "51fc115de5fb3806bc362060bb683024660ed2bc13c1183d1f01d464e4537139" dependencies = [ "proc-macro2", "verus_syn", @@ -1446,11 +1430,11 @@ dependencies = [ [[package]] name = "verus_state_machines_macros" -version = "0.0.0-2026-07-27-0206" +version = "0.0.0-2026-08-02-0125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "911e2a65229749eeb48a1f27d2b47a46b4fb43673f918854b065a932c86305b9" +checksum = "93f77ff8d121edb1bf2651335769a80a2f611da8573c3b498434a66b3162805f" dependencies = [ - "indexmap 1.9.3", + "indexmap", "proc-macro2", "quote", "verus_syn", @@ -1458,9 +1442,9 @@ dependencies = [ [[package]] name = "verus_syn" -version = "0.0.0-2026-07-27-0206" +version = "0.0.0-2026-08-02-0125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdcccb102ae806c65ae59653757ac28ecff328f996159757d17b5c735a7f7850" +checksum = "f17237ea6d267e457d53ce36f55b315fc9edd26eb88c765dd95e035c0b415869" dependencies = [ "proc-macro2", "quote", @@ -1475,9 +1459,9 @@ checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" [[package]] name = "vstd" -version = "0.0.0-2026-07-27-0206" +version = "0.0.0-2026-08-09-0044" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c72419942f28be7f54bca5e8bc1ac207f9efd45e424788905ecb1213881950a" +checksum = "512e38f177f75c473a03121bf0934a276468f78fea84c073f979d6670e862705" dependencies = [ "verus_builtin", "verus_builtin_macros", diff --git a/bindings/wasm/Cargo.lock b/bindings/wasm/Cargo.lock index 55c08ed6a..c53da12f9 100644 --- a/bindings/wasm/Cargo.lock +++ b/bindings/wasm/Cargo.lock @@ -644,9 +644,9 @@ checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" [[package]] name = "lru" -version = "0.18.1" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b6180140927ee907000b0aa540091f6ea512ead4447c92b8fc35bc72788a5a6" +checksum = "5d2f2f9b4ba7e6b24d95e7e899329d35be83bcded72c8540cdd5368932d1d90a" [[package]] name = "memchr" From efd8d0d50cc0f1170c40734485c42d730a119d8b Mon Sep 17 00:00:00 2001 From: Jay Lorch Date: Thu, 13 Aug 2026 15:39:08 -0700 Subject: [PATCH 39/41] Remove verus-shim and add vstd dependency --- .github/skills/verus-verification/SKILL.md | 2 +- .github/workflows/verus.yml | 2 +- Cargo.lock | 5 --- Cargo.toml | 15 +++------ bindings/ffi/Cargo.lock | 5 --- bindings/java/Cargo.lock | 5 --- bindings/python/Cargo.lock | 5 --- bindings/ruby/Cargo.lock | 5 --- bindings/wasm/Cargo.lock | 5 --- src/lib.rs | 12 ++++--- src/number.rs | 3 -- verus-shim/Cargo.toml | 14 -------- verus-shim/src/lib.rs | 39 ---------------------- 13 files changed, 13 insertions(+), 104 deletions(-) delete mode 100644 verus-shim/Cargo.toml delete mode 100644 verus-shim/src/lib.rs diff --git a/.github/skills/verus-verification/SKILL.md b/.github/skills/verus-verification/SKILL.md index f9c1d6aa8..28b0cead6 100644 --- a/.github/skills/verus-verification/SKILL.md +++ b/.github/skills/verus-verification/SKILL.md @@ -264,7 +264,7 @@ First trace a concrete input through the runtime behavior. After the first substantive edit, immediately run the narrowest check: ```bash -cargo verus verify --features verus \ +cargo verus verify \ --fwd-verus-args-to roots -- --verify-module number ``` diff --git a/.github/workflows/verus.yml b/.github/workflows/verus.yml index ddaedf9ac..309f8f7be 100644 --- a/.github/workflows/verus.yml +++ b/.github/workflows/verus.yml @@ -77,4 +77,4 @@ jobs: export PATH="$(dirname "$cargo_verus_bin"):$(dirname "$verus_bin"):$PATH" cargo verus --help cargo fetch --locked - cargo verus verify --locked --features verus + cargo verus verify --locked diff --git a/Cargo.lock b/Cargo.lock index 236834524..cc697681e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1418,7 +1418,6 @@ dependencies = [ "rand", "regex", "regorus-mimalloc", - "regorus-verus-shim", "semver", "serde", "serde_json", @@ -1446,10 +1445,6 @@ dependencies = [ "cc", ] -[[package]] -name = "regorus-verus-shim" -version = "0.0.0" - [[package]] name = "rustversion" version = "1.0.23" diff --git a/Cargo.toml b/Cargo.toml index 6fb241bcd..8cab5b01b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,6 @@ members = [ "tests/ensure_no_std", "xtask", - "verus-shim", ] [package] @@ -27,7 +26,6 @@ doctest = false [features] default = ["full-opa", "arc", "rvm"] -verus = ["dep:vstd"] arc = [] ast = [] @@ -50,7 +48,7 @@ cache = ["dep:lru"] rvm = ["dep:postcard", "dep:indexmap"] semver = ["dep:semver"] allocator-memory-limits = ["std", "mimalloc", "mimalloc/allocator-memory-limits"] -std = ["rand/std", "rand/std_rng", "serde_json/std", "indexmap?/std", "msvc_spectre_libs", "dep:parking_lot", "vstd?/std" ] +std = ["rand/std", "rand/std_rng", "serde_json/std", "indexmap?/std", "msvc_spectre_libs", "dep:parking_lot", "vstd/std" ] time = ["dep:chrono", "dep:chrono-tz"] uuid = ["dep:uuid"] urlquery = ["dep:url"] @@ -142,14 +140,9 @@ indexmap = { version = "2.13.1", default-features = false, features = ["serde"], postcard = { version = "1.1.3", default-features = false, features = ["alloc"], optional = true } # Verus-related dependencies. -# vstd is enabled via the `verus` feature. In no_std builds only the `alloc` feature is used; -# the crate's `std` feature additionally enables `vstd/std` (matching vstd's default features). -vstd = { version = "=0.0.0-2026-08-09-0044", optional = true, default-features = false, features = ["alloc"] } - -# No-op stand-ins for verus_verify/verus_spec/proof, used when the `verus` feature -# is disabled so the annotated source still compiles as ordinary Rust. This is a -# compile-time-only proc-macro crate and pulls in no runtime/verus dependencies. -regorus-verus-shim = { path = "verus-shim", version = "0.0.0" } +# In no_std builds only the `alloc` feature is used; the crate's `std` feature +# additionally enables `vstd/std` (matching vstd's default features). +vstd = { version = "=0.0.0-2026-08-09-0044", default-features = false, features = ["alloc"] } [dev-dependencies] anyhow = "1.0.102" diff --git a/bindings/ffi/Cargo.lock b/bindings/ffi/Cargo.lock index 44ee103c7..504ffb926 100644 --- a/bindings/ffi/Cargo.lock +++ b/bindings/ffi/Cargo.lock @@ -1142,7 +1142,6 @@ dependencies = [ "rand", "regex", "regorus-mimalloc", - "regorus-verus-shim", "semver", "serde", "serde_json", @@ -1179,10 +1178,6 @@ dependencies = [ "cc", ] -[[package]] -name = "regorus-verus-shim" -version = "0.0.0" - [[package]] name = "rustix" version = "1.1.4" diff --git a/bindings/java/Cargo.lock b/bindings/java/Cargo.lock index ed0b874a0..53b894eec 100644 --- a/bindings/java/Cargo.lock +++ b/bindings/java/Cargo.lock @@ -1014,7 +1014,6 @@ dependencies = [ "rand", "regex", "regorus-mimalloc", - "regorus-verus-shim", "semver", "serde", "serde_json", @@ -1050,10 +1049,6 @@ dependencies = [ "cc", ] -[[package]] -name = "regorus-verus-shim" -version = "0.0.0" - [[package]] name = "rustc_version" version = "0.4.1" diff --git a/bindings/python/Cargo.lock b/bindings/python/Cargo.lock index 277812834..e47cf243f 100644 --- a/bindings/python/Cargo.lock +++ b/bindings/python/Cargo.lock @@ -1022,7 +1022,6 @@ dependencies = [ "rand", "regex", "regorus-mimalloc", - "regorus-verus-shim", "semver", "serde", "serde_json", @@ -1048,10 +1047,6 @@ dependencies = [ "cc", ] -[[package]] -name = "regorus-verus-shim" -version = "0.0.0" - [[package]] name = "regoruspy" version = "0.11.0" diff --git a/bindings/ruby/Cargo.lock b/bindings/ruby/Cargo.lock index 44223f0cc..370dadaa4 100644 --- a/bindings/ruby/Cargo.lock +++ b/bindings/ruby/Cargo.lock @@ -1053,7 +1053,6 @@ dependencies = [ "rand", "regex", "regorus-mimalloc", - "regorus-verus-shim", "semver", "serde", "serde_json", @@ -1079,10 +1078,6 @@ dependencies = [ "cc", ] -[[package]] -name = "regorus-verus-shim" -version = "0.0.0" - [[package]] name = "regorusrb" version = "0.11.0" diff --git a/bindings/wasm/Cargo.lock b/bindings/wasm/Cargo.lock index c53da12f9..83bc57f41 100644 --- a/bindings/wasm/Cargo.lock +++ b/bindings/wasm/Cargo.lock @@ -1012,7 +1012,6 @@ dependencies = [ "postcard", "rand", "regex", - "regorus-verus-shim", "semver", "serde", "serde_json", @@ -1024,10 +1023,6 @@ dependencies = [ "vstd", ] -[[package]] -name = "regorus-verus-shim" -version = "0.0.0" - [[package]] name = "regorusjs" version = "0.11.0" diff --git a/src/lib.rs b/src/lib.rs index 5102213f0..27776974e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,11 +1,13 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -// Unsafe code should not be used, except during verification. -// Hard to reason about correctness, and maintainability. -// The `verus` feature is only used during verification, never in production -// builds, so the forbid remains in force for all shipped code. -#![cfg_attr(not(feature = "verus"), forbid(unsafe_code))] +// Unsafe code should not be used since it's hard to reason about +// its correctness and maintainability. +// However, `verus_keep_ghost` is only set during verification, +// never in production builds, so it's OK to allow unsafe code +// during verification. The `forbid` remains in force for all +// shipped code. +#![cfg_attr(not(verus_keep_ghost), forbid(unsafe_code))] // `anyhow!` with a literal lowers to `Arguments::from_str`, which is unstable. // Verus needs to name it to give it a specification. Verification builds use the // Verus toolchain, so this gate never applies to shipped code. diff --git a/src/number.rs b/src/number.rs index 073f3b662..45dd12cfb 100644 --- a/src/number.rs +++ b/src/number.rs @@ -27,9 +27,6 @@ use num_traits::{One, Signed, ToPrimitive, Zero}; use serde::ser::Serializer; use serde::Serialize; -#[cfg(not(feature = "verus"))] -use regorus_verus_shim::{proof, verus_spec, verus_verify}; -#[cfg(feature = "verus")] use vstd::prelude::*; #[cfg(verus_keep_ghost)] diff --git a/verus-shim/Cargo.toml b/verus-shim/Cargo.toml deleted file mode 100644 index 1a647602f..000000000 --- a/verus-shim/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. - -[package] -name = "regorus-verus-shim" -description = "No-op stand-ins for Verus's verus_verify/verus_spec/proof macros, used when the `verus` feature is disabled so annotated source still compiles as ordinary Rust." -version = "0.0.0" -edition = "2021" -license = "MIT" -repository = "https://github.com/microsoft/regorus" -publish = false - -[lib] -proc-macro = true diff --git a/verus-shim/src/lib.rs b/verus-shim/src/lib.rs deleted file mode 100644 index 43b08a8d0..000000000 --- a/verus-shim/src/lib.rs +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -//! No-op stand-ins for the Verus attribute/macros used to annotate source for -//! verification (`verus_verify`, `verus_spec`, `proof!`). -//! -//! When the `verus` feature is enabled, the real macros are provided by -//! `vstd::prelude`. When it is disabled, these no-ops are imported instead so -//! that a normal `cargo build` compiles the annotated source as ordinary Rust: -//! the attributes are stripped (their contents discarded) and `proof!` blocks -//! expand to nothing. - -use proc_macro::TokenStream; - -/// No-op replacement for `#[verus_verify]`. Returns the annotated item -/// unchanged, discarding any attribute arguments (e.g. `external_derive`). -#[proc_macro_attribute] -pub fn verus_verify(_attr: TokenStream, item: TokenStream) -> TokenStream { - item -} - -/// No-op replacement for `#[verus_spec(...)]`. Returns the annotated item -/// unchanged, discarding the specification. -#[proc_macro_attribute] -pub fn verus_spec(_attr: TokenStream, item: TokenStream) -> TokenStream { - item -} - -/// No-op replacement for `proof! { ... }`. Expands to nothing. -#[proc_macro] -pub fn proof(_input: TokenStream) -> TokenStream { - TokenStream::new() -} - -/// No-op replacement for `proof_decl! { ... }`. Expands to nothing. -#[proc_macro] -pub fn proof_decl(_input: TokenStream) -> TokenStream { - TokenStream::new() -} From 54bdd1ecc0f4a1af4cdffa410868cf45ea261c7f Mon Sep 17 00:00:00 2001 From: Jay Lorch Date: Fri, 14 Aug 2026 17:33:51 -0700 Subject: [PATCH 40/41] Move material that doesn't need auditing out of number_specs.rs --- .dir-locals.el | 2 +- src/verify/number_proofs.rs | 134 +++++++++++++++++++++++++++++++++++- src/verify/number_specs.rs | 105 +--------------------------- 3 files changed, 135 insertions(+), 106 deletions(-) diff --git a/.dir-locals.el b/.dir-locals.el index 1542960e4..f6e134e33 100644 --- a/.dir-locals.el +++ b/.dir-locals.el @@ -9,4 +9,4 @@ ;; ;; Everything before `--' is passed to cargo-verus; everything after `--' is ;; forwarded to the Verus binary. The `--' is required by verus-mode.el. -((verus-mode . ((verus-cargo-verus-arguments . ("--features" "verus" "--"))))) +;((verus-mode . ((verus-cargo-verus-arguments . ("--features" "verus" "--"))))) diff --git a/src/verify/number_proofs.rs b/src/verify/number_proofs.rs index 41a5578af..d5064419a 100644 --- a/src/verify/number_proofs.rs +++ b/src/verify/number_proofs.rs @@ -5,8 +5,11 @@ use vstd::prelude::*; verus! { -use super::number_specs::NumberView; +use core::cmp::Ordering; use crate::number::Number; +use num_bigint::BigInt; +use super::bigint_assumptions::*; +use super::number_specs::NumberView; use vstd::arithmetic::div_mod::{ lemma_div_of0, lemma_fundamental_div_mod, @@ -19,8 +22,137 @@ use vstd::arithmetic::mul::{ lemma_mul_strictly_increases, lemma_mul_unary_negation, }; +use vstd::float::*; +use vstd::std_specs::cmp::*; +use vstd::std_specs::convert::*; use vstd::std_specs::ops::*; +/// Spec trait implementations + +// For various `T`, we implement `From` for `Number`. This means +// that Verus demands a proof that it implements `FromSpecImpl`. +// The easiest (and most obviously correct) way to do this is to just +// define `obeys_from_spec()` as always returning `false`. We also +// need to define a `from_spec`, but it's meaningless since +// `obeys_from_spec()` always returns false. So we may as well leave +// it uninterpreted. + +impl FromSpecImpl for Number { + open spec fn obeys_from_spec() -> bool + { + false + } + + uninterp spec fn from_spec(v: BigInt) -> Number; +} + +impl FromSpecImpl for Number { + open spec fn obeys_from_spec() -> bool + { + false + } + + uninterp spec fn from_spec(v: u64) -> Number; +} + +impl FromSpecImpl for Number { + open spec fn obeys_from_spec() -> bool + { + false + } + + uninterp spec fn from_spec(v: usize) -> Number; +} + +impl FromSpecImpl for Number { + open spec fn obeys_from_spec() -> bool + { + false + } + + uninterp spec fn from_spec(v: u128) -> Number; +} + +impl FromSpecImpl for Number { + open spec fn obeys_from_spec() -> bool + { + false + } + + uninterp spec fn from_spec(v: i64) -> Number; +} + +impl FromSpecImpl for Number { + open spec fn obeys_from_spec() -> bool + { + false + } + + uninterp spec fn from_spec(v: i128) -> Number; +} + +impl FromSpecImpl for Number { + open spec fn obeys_from_spec() -> bool + { + false + } + + uninterp spec fn from_spec(v: f64) -> Number; +} + +// We implement `PartialEq` for `Number`. This means that Verus +// demands a proof that it implements `PartialEqSpecImpl`. The +// simplest, and most obviously correct, way to do this is to just say +// that `obeys_eq_spec()` always returns `false`. This makes all the +// postconditions trivial. We also need to define an `eq_spec`, but +// it's meaningless since `obeys_from_spec()` always returns false. So +// we may as well leave it uninterpreted. + +impl PartialEqSpecImpl for Number { + open spec fn obeys_eq_spec() -> bool + { + false + } + + uninterp spec fn eq_spec(&self, other: &Self) -> bool; +} + +// We implement `Ord` for `Number`. This means that Verus demands a +// proof that it implements `OrdSpecImpl`. The simplest, and most +// obviously correct, way to do this is to just say that +// `obeys_eq_spec()` always returns `false`. This makes all the +// postconditions trivial. We also need to define a `cmp_spec`, but +// it's meaningless since `obeys_from_spec()` always returns false. So +// we may as well leave it uninterpreted. + +impl OrdSpecImpl for Number { + open spec fn obeys_cmp_spec() -> bool + { + false + } + + uninterp spec fn cmp_spec(&self, other: &Self) -> Ordering; +} + +/// View implementation (internal to crate) + +impl View for Number +{ + type V = NumberView; + + open(crate) spec fn view(&self) -> NumberView + { + match self { + Number::UInt(n) => NumberView::Integer(n as int), + Number::Int(n) => NumberView::Integer(n as int), + Number::Float(f) => NumberView::Float(*f), + Number::BigInt(b) => NumberView::Integer(b@), + } + } +} + +/// Helpful lemmas + pub proof fn lemma_div_ensures_cases(lhs: NumberView, rhs: NumberView) ensures match (lhs, rhs) { diff --git a/src/verify/number_specs.rs b/src/verify/number_specs.rs index cb1bec4a0..32cb66be0 100644 --- a/src/verify/number_specs.rs +++ b/src/verify/number_specs.rs @@ -18,13 +18,11 @@ use vstd::prelude::*; verus! { -use core::cmp::Ordering; use crate::number::*; use super::bigint_assumptions::*; use super::f64_assumptions::*; use vstd::float::*; -use vstd::std_specs::cmp::*; -use vstd::std_specs::convert::*; +use vstd::std_specs::cmp::PartialEqSpec; pub assume_specification[ ::clone ](n: &Number) -> (res: Number) ensures @@ -36,21 +34,6 @@ pub enum NumberView { Float(f64), } -impl View for Number -{ - type V = NumberView; - - open(crate) spec fn view(&self) -> NumberView - { - match self { - Number::UInt(n) => NumberView::Integer(n as int), - Number::Int(n) => NumberView::Integer(n as int), - Number::Float(f) => NumberView::Float(*f), - Number::BigInt(b) => NumberView::Integer(b@), - } - } -} - pub open spec fn float_to_small_int(value: f64) -> Option { if !value.is_finite_spec() || @@ -200,90 +183,4 @@ impl NumberView { } -impl FromSpecImpl for Number { - open spec fn obeys_from_spec() -> bool - { - false - } - - uninterp spec fn from_spec(v: BigInt) -> Number; -} - -impl FromSpecImpl for Number { - open spec fn obeys_from_spec() -> bool - { - false - } - - uninterp spec fn from_spec(v: u64) -> Number; -} - -impl FromSpecImpl for Number { - open spec fn obeys_from_spec() -> bool - { - false - } - - uninterp spec fn from_spec(v: usize) -> Number; -} - -impl FromSpecImpl for Number { - open spec fn obeys_from_spec() -> bool - { - false - } - - uninterp spec fn from_spec(v: u128) -> Number; -} - -impl FromSpecImpl for Number { - open spec fn obeys_from_spec() -> bool - { - false - } - - uninterp spec fn from_spec(v: i64) -> Number; -} - -impl FromSpecImpl for Number { - open spec fn obeys_from_spec() -> bool - { - false - } - - uninterp spec fn from_spec(v: i128) -> Number; -} - -impl FromSpecImpl for Number { - open spec fn obeys_from_spec() -> bool - { - false - } - - uninterp spec fn from_spec(v: f64) -> Number; -} - -impl PartialEqSpecImpl for Number { - open spec fn obeys_eq_spec() -> bool - { - false - } - - open spec fn eq_spec(&self, other: &Self) -> bool - { - *self == *other - } -} - -impl OrdSpecImpl for Number { - // `Number::cmp` is specified directly in terms of `NumberView`, so there's - // no need for a `cmp_spec` that would expose the internal representation. - open spec fn obeys_cmp_spec() -> bool - { - false - } - - uninterp spec fn cmp_spec(&self, other: &Self) -> Ordering; -} - } // end verus! From ae6ac396f37bf19e88a4fb0c2f03d6a82d151547 Mon Sep 17 00:00:00 2001 From: Jay Lorch Date: Mon, 17 Aug 2026 14:25:34 -0700 Subject: [PATCH 41/41] Remove unnecessary .dir-locals.el --- .dir-locals.el | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 .dir-locals.el diff --git a/.dir-locals.el b/.dir-locals.el deleted file mode 100644 index f6e134e33..000000000 --- a/.dir-locals.el +++ /dev/null @@ -1,12 +0,0 @@ -;;; Directory Local Variables -*- no-byte-compile: t; -*- -;;; For more information see (info "(emacs) Directory Variables") - -;; Regorus is a cargo-verus project (package.metadata.verus.verify = true), so -;; verus-mode.el runs `cargo verus verify' rather than the raw `verus' binary. -;; The cargo-verus path ignores `package.metadata.verus.ide.extra_args' and -;; instead reads `verus-cargo-verus-arguments'. We set it here so that Verus is -;; invoked with the `verus' Cargo feature enabled. -;; -;; Everything before `--' is passed to cargo-verus; everything after `--' is -;; forwarded to the Verus binary. The `--' is required by verus-mode.el. -;((verus-mode . ((verus-cargo-verus-arguments . ("--features" "verus" "--")))))