arnaucube
arnaucube/phantom-zonePublic Self-hosted repository
phantom-zone/src/bool/mp_api.rs
src/bool/mp_api.rsRaw
  1use std::{cell::RefCell, sync::OnceLock};
  2
  3use crate::{
  4    backend::{ModularOpsU64, ModulusPowerOf2},
  5    ntt::NttBackendU64,
  6    random::{DefaultSecureRng, NewWithSeed},
  7    utils::{Global, WithLocal},
  8};
  9
 10use super::{evaluator::InteractiveMultiPartyCrs, keys::*, parameters::*, ClientKey};
 11
 12pub(crate) type BoolEvaluator = super::evaluator::BoolEvaluator<
 13    Vec<Vec<u64>>,
 14    NttBackendU64,
 15    ModularOpsU64<CiphertextModulus<u64>>,
 16    ModulusPowerOf2<CiphertextModulus<u64>>,
 17    ShoupServerKeyEvaluationDomain<Vec<Vec<u64>>>,
 18>;
 19
 20thread_local! {
 21    static BOOL_EVALUATOR: RefCell<Option<BoolEvaluator>> = RefCell::new(None);
 22
 23}
 24static BOOL_SERVER_KEY: OnceLock<ShoupServerKeyEvaluationDomain<Vec<Vec<u64>>>> = OnceLock::new();
 25
 26static MULTI_PARTY_CRS: OnceLock<InteractiveMultiPartyCrs<[u8; 32]>> = OnceLock::new();
 27
 28pub enum ParameterSelector {
 29    InteractiveLTE2Party,
 30    InteractiveLTE4Party,
 31    InteractiveLTE8Party,
 32}
 33
 34/// Select Interactive multi-party parameter variant
 35pub fn set_parameter_set(select: ParameterSelector) {
 36    match select {
 37        ParameterSelector::InteractiveLTE2Party => {
 38            BOOL_EVALUATOR.with_borrow_mut(|v| *v = Some(BoolEvaluator::new(I_2P_LB_SR)));
 39        }
 40        ParameterSelector::InteractiveLTE4Party => {
 41            BOOL_EVALUATOR.with_borrow_mut(|v| *v = Some(BoolEvaluator::new(I_4P)));
 42        }
 43        ParameterSelector::InteractiveLTE8Party => {
 44            BOOL_EVALUATOR.with_borrow_mut(|v| *v = Some(BoolEvaluator::new(I_8P)));
 45        }
 46    }
 47}
 48
 49/// Set application specific interactive multi-party common reference string
 50pub fn set_common_reference_seed(seed: [u8; 32]) {
 51    assert!(
 52        MULTI_PARTY_CRS
 53            .set(InteractiveMultiPartyCrs { seed: seed })
 54            .is_ok(),
 55        "Attempted to set MP SEED twice."
 56    )
 57}
 58
 59/// Generate client key for interactive multi-party protocol
 60pub fn gen_client_key() -> ClientKey {
 61    BoolEvaluator::with_local(|e| e.client_key())
 62}
 63
 64/// Generate client's share for collective public key, i.e round 1 share, in
 65/// round 1 of the 2 round protocol
 66pub fn interactive_multi_party_round1_share(
 67    ck: &ClientKey,
 68) -> CommonReferenceSeededCollectivePublicKeyShare<Vec<u64>, [u8; 32], BoolParameters<u64>> {
 69    BoolEvaluator::with_local(|e| {
 70        let pk_share = e.multi_party_public_key_share(InteractiveMultiPartyCrs::global(), ck);
 71        pk_share
 72    })
 73}
 74
 75/// Generate clients share for collective server key, i.e. round 2, of the
 76/// protocol
 77pub fn gen_mp_keys_phase2<R, ModOp>(
 78    ck: &ClientKey,
 79    user_id: usize,
 80    total_users: usize,
 81    pk: &PublicKey<Vec<Vec<u64>>, R, ModOp>,
 82) -> CommonReferenceSeededInteractiveMultiPartyServerKeyShare<
 83    Vec<Vec<u64>>,
 84    BoolParameters<u64>,
 85    InteractiveMultiPartyCrs<[u8; 32]>,
 86> {
 87    BoolEvaluator::with_local_mut(|e| {
 88        let server_key_share = e.gen_interactive_multi_party_server_key_share(
 89            user_id,
 90            total_users,
 91            InteractiveMultiPartyCrs::global(),
 92            pk.key(),
 93            ck,
 94        );
 95        server_key_share
 96    })
 97}
 98
 99/// Aggregate public key shares from all parties.
