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}