Skip to main content

fixed_bigint/fixeduint/
has_nonzero_impl.rs

1// Copyright 2021 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! `HasNonZero` + `DivNonZero` carrier impls for `FixedUInt`.
16//!
17//! `HasNonZero::into_nonzero` returns `Option`, which is branchful at
18//! the call site. Fine for public moduli; secret-derived proofs go
19//! through `CtNonZero::into_nonzero_ct` (a masked-return `CtOption`)
20//! implemented lower in this file.
21
22// `let _ = <T as AssertNonzeroCarrier>::CHECK;` in `Default::default()`
23// binds a unit-typed associated const to force monomorphization-time
24// evaluation of the `N > 0` assertion. Same idiom as
25// `byte_conversion_panic_free.rs`.
26#![allow(clippy::let_unit_value)]
27
28use super::{FixedUInt, MachineWord};
29use crate::machineword::ConstMachineWord;
30use const_num_traits::Zero;
31use const_num_traits::{DivNonZero, HasNonZero, Nct, Personality};
32
33/// Non-zero `FixedUInt`. Constructed via [`HasNonZero::into_nonzero`].
34///
35/// `#[repr(transparent)]` over `FixedUInt<T, N, P>` — the layout is
36/// identical, so `NonZeroFixedUInt` can round-trip through FFI at the
37/// same ABI as the inner. Always `Copy` (matches
38/// `HasNonZero::NonZero: Copy`); the drop of the inner runs because
39/// the field is owned, not because of the repr.
40#[repr(transparent)]
41#[derive(Clone, Copy, PartialEq, Eq)]
42pub struct NonZeroFixedUInt<T, const N: usize, P: Personality>(FixedUInt<T, N, P>)
43where
44    T: MachineWord;
45
46// Manual `Debug` (not `#[derive]`) — Nct needs `T: Debug`, Ct doesn't;
47// a derive would add a uniform bound the Ct arm shouldn't require.
48impl<T: MachineWord + core::fmt::Debug, const N: usize> core::fmt::Debug
49    for NonZeroFixedUInt<T, N, const_num_traits::Nct>
50{
51    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
52        write!(f, "NonZero({:?})", self.0)
53    }
54}
55
56impl<T: MachineWord, const N: usize> core::fmt::Debug
57    for NonZeroFixedUInt<T, N, const_num_traits::Ct>
58{
59    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
60        // Ct's `FixedUInt::Debug` doesn't reveal contents (CT hygiene);
61        // we propagate that.
62        write!(f, "NonZero({:?})", self.0)
63    }
64}
65
66impl<T, const N: usize, P: Personality> NonZeroFixedUInt<T, N, P>
67where
68    T: MachineWord,
69{
70    /// Recover the underlying `FixedUInt`.
71    #[inline]
72    pub fn get(self) -> FixedUInt<T, N, P> {
73        self.0
74    }
75}
76
77// Type-level compile-time assertion `N > 0`. `const { assert!(N > 0) }`
78// inside a fn body is rejected on nightly with `generic_const_exprs` as
79// "overly complex generic constant" (blocks aren't supported there).
80// Moving the assertion to an associated const on a trait impl sidesteps
81// that — same pattern as `AssertBufferFits` in
82// `byte_conversion_panic_free.rs`.
83trait AssertNonzeroCarrier {
84    const CHECK: ();
85}
86impl<T: MachineWord, const N: usize, P: Personality> AssertNonzeroCarrier
87    for NonZeroFixedUInt<T, N, P>
88{
89    const CHECK: () = assert!(
90        N > 0,
91        "NonZeroFixedUInt::default() requires N > 0 (an N=0 carrier can only represent zero)"
92    );
93}
94
95// `Default` returns the smallest non-zero value (1). Convention: the
96// "default non-zero" is the multiplicative identity, the same shape
97// `core::num::NonZero` uses (`NonZero::<u32>::new(1).unwrap()`). Needed
98// by `subtle::CtOption::map` whose bound is `T: Default +
99// ConditionallySelectable` on the input value type.
100impl<T, const N: usize, P: Personality> Default for NonZeroFixedUInt<T, N, P>
101where
102    T: MachineWord,
103{
104    fn default() -> Self {
105        // Fires at monomorphization when N == 0.
106        let _ = <Self as AssertNonzeroCarrier>::CHECK;
107        // Under N > 0, `FixedUInt::from(1u8)` writes 1 into the low limb
108        // so the value is non-zero.
109        NonZeroFixedUInt(FixedUInt::from(1u8))
110    }
111}
112
113// Ct-only (mirrors `FixedUInt`'s own `ConditionallySelectable`).
114// Soundness: both `a` and `b` carry the "value != 0" invariant, so
115// the selected value is non-zero regardless of which arm is taken.
116impl<T, const N: usize> subtle::ConditionallySelectable
117    for NonZeroFixedUInt<T, N, const_num_traits::Ct>
118where
119    T: MachineWord + subtle::ConditionallySelectable,
120{
121    fn conditional_select(a: &Self, b: &Self, choice: subtle::Choice) -> Self {
122        Self(<FixedUInt<T, N, const_num_traits::Ct> as subtle::ConditionallySelectable>::conditional_select(
123            &a.0, &b.0, choice,
124        ))
125    }
126}
127
128c0nst::c0nst! {
129    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> HasNonZero for FixedUInt<T, N, P> {
130        type NonZero = NonZeroFixedUInt<T, N, P>;
131
132        // `Option` shape is branchful at the call site — fine for
133        // public inputs, but a secret-derived `self` leaks the "is
134        // zero" bit through the `Some`/`None` discriminant. Ct callers
135        // route through `CtNonZero::into_nonzero_ct` (below) which
136        // returns a masked `CtOption`.
137        #[inline]
138        fn into_nonzero(self) -> Option<Self::NonZero> {
139            if <Self as Zero>::is_zero(&self) {
140                None
141            } else {
142                Some(NonZeroFixedUInt(self))
143            }
144        }
145
146        #[inline]
147        fn nonzero_get(nz: Self::NonZero) -> Self {
148            nz.0
149        }
150    }
151
152    // `DivNonZero` is `Nct`-only because `core::ops::Div for FixedUInt` is
153    // `Nct`-only (the long-division body is value-dependent — `if remainder
154    // >= divisor` etc. — and doesn't fit `Ct` semantics).
155    //
156    // Panic-freeness: the API contract ("no `Result` or `.unwrap()` at
157    // the caller boundary") is met — the `NonZeroFixedUInt` proof-type
158    // discharges the divide-by-zero check statically. `self / d.0` routes
159    // through `const_div_rem`, whose runtime divisor-non-zero check LLVM
160    // can't prove unreachable through the wrapper on its own, so we restate
161    // the invariant with `unreachable_unchecked` on the same `const_is_zero`
162    // the divide performs — the two unify and the `panic_fmt` DCEs, leaving
163    // a binary-level panic-free divide.
164    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize> DivNonZero for FixedUInt<T, N, Nct> {
165        type Output = FixedUInt<T, N, Nct>;
166
167        #[inline]
168        fn div_nonzero(self, d: Self::NonZero) -> Self::Output {
169            // Call `const_div` directly rather than `self / d.0`: the latter
170            // routes through `const_div_rem`, whose divisor zero-check LLVM
171            // keeps (with its `panic_fmt`) because it can't see the proof
172            // through the wrapper. `NonZeroFixedUInt` guarantees `d != 0`, so
173            // the long division is always well-defined and this path carries
174            // no panic symbol.
175            let mut quotient = self.array;
176            let _remainder = super::const_div(&mut quotient, &d.0.array);
177            Self::from_array(quotient)
178        }
179
180        #[inline]
181        fn rem_nonzero(self, d: Self::NonZero) -> Self::Output {
182            // See `div_nonzero`; `const_div` returns the remainder.
183            let mut quotient = self.array;
184            let remainder = super::const_div(&mut quotient, &d.0.array);
185            Self::from_array(remainder)
186        }
187    }
188}
189
190// `CtNonZero` — masked-return `into_nonzero` for both personalities;
191// the `Choice` mask is value-level and CT-uniform.
192impl<T, const N: usize, P: Personality> const_num_traits::CtNonZero for FixedUInt<T, N, P>
193where
194    T: MachineWord + subtle::ConstantTimeEq,
195{
196    fn into_nonzero_ct(self) -> subtle::CtOption<Self::NonZero> {
197        use const_num_traits::ops::ct::CtIsZero;
198        let zero = self.ct_is_zero();
199        // The `!zero` mask gates observation, so consumers only see
200        // the wrapper when `self` is actually non-zero.
201        subtle::CtOption::new(NonZeroFixedUInt(self), !zero)
202    }
203}
204
205#[cfg(test)]
206mod tests {
207    use super::*;
208    use const_num_traits::{Ct, Nct};
209
210    type U32 = FixedUInt<u8, 4, Nct>;
211    type U32Ct = FixedUInt<u8, 4, Ct>;
212
213    #[test]
214    fn into_nonzero_some_for_nonzero() {
215        assert!(U32::from(5u32).into_nonzero().is_some());
216    }
217
218    #[test]
219    fn into_nonzero_none_for_zero() {
220        assert!(U32::from(0u32).into_nonzero().is_none());
221    }
222
223    #[test]
224    fn into_nonzero_works_under_ct_too() {
225        assert!(U32Ct::from(5u32).into_nonzero().is_some());
226        assert!(U32Ct::from(0u32).into_nonzero().is_none());
227    }
228
229    #[test]
230    fn nonzero_round_trip() {
231        let v = U32::from(42u32);
232        let nz = v.into_nonzero().unwrap();
233        assert_eq!(<U32 as HasNonZero>::nonzero_get(nz), v);
234        assert_eq!(nz.get(), v);
235    }
236
237    #[test]
238    fn div_rem_nonzero_match_div_rem() {
239        let a = U32::from(100u32);
240        let m = U32::from(7u32);
241        let nz = m.into_nonzero().unwrap();
242        assert_eq!(<U32 as DivNonZero>::div_nonzero(a, nz), a / m);
243        assert_eq!(<U32 as DivNonZero>::rem_nonzero(a, nz), a % m);
244    }
245
246    #[test]
247    fn div_rem_nonzero_wider_carrier() {
248        // Spot-check a u32-backed carrier to confirm the trait composes
249        // across limb-width variants.
250        type U128 = FixedUInt<u32, 4, Nct>;
251        let a = U128::from(12_345_678u32);
252        let m = U128::from(101u32);
253        let nz = m.into_nonzero().unwrap();
254        assert_eq!(<U128 as DivNonZero>::div_nonzero(a, nz), a / m);
255        assert_eq!(<U128 as DivNonZero>::rem_nonzero(a, nz), a % m);
256    }
257
258    // Compile-time guard: `DivNonZero for FixedUInt<_, _, Ct>` does NOT
259    // exist (Div on FixedUInt is Nct-only). The `HasNonZero` impl stays
260    // generic in P — the proof type exists for Ct, it just can't be
261    // divided by. `static_assertions::assert_not_impl_any!` fires at
262    // compile time if a future `impl DivNonZero for FixedUInt<_, _, Ct>`
263    // sneaks in.
264    static_assertions::assert_not_impl_any!(
265        FixedUInt<u32, 4, const_num_traits::Ct>: DivNonZero
266    );
267
268    #[test]
269    fn into_nonzero_ct_masks_zero() {
270        use const_num_traits::CtNonZero;
271        type U32Nct = FixedUInt<u8, 4, Nct>;
272        type U32Ct = FixedUInt<u8, 4, Ct>;
273
274        // Nct carrier
275        let nz = U32Nct::from(5u32).into_nonzero_ct();
276        assert!(bool::from(nz.is_some()));
277        assert_eq!(nz.unwrap().get(), U32Nct::from(5u32));
278
279        let z = U32Nct::from(0u32).into_nonzero_ct();
280        assert!(!bool::from(z.is_some()));
281
282        // Ct carrier
283        let nz_ct: U32Ct = U32Nct::from(42u32).into();
284        let opt = nz_ct.into_nonzero_ct();
285        assert!(bool::from(opt.is_some()));
286        assert_eq!(opt.unwrap().get(), nz_ct);
287
288        let z_ct: U32Ct = U32Nct::from(0u32).into();
289        let opt = z_ct.into_nonzero_ct();
290        assert!(!bool::from(opt.is_some()));
291    }
292}