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. But the produced
159    // binary still contains a `panic_fmt` symbol because `self / d.0`
160    // routes through `const_div_rem`, which retains a runtime
161    // divisor-non-zero check whose branch LLVM can't prove unreachable
162    // through the `#[repr(transparent)]` wrapper. Consumers who need
163    // a *binary-level* panic-free divide should audit downstream.
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            self / d.0
170        }
171
172        #[inline]
173        fn rem_nonzero(self, d: Self::NonZero) -> Self::Output {
174            self % d.0
175        }
176    }
177}
178
179// `CtNonZero` — masked-return `into_nonzero` for both personalities;
180// the `Choice` mask is value-level and CT-uniform.
181impl<T, const N: usize, P: Personality> const_num_traits::CtNonZero for FixedUInt<T, N, P>
182where
183    T: MachineWord + subtle::ConstantTimeEq,
184{
185    fn into_nonzero_ct(self) -> subtle::CtOption<Self::NonZero> {
186        use const_num_traits::ops::ct::CtIsZero;
187        let zero = self.ct_is_zero();
188        // The `!zero` mask gates observation, so consumers only see
189        // the wrapper when `self` is actually non-zero.
190        subtle::CtOption::new(NonZeroFixedUInt(self), !zero)
191    }
192}
193
194#[cfg(test)]
195mod tests {
196    use super::*;
197    use const_num_traits::{Ct, Nct};
198
199    type U32 = FixedUInt<u8, 4, Nct>;
200    type U32Ct = FixedUInt<u8, 4, Ct>;
201
202    #[test]
203    fn into_nonzero_some_for_nonzero() {
204        assert!(U32::from(5u32).into_nonzero().is_some());
205    }
206
207    #[test]
208    fn into_nonzero_none_for_zero() {
209        assert!(U32::from(0u32).into_nonzero().is_none());
210    }
211
212    #[test]
213    fn into_nonzero_works_under_ct_too() {
214        assert!(U32Ct::from(5u32).into_nonzero().is_some());
215        assert!(U32Ct::from(0u32).into_nonzero().is_none());
216    }
217
218    #[test]
219    fn nonzero_round_trip() {
220        let v = U32::from(42u32);
221        let nz = v.into_nonzero().unwrap();
222        assert_eq!(<U32 as HasNonZero>::nonzero_get(nz), v);
223        assert_eq!(nz.get(), v);
224    }
225
226    #[test]
227    fn div_rem_nonzero_match_div_rem() {
228        let a = U32::from(100u32);
229        let m = U32::from(7u32);
230        let nz = m.into_nonzero().unwrap();
231        assert_eq!(<U32 as DivNonZero>::div_nonzero(a, nz), a / m);
232        assert_eq!(<U32 as DivNonZero>::rem_nonzero(a, nz), a % m);
233    }
234
235    #[test]
236    fn div_rem_nonzero_wider_carrier() {
237        // Spot-check a u32-backed carrier to confirm the trait composes
238        // across limb-width variants.
239        type U128 = FixedUInt<u32, 4, Nct>;
240        let a = U128::from(12_345_678u32);
241        let m = U128::from(101u32);
242        let nz = m.into_nonzero().unwrap();
243        assert_eq!(<U128 as DivNonZero>::div_nonzero(a, nz), a / m);
244        assert_eq!(<U128 as DivNonZero>::rem_nonzero(a, nz), a % m);
245    }
246
247    // Compile-time guard: `DivNonZero for FixedUInt<_, _, Ct>` does NOT
248    // exist (Div on FixedUInt is Nct-only). The `HasNonZero` impl stays
249    // generic in P — the proof type exists for Ct, it just can't be
250    // divided by. `static_assertions::assert_not_impl_any!` fires at
251    // compile time if a future `impl DivNonZero for FixedUInt<_, _, Ct>`
252    // sneaks in.
253    static_assertions::assert_not_impl_any!(
254        FixedUInt<u32, 4, const_num_traits::Ct>: DivNonZero
255    );
256
257    #[test]
258    fn into_nonzero_ct_masks_zero() {
259        use const_num_traits::CtNonZero;
260        type U32Nct = FixedUInt<u8, 4, Nct>;
261        type U32Ct = FixedUInt<u8, 4, Ct>;
262
263        // Nct carrier
264        let nz = U32Nct::from(5u32).into_nonzero_ct();
265        assert!(bool::from(nz.is_some()));
266        assert_eq!(nz.unwrap().get(), U32Nct::from(5u32));
267
268        let z = U32Nct::from(0u32).into_nonzero_ct();
269        assert!(!bool::from(z.is_some()));
270
271        // Ct carrier
272        let nz_ct: U32Ct = U32Nct::from(42u32).into();
273        let opt = nz_ct.into_nonzero_ct();
274        assert!(bool::from(opt.is_some()));
275        assert_eq!(opt.unwrap().get(), nz_ct);
276
277        let z_ct: U32Ct = U32Nct::from(0u32).into();
278        let opt = z_ct.into_nonzero_ct();
279        assert!(!bool::from(opt.is_some()));
280    }
281}