100///
101/// Public key shares are generated per client in round 1. Aggregation of public
102/// key shares marks the end of round 1.
103pub fn aggregate_public_key_shares(
104    shares: &[CommonReferenceSeededCollectivePublicKeyShare<
105        Vec<u64>,
106        [u8; 32],
107        BoolParameters<u64>,
108    >],
109) -> PublicKey<Vec<Vec<u64>>, DefaultSecureRng, ModularOpsU64<CiphertextModulus<u64>>> {
110    PublicKey::from(shares)
111}
112
113/// Aggregate server key shares
114pub fn aggregate_server_key_shares(
115    shares: &[CommonReferenceSeededInteractiveMultiPartyServerKeyShare<
116        Vec<Vec<u64>>,
117        BoolParameters<u64>,
118        InteractiveMultiPartyCrs<[u8; 32]>,
119    >],
120) -> SeededInteractiveMultiPartyServerKey<
121    Vec<Vec<u64>>,
122    InteractiveMultiPartyCrs<[u8; 32]>,
123    BoolParameters<u64>,
124> {
125    BoolEvaluator::with_local(|e| e.aggregate_interactive_multi_party_server_key_shares(shares))
126}
127
128impl
129    SeededInteractiveMultiPartyServerKey<
130        Vec<Vec<u64>>,
131        InteractiveMultiPartyCrs<<DefaultSecureRng as NewWithSeed>::Seed>,
132        BoolParameters<u64>,
133    >
134{
135    /// Sets the server key as a global reference for circuit evaluation
136    pub fn set_server_key(&self) {
137        assert!(
138            BOOL_SERVER_KEY
139                .set(ShoupServerKeyEvaluationDomain::from(
140                    ServerKeyEvaluationDomain::<_, _, DefaultSecureRng, NttBackendU64>::from(self),
141                ))
142                .is_ok(),
143            "Attempted to set server key twice."
144        );
145    }
146}
147
148//  MULTIPARTY CRS  //
149impl Global for InteractiveMultiPartyCrs<[u8; 32]> {
150    fn global() -> &'static Self {
151        MULTI_PARTY_CRS
152            .get()
153            .expect("Multi Party Common Reference String not set")
154    }
155}
156
157// BOOL EVALUATOR //
158impl WithLocal for BoolEvaluator {
159    fn with_local<F, R>(func: F) -> R
160    where
161        F: Fn(&Self) -> R,
162    {
163        BOOL_EVALUATOR.with_borrow(|s| func(s.as_ref().expect("Parameters not set")))
164    }
165
166    fn with_local_mut<F, R>(func: F) -> R
167    where
168        F: Fn(&mut Self) -> R,
169    {
170        BOOL_EVALUATOR.with_borrow_mut(|s| func(s.as_mut().expect("Parameters not set")))
171    }
172
173    fn with_local_mut_mut<F, R>(func: &mut F) -> R
174    where
175        F: FnMut(&mut Self) -> R,
176    {
177        BOOL_EVALUATOR.with_borrow_mut(|s| func(s.as_mut().expect("Parameters not set")))
178    }
179}
180
181pub(crate) type RuntimeServerKey = ShoupServerKeyEvaluationDomain<Vec<Vec<u64>>>;
182impl Global for RuntimeServerKey {
183    fn global() -> &'static Self {
184        BOOL_SERVER_KEY.get().expect("Server key not set!")
185    }
186}
187
188mod impl_enc_dec {
189    use crate::{
190        bool::evaluator::BoolEncoding,
191        multi_party::{
192            multi_party_aggregate_decryption_shares_and_decrypt, multi_party_decryption_share,
193        },
194        pbs::{sample_extract, PbsInfo},
195        rgsw::public_key_encrypt_rlwe,
196        utils::TryConvertFrom1,
197        Encryptor, Matrix, MatrixEntity, MultiPartyDecryptor, RowEntity,
198    };
199    use itertools::Itertools;
200    use num_traits::{ToPrimitive, Zero};
201
202    use super::*;
203
204    type Mat = Vec<Vec<u64>>;
205
206    impl<Rng, ModOp> Encryptor<[bool], Vec<Mat>> for PublicKey<Mat, Rng, ModOp> {
207        fn encrypt(&self, m: &[bool]) -> Vec<Mat> {
208            BoolEvaluator::with_local(|e| {
209                DefaultSecureRng::with_local_mut(|rng| {
210                    let parameters = e.parameters();
211                    let ring_size = parameters.rlwe_n().0;
212
213                    let rlwe_count = ((m.len() as f64 / ring_size as f64).ceil())
214                        .to_usize()
215                        .unwrap();
216
217                    // encrypt `m` into ceil(len(m)/N) RLWE ciphertexts
218                    let rlwes = (0..rlwe_count)
219                        .map(|index| {
220                            let mut message = vec![<Mat as Matrix>::MatElement::zero(); ring_size];
221                            m[(index * ring_size)..std::cmp::min(m.len(), (index + 1) * ring_size)]
222                                .iter()
223                                .enumerate()
224                                .for_each(|(i, v)| {
225                                    if *v {
226                                        message[i] = parameters.rlwe_q().true_el()
227                                    } else {
228                                        message[i] = parameters.rlwe_q().false_el()
229                                    }
230                                });
231
232                            // encrypt message
233                            let mut rlwe_out =
234                                <Mat as MatrixEntity>::zeros(2, parameters.rlwe_n().0);
235
236                            public_key_encrypt_rlwe::<_, _, _, _, i32, _>(
237                                &mut rlwe_out,
238                                self.key(),
239                                &message,
240                                e.pbs_info().modop_rlweq(),
241                                e.pbs_info().nttop_rlweq(),
242                                rng,
243                            );
244
245                            rlwe_out
246                        })
247                        .collect_vec();
248                    rlwes
249                })
250            })
251        }
252    }
253
254    impl<Rng, ModOp> Encryptor<bool, <Mat as Matrix>::R> for PublicKey<Mat, Rng, ModOp> {
255        fn encrypt(&self, m: &bool) -> <Mat as Matrix>::R {
256            let m = vec![*m];
257            let rlwe = &self.encrypt(m.as_slice())[0];
258            BoolEvaluator::with_local(|e| {
259                let mut lwe = <Mat as Matrix>::R::zeros(e.parameters().rlwe_n().0 + 1);
260                sample_extract(&mut lwe, rlwe, e.pbs_info().modop_rlweq(), 0);
261                lwe
262            })
263        }
264    }
265
266    impl<K> MultiPartyDecryptor<bool, <Mat as Matrix>::R> for K
267    where
268        K: InteractiveMultiPartyClientKey,
269        <Mat as Matrix>::R:
270            TryConvertFrom1<[K::Element], CiphertextModulus<<Mat as Matrix>::MatElement>>,
271    {
272        type DecryptionShare = <Mat as Matrix>::MatElement;
273
274        fn gen_decryption_share(&self, c: &<Mat as Matrix>::R) -> Self::DecryptionShare {
275            BoolEvaluator::with_local(|e| {
276                DefaultSecureRng::with_local_mut(|rng| {
277                    multi_party_decryption_share(
278                        c,
279                        self.sk_rlwe().as_slice(),
280                        e.pbs_info().modop_rlweq(),
281                        rng,
282                    )
283                })
284            })
285        }
286
287        fn aggregate_decryption_shares(
288            &self,
289            c: &<Mat as Matrix>::R,
290            shares: &[Self::DecryptionShare],
291        ) -> bool {
292            BoolEvaluator::with_local(|e| {
293                let noisy_m = multi_party_aggregate_decryption_shares_and_decrypt(
294                    c,
295                    shares,
296                    e.pbs_info().modop_rlweq(),
297                );
298
299                e.pbs_info().rlwe_q().decode(noisy_m)
300            })
301        }
302    }
303}
304
305#[cfg(test)]
306mod tests {
307    use itertools::Itertools;
308    use rand::{thread_rng, Rng, RngCore};
309
310    use crate::{bool::evaluator::BoolEncoding, Encryptor, MultiPartyDecryptor, SampleExtractor};
311
312    use super::*;
313
314    #[test]
315    fn batched_fhe_u8s_extract_works() {
316        set_parameter_set(ParameterSelector::InteractiveLTE2Party);
317        let mut seed = [0u8; 32];
318        thread_rng().fill_bytes(&mut seed);
319        set_common_reference_seed(seed);
320
321        let parties = 2;
322        let cks = (0..parties).map(|_| gen_client_key()).collect_vec();
323
324        // round 1
325        let pk_shares = cks
326            .iter()
327            .map(|k| interactive_multi_party_round1_share(k))
328            .collect_vec();
329
330        // collective pk
331        let pk = aggregate_public_key_shares(&pk_shares);
332
333        let parameters = BoolEvaluator::with_local(|e| e.parameters().clone());
334
335        let batch_size = parameters.rlwe_n().0 * 3 + 123;
336        let m = (0..batch_size)
337            .map(|_| thread_rng().gen::<u8>())
338            .collect_vec();
339
340        let seeded_ct = pk.encrypt(m.as_slice());
341
342        let m_back = (0..batch_size)
343            .map(|i| {
344                let ct = seeded_ct.extract_at(i);
345                cks[0].aggregate_decryption_shares(
346                    &ct,
347                    &cks.iter()
348                        .map(|k| k.gen_decryption_share(&ct))
349                        .collect_vec(),
350                )
351            })
352            .collect_vec();
353
354        assert_eq!(m, m_back);
355    }
356
357    mod sp_api {
358        use num_traits::ToPrimitive;
359
360        use crate::{
361            bool::impl_bool_frontend::FheBool, pbs::PbsInfo, rgsw::seeded_secret_key_encrypt_rlwe,
362            Decryptor,
363        };
364
365        use super::*;
366
367        pub(crate) fn set_single_party_parameter_sets(parameter: BoolParameters<u64>) {
368            BOOL_EVALUATOR.with_borrow_mut(|e| *e = Some(BoolEvaluator::new(parameter)));
369        }
370
371        // SERVER KEY EVAL (/SHOUP) DOMAIN //
372        impl SeededSinglePartyServerKey<Vec<Vec<u64>>, BoolParameters<u64>, [u8; 32]> {
373            pub fn set_server_key(&self) {
374                assert!(
375                    BOOL_SERVER_KEY
376                        .set(
377                            ShoupServerKeyEvaluationDomain::from(ServerKeyEvaluationDomain::<
378                                _,
379                                _,
380                                DefaultSecureRng,
381                                NttBackendU64,
382                            >::from(
383                                self
384                            ),)
385                        )
386                        .is_ok(),
387                    "Attempted to set server key twice."
388                );
389            }
390        }
391
392        pub(crate) fn gen_keys() -> (
393            ClientKey,
394            SeededSinglePartyServerKey<Vec<Vec<u64>>, BoolParameters<u64>, [u8; 32]>,
395        ) {
396            super::BoolEvaluator::with_local_mut(|e| {
397                let ck = e.client_key();
398                let sk = e.single_party_server_key(&ck);
399
400                (ck, sk)
401            })
402        }
403
404        impl<K: SinglePartyClientKey<Element = i32>> Encryptor<bool, Vec<u64>> for K {
405            fn encrypt(&self, m: &bool) -> Vec<u64> {
406                BoolEvaluator::with_local(|e| e.sk_encrypt(*m, self))
407            }
408        }
409
410        impl<K: SinglePartyClientKey<Element = i32>> Decryptor<bool, Vec<u64>> for K {
411            fn decrypt(&self, c: &Vec<u64>) -> bool {
412                BoolEvaluator::with_local(|e| e.sk_decrypt(c, self))
413            }
414        }
415
416        impl<K: SinglePartyClientKey<Element = i32>, C> Encryptor<bool, FheBool<C>> for K
417        where
418            K: Encryptor<bool, C>,
419        {
420            fn encrypt(&self, m: &bool) -> FheBool<C> {
421                FheBool {
422                    data: self.encrypt(m),
423                }
424            }
425        }
426
427        impl<K: SinglePartyClientKey<Element = i32>, C> Decryptor<bool, FheBool<C>> for K
428        where
429            K: Decryptor<bool, C>,
430        {
431            fn decrypt(&self, c: &FheBool<C>) -> bool {
432                self.decrypt(c.data())
433            }
434        }
435
436        impl<K> Encryptor<[bool], (Vec<Vec<u64>>, [u8; 32])> for K
437        where
438            K: SinglePartyClientKey<Element = i32>,
439        {
440            fn encrypt(&self, m: &[bool]) -> (Vec<Vec<u64>>, [u8; 32]) {
441                BoolEvaluator::with_local(|e| {
442                    DefaultSecureRng::with_local_mut(|rng| {
443                        let parameters = e.parameters();
444                        let ring_size = parameters.rlwe_n().0;
445
446                        let rlwe_count = ((m.len() as f64 / ring_size as f64).ceil())
447                            .to_usize()
448                            .unwrap();
449
450                        let mut seed = <DefaultSecureRng as NewWithSeed>::Seed::default();
451                        rng.fill_bytes(&mut seed);
452                        let mut prng = DefaultSecureRng::new_seeded(seed);
453
454                        let sk_u = self.sk_rlwe();
455
456                        // encrypt `m` into ceil(len(m)/N) RLWE ciphertexts
457                        let rlwes = (0..rlwe_count)
458                            .map(|index| {
459                                let mut message = vec![0; ring_size];
460                                m[(index * ring_size)
461                                    ..std::cmp::min(m.len(), (index + 1) * ring_size)]
462                                    .iter()
463                                    .enumerate()
464                                    .for_each(|(i, v)| {
465                                        if *v {
466                                            message[i] = parameters.rlwe_q().true_el()
467                                        } else {
468                                            message[i] = parameters.rlwe_q().false_el()
469                                        }
470                                    });
471
472                                // encrypt message
473                                let mut rlwe_out = vec![0u64; parameters.rlwe_n().0];
474                                seeded_secret_key_encrypt_rlwe(
475                                    &message,
476                                    &mut rlwe_out,
477                                    &sk_u,
478                                    e.pbs_info().modop_rlweq(),
479                                    e.pbs_info().nttop_rlweq(),
480                                    &mut prng,
481                                    rng,
482                                );
483
484                                rlwe_out
485                            })
486                            .collect_vec();
487
488                        (rlwes, seed)
489                    })
490                })
491            }
492        }
493
494        #[test]
495        #[cfg(feature = "interactive_mp")]
496        fn all_uint8_apis() {
497            use num_traits::Euclid;
498
499            use crate::{div_zero_error_flag, FheBool};
500
501            set_single_party_parameter_sets(SP_TEST_BOOL_PARAMS);
502
503            let (ck, sk) = gen_keys();
504            sk.set_server_key();
505
506            for i in 0..=255 {
507                for j in 0..=255 {
508                    let m0 = i;
509                    let m1 = j;
510                    let c0 = ck.encrypt(&m0);
511                    let c1 = ck.encrypt(&m1);
512
513                    assert!(ck.decrypt(&c0) == m0);
514                    assert!(ck.decrypt(&c1) == m1);
515
516                    // Arithmetic
517                    {
518                        {
519                            // Add
520                            let c_add = &c0 + &c1;
521                            let m0_plus_m1 = ck.decrypt(&c_add);
522                            assert_eq!(
523                                m0_plus_m1,
524                                m0.wrapping_add(m1),
525                                "Expected {} but got {m0_plus_m1} for
526                        {i}+{j}",
527                                m0.wrapping_add(m1)
528                            );
529                        }
530                        {
531                            // Sub
532                            let c_sub = &c0 - &c1;
533                            let m0_sub_m1 = ck.decrypt(&c_sub);
534                            assert_eq!(
535                                m0_sub_m1,
536                                m0.wrapping_sub(m1),
537                                "Expected {} but got {m0_sub_m1} for
538                        {i}-{j}",
539                                m0.wrapping_sub(m1)
540                            );
541                        }
542
543                        {
544                            // Mul
545                            let c_m0m1 = &c0 * &c1;
546                            let m0m1 = ck.decrypt(&c_m0m1);
547                            assert_eq!(
548                                m0m1,
549                                m0.wrapping_mul(m1),
550                                "Expected {} but got {m0m1} for {i}x{j}",
551                                m0.wrapping_mul(m1)
552                            );
553                        }
554
555                        // Div & Rem
556                        {
557                            let (c_quotient, c_rem) = c0.div_rem(&c1);
558                            let m_quotient = ck.decrypt(&c_quotient);
559                            let m_remainder = ck.decrypt(&c_rem);
560                            if j != 0 {
561                                let (q, r) = i.div_rem_euclid(&j);
562                                assert_eq!(
563                                    m_quotient, q,
564                                    "Expected {} but got {m_quotient} for
565                    {i}/{j}",
566                                    q
567                                );
568                                assert_eq!(
569                                    m_remainder, r,
570                                    "Expected {} but got {m_remainder} for
571                    {i}%{j}",
572                                    r
573                                );
574                            } else {
575                                assert_eq!(
576                                    m_quotient, 255,
577                                    "Expected 255 but got {m_quotient}. Case
578                    div by zero"
579                                );
580                                assert_eq!(
581                                    m_remainder, i,
582                                    "Expected {i} but got {m_remainder}. Case
583                    div by zero"
584                                );
585
586                                let div_by_zero = ck.decrypt(&div_zero_error_flag().unwrap());
587                                assert_eq!(
588                                    div_by_zero, true,
589                                    "Expected true but got {div_by_zero}"
590                                );
591                            }
592                        }
593                    }
594
595                    // // Comparisons
596                    {
597                        {
598                            let c_eq = c0.eq(&c1);
599                            let is_eq = ck.decrypt(&c_eq);
600                            assert_eq!(
601                                is_eq,
602                                i == j,
603                                "Expected {} but got {is_eq} for {i}=={j}",
604                                i == j
605                            );
606                        }
607
608                        {
609                            let c_gt = c0.gt(&c1);
610                            let is_gt = ck.decrypt(&c_gt);
611                            assert_eq!(
612                                is_gt,
613                                i > j,
614                                "Expected {} but got {is_gt} for {i}>{j}",
615                                i > j
616                            );
617                        }
618
619                        {
620                            let c_lt = c0.lt(&c1);
621                            let is_lt = ck.decrypt(&c_lt);
622                            assert_eq!(
623                                is_lt,
624                                i < j,
625                                "Expected {} but got {is_lt} for {i}<{j}",
626                                i < j
627                            );
628                        }
629
630                        {
631                            let c_ge = c0.ge(&c1);
632                            let is_ge = ck.decrypt(&c_ge);
633                            assert_eq!(
634                                is_ge,
635                                i >= j,
636                                "Expected {} but got {is_ge} for {i}>={j}",
637                                i >= j
638                            );
639                        }
640
641                        {
642                            let c_le = c0.le(&c1);
643                            let is_le = ck.decrypt(&c_le);
644                            assert_eq!(
645                                is_le,
646                                i <= j,
647                                "Expected {} but got {is_le} for {i}<={j}",
648                                i <= j
649                            );
650                        }
651                    }
652
653                    // mux
654                    {
655                        let selector = thread_rng().gen_bool(0.5);
656                        let selector_enc: FheBool = ck.encrypt(&selector);
657                        let mux_out = ck.decrypt(&c0.mux(&c1, &selector_enc));
658                        let want_mux_out = if selector { m0 } else { m1 };
659                        assert_eq!(mux_out, want_mux_out);
660                    }
661                }
662            }
663        }
664
665        #[test]
666        #[cfg(feature = "interactive_mp")]
667        fn all_bool_apis() {
668            use crate::FheBool;
669
670            set_single_party_parameter_sets(SP_TEST_BOOL_PARAMS);
671
672            let (ck, sk) = gen_keys();
673            sk.set_server_key();
674
675            for _ in 0..100 {
676                let a = thread_rng().gen_bool(0.5);
677                let b = thread_rng().gen_bool(0.5);
678
679                let c_a: FheBool = ck.encrypt(&a);
680                let c_b: FheBool = ck.encrypt(&b);
681
682                let c_out = &c_a & &c_b;
683                let out = ck.decrypt(&c_out);
684                assert_eq!(out, a & b, "Expected {} but got {out}", a & b);
685
686                let c_out = &c_a | &c_b;
687                let out = ck.decrypt(&c_out);
688                assert_eq!(out, a | b, "Expected {} but got {out}", a | b);
689
690                let c_out = &c_a ^ &c_b;
691                let out = ck.decrypt(&c_out);
692                assert_eq!(out, a ^ b, "Expected {} but got {out}", a ^ b);
693
694                let c_out = !(&c_a);
695                let out = ck.decrypt(&c_out);
696                assert_eq!(out, !a, "Expected {} but got {out}", !a);
697            }
698        }
699    }
700}