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}