Skip to main content

oxilean_std/cryptographic_protocols/
types.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4use super::functions::*;
5
6/// Schnorr identification protocol parameters.
7///
8/// Proves knowledge of discrete logarithm x such that g^x = y (mod p).
9///
10/// # WARNING
11/// This is an educational implementation with tiny parameters.
12/// Real Schnorr uses 256-bit+ groups.
13#[derive(Debug, Clone)]
14pub struct SchnorrParams {
15    /// Prime modulus p
16    pub p: u64,
17    /// Prime order q of the group (q | p-1)
18    pub q: u64,
19    /// Generator g of order q
20    pub g: u64,
21}
22impl SchnorrParams {
23    /// Prover commits with randomness r; returns (commitment, r).
24    ///
25    /// # WARNING
26    /// Educational only. Use a cryptographic RNG in production.
27    pub fn commit(&self, r: u64) -> u64 {
28        mod_exp(self.g, r % self.q, self.p)
29    }
30    /// Prover computes response z = (r + e * x) mod q.
31    pub fn respond(&self, r: u64, challenge: u64, secret_x: u64) -> u64 {
32        let r = r % self.q;
33        let e_x = (challenge as u128 * secret_x as u128 % self.q as u128) as u64;
34        (r + e_x) % self.q
35    }
36    /// Verifier checks: g^z = a · y^e (mod p) where y = g^x.
37    pub fn verify(&self, transcript: &SchnorrTranscript, public_y: u64) -> bool {
38        let lhs = mod_exp(self.g, transcript.response, self.p);
39        let ye = mod_exp(public_y, transcript.challenge, self.p);
40        let rhs = (transcript.commitment as u128 * ye as u128 % self.p as u128) as u64;
41        lhs == rhs
42    }
43    /// Complete Schnorr proof-of-knowledge for secret x with randomness r and challenge e.
44    pub fn prove(&self, secret_x: u64, r: u64, challenge: u64) -> SchnorrTranscript {
45        let commitment = self.commit(r);
46        let response = self.respond(r, challenge, secret_x);
47        SchnorrTranscript {
48            commitment,
49            challenge,
50            response,
51        }
52    }
53}
54#[allow(dead_code)]
55#[derive(Debug, Clone, PartialEq)]
56pub enum MPCSecurityModel {
57    SemiHonest,
58    Malicious,
59    Covert,
60}
61/// Shamir (t, n) secret sharing over a prime field F_p.
62///
63/// # WARNING
64/// Educational implementation. Uses small prime fields, unsuitable for production.
65#[derive(Debug, Clone)]
66pub struct ShamirSS {
67    /// Field prime p
68    pub p: u64,
69    /// Threshold t: minimum shares needed to reconstruct
70    pub t: usize,
71    /// Total shares n
72    pub n: usize,
73}
74impl ShamirSS {
75    /// Split secret `s` into n shares using a random degree-(t-1) polynomial.
76    ///
77    /// Coefficients a[0]=s, a[1..t] come from `coeffs` (length t-1).
78    ///
79    /// # WARNING
80    /// In production, coefficients must be uniformly random elements of F_p.
81    pub fn split(&self, secret: u64, coeffs: &[u64]) -> Vec<(u64, u64)> {
82        assert_eq!(
83            coeffs.len(),
84            self.t - 1,
85            "Need exactly t-1 random coefficients"
86        );
87        (1..=(self.n as u64))
88            .map(|i| {
89                let mut val: u128 = secret as u128;
90                let mut ipow: u128 = i as u128;
91                for &c in coeffs {
92                    val = (val + c as u128 * ipow) % self.p as u128;
93                    ipow = ipow * i as u128 % self.p as u128;
94                }
95                (i, val as u64)
96            })
97            .collect()
98    }
99    /// Reconstruct secret from any t shares using Lagrange interpolation mod p.
100    ///
101    /// `shares`: slice of (x_i, y_i) pairs.
102    pub fn reconstruct(&self, shares: &[(u64, u64)]) -> u64 {
103        assert!(shares.len() >= self.t, "Need at least t shares");
104        let shares = &shares[..self.t];
105        let p = self.p;
106        let mut secret: i128 = 0;
107        for (j, &(xj, yj)) in shares.iter().enumerate() {
108            let mut num: i128 = 1;
109            let mut den: i128 = 1;
110            for (k, &(xk, _)) in shares.iter().enumerate() {
111                if k == j {
112                    continue;
113                }
114                num = num * (-(xk as i128)) % p as i128;
115                den = den * (xj as i128 - xk as i128) % p as i128;
116            }
117            let den_inv =
118                mod_inv(((den % p as i128 + p as i128) % p as i128) as u64, p).unwrap_or(0) as i128;
119            let lagrange = num * den_inv % p as i128;
120            secret = (secret + yj as i128 * lagrange) % p as i128;
121        }
122        ((secret % p as i128 + p as i128) % p as i128) as u64
123    }
124}
125/// Toy 1-of-2 Oblivious Transfer parameters.
126///
127/// Based on simplified Naor-Pinkas OT using DH assumptions.
128///
129/// # WARNING
130/// This is a simplified, non-secure educational sketch. Real OT requires
131/// careful implementation with secure group operations and hash functions.
132#[derive(Debug, Clone)]
133pub struct ToyOT {
134    /// DH group prime p
135    pub p: u64,
136    /// Generator g
137    pub g: u64,
138}
139impl ToyOT {
140    /// Sender setup: pick random a, publish c = g^a mod p.
141    pub fn sender_setup(&self, a: u64) -> u64 {
142        mod_exp(self.g, a, self.p)
143    }
144    /// Receiver message for choice bit b ∈ {0, 1}: picks k, sends pk_b = g^k,
145    /// sets pk_{1-b} = c / g^k (implicitly). Returns (pk0, pk1) for bit b.
146    pub fn receiver_choose(&self, c: u64, b: u8, k: u64) -> (u64, u64) {
147        let gk = mod_exp(self.g, k, self.p);
148        let gk_inv = mod_inv(gk, self.p).unwrap_or(1);
149        let other = (c as u128 * gk_inv as u128 % self.p as u128) as u64;
150        if b == 0 {
151            (gk, other)
152        } else {
153            (other, gk)
154        }
155    }
156    /// Receiver derives the shared key for their chosen bit b.
157    pub fn receiver_key(&self, c: u64, b: u8, k: u64) -> u64 {
158        let _ = b;
159        mod_exp(c, k, self.p)
160    }
161}
162/// Oblivious transfer (OT) protocol.
163#[allow(dead_code)]
164#[derive(Debug, Clone)]
165pub struct ObliviousTransfer {
166    pub variant: OTVariant,
167    pub security_parameter: usize,
168}
169#[allow(dead_code)]
170impl ObliviousTransfer {
171    pub fn new(variant: OTVariant, sec: usize) -> Self {
172        ObliviousTransfer {
173            variant,
174            security_parameter: sec,
175        }
176    }
177    pub fn is_fundamental(&self) -> bool {
178        matches!(self.variant, OTVariant::OneOutOfTwo)
179    }
180    pub fn n_messages(&self) -> usize {
181        match &self.variant {
182            OTVariant::OneOutOfTwo => 2,
183            OTVariant::OneOutOfN(n) => *n,
184            OTVariant::RandomOT => 2,
185        }
186    }
187}
188/// Extended Pedersen commitment with batch verification.
189///
190/// Supports committing to a vector of values and homomorphic operations.
191///
192/// # WARNING
193/// Educational only. Uses toy parameters.
194#[derive(Debug, Clone)]
195pub struct PedersenCommitment {
196    /// Prime field modulus
197    pub p: u64,
198    /// Group order (prime q | p-1)
199    pub q: u64,
200    /// Generator g
201    pub g: u64,
202    /// Independent generator h (log_g(h) unknown)
203    pub h: u64,
204}
205impl PedersenCommitment {
206    /// Commit: C = g^m * h^r mod p
207    pub fn commit(&self, m: u64, r: u64) -> u64 {
208        let gm = mod_exp(self.g, m % self.q, self.p);
209        let hr = mod_exp(self.h, r % self.q, self.p);
210        (gm as u128 * hr as u128 % self.p as u128) as u64
211    }
212    /// Verify opening: check C == g^m * h^r mod p
213    pub fn verify(&self, c: u64, m: u64, r: u64) -> bool {
214        self.commit(m, r) == c
215    }
216    /// Homomorphic add: Commit(m1,r1) * Commit(m2,r2) = Commit(m1+m2, r1+r2)
217    pub fn add(&self, c1: u64, c2: u64) -> u64 {
218        (c1 as u128 * c2 as u128 % self.p as u128) as u64
219    }
220    /// Batch commit: commit to a vector of (value, randomness) pairs.
221    /// Returns a vector of commitments.
222    pub fn batch_commit(&self, pairs: &[(u64, u64)]) -> Vec<u64> {
223        pairs.iter().map(|&(m, r)| self.commit(m, r)).collect()
224    }
225}
226/// Zero-knowledge proof system.
227#[allow(dead_code)]
228#[derive(Debug, Clone)]
229pub struct ZKProofSystem {
230    pub name: String,
231    pub is_interactive: bool,
232    pub soundness_error: f64,
233    pub completeness_error: f64,
234    pub proof_size_bytes: Option<usize>,
235}
236#[allow(dead_code)]
237impl ZKProofSystem {
238    pub fn new(name: &str, interactive: bool) -> Self {
239        ZKProofSystem {
240            name: name.to_string(),
241            is_interactive: interactive,
242            soundness_error: 0.5,
243            completeness_error: 0.0,
244            proof_size_bytes: None,
245        }
246    }
247    pub fn schnorr() -> Self {
248        let mut s = ZKProofSystem::new("Schnorr", true);
249        s.soundness_error = 1.0 / 2.0_f64.powi(128);
250        s.proof_size_bytes = Some(64);
251        s
252    }
253    pub fn groth16() -> Self {
254        let mut s = ZKProofSystem::new("Groth16", false);
255        s.soundness_error = 1.0 / 2.0_f64.powi(128);
256        s.proof_size_bytes = Some(128);
257        s
258    }
259    pub fn plonk() -> Self {
260        let mut s = ZKProofSystem::new("PLONK", false);
261        s.soundness_error = 1.0 / 2.0_f64.powi(128);
262        s.proof_size_bytes = Some(640);
263        s
264    }
265    pub fn is_non_interactive(&self) -> bool {
266        !self.is_interactive
267    }
268    pub fn is_succinct(&self) -> bool {
269        matches!(self.proof_size_bytes, Some(n) if n < 1000)
270    }
271}
272/// Extended Shamir secret sharing with explicit reconstruction from arbitrary shares.
273///
274/// # WARNING
275/// Educational only. Small prime field.
276#[derive(Debug, Clone)]
277pub struct ShamirSecretSharingExtended {
278    /// Prime field modulus
279    pub p: u64,
280    /// Threshold t
281    pub t: usize,
282    /// Number of shares n
283    pub n: usize,
284}
285impl ShamirSecretSharingExtended {
286    /// Share the secret using the provided polynomial coefficients a[1..t].
287    /// Returns n shares as (x_i, y_i) pairs.
288    pub fn share(&self, secret: u64, coeffs: &[u64]) -> Vec<(u64, u64)> {
289        assert_eq!(coeffs.len(), self.t - 1, "Need t-1 coefficients");
290        (1..=self.n as u64)
291            .map(|i| {
292                let mut val: u128 = secret as u128 % self.p as u128;
293                let mut ipow: u128 = i as u128;
294                for &c in coeffs {
295                    val = (val + c as u128 % self.p as u128 * ipow) % self.p as u128;
296                    ipow = ipow * i as u128 % self.p as u128;
297                }
298                (i, val as u64)
299            })
300            .collect()
301    }
302    /// Reconstruct from any t shares using Lagrange interpolation mod p.
303    pub fn reconstruct(&self, shares: &[(u64, u64)]) -> u64 {
304        assert!(shares.len() >= self.t);
305        let shares = &shares[..self.t];
306        let p = self.p;
307        let mut acc: i128 = 0;
308        for (j, &(xj, yj)) in shares.iter().enumerate() {
309            let mut num: i128 = 1;
310            let mut den: i128 = 1;
311            for (k, &(xk, _)) in shares.iter().enumerate() {
312                if k == j {
313                    continue;
314                }
315                num = num * (p as i128 - xk as i128) % p as i128;
316                den = den * ((xj as i128 - xk as i128).rem_euclid(p as i128)) % p as i128;
317            }
318            let den_pos = den.rem_euclid(p as i128) as u64;
319            let inv = mod_inv(den_pos, p).unwrap_or(0) as i128;
320            let lagrange = num % p as i128 * inv % p as i128;
321            acc = (acc + yj as i128 * lagrange % p as i128).rem_euclid(p as i128);
322        }
323        acc as u64
324    }
325}
326/// A simple garbled AND/OR gate simulator.
327///
328/// Labels for 0 and 1 on each wire are represented as u64 tokens.
329/// The garbled table encrypts the output label for each input pair.
330///
331/// # WARNING
332/// Educational sketch only. Real garbling uses AES-based encryption.
333#[derive(Debug, Clone)]
334pub struct GarbledGate {
335    /// Garbled table: indexed [a_bit][b_bit] → output label
336    pub table: [[u64; 2]; 2],
337    /// Labels for wire A: [label_0, label_1]
338    pub labels_a: [u64; 2],
339    /// Labels for wire B: [label_0, label_1]
340    pub labels_b: [u64; 2],
341    /// Labels for output wire: [label_0, label_1]
342    pub labels_out: [u64; 2],
343}
344impl GarbledGate {
345    /// Build a garbled AND gate.
346    /// labels_a, labels_b, labels_out: [label_for_0, label_for_1] on each wire.
347    pub fn garble_and(labels_a: [u64; 2], labels_b: [u64; 2], labels_out: [u64; 2]) -> Self {
348        let mut table = [[0u64; 2]; 2];
349        for a in 0usize..2 {
350            for b in 0usize..2 {
351                let out_bit = a & b;
352                table[a][b] = toy_encrypt(labels_a[a], labels_b[b], labels_out[out_bit]);
353            }
354        }
355        GarbledGate {
356            table,
357            labels_a,
358            labels_b,
359            labels_out,
360        }
361    }
362    /// Build a garbled OR gate.
363    pub fn garble_or(labels_a: [u64; 2], labels_b: [u64; 2], labels_out: [u64; 2]) -> Self {
364        let mut table = [[0u64; 2]; 2];
365        for a in 0usize..2 {
366            for b in 0usize..2 {
367                let out_bit = a | b;
368                table[a][b] = toy_encrypt(labels_a[a], labels_b[b], labels_out[out_bit]);
369            }
370        }
371        GarbledGate {
372            table,
373            labels_a,
374            labels_b,
375            labels_out,
376        }
377    }
378    /// Evaluate the garbled gate: given input labels, recover the output label.
379    pub fn evaluate(&self, label_a: u64, label_b: u64) -> Option<u64> {
380        for a in 0usize..2 {
381            for b in 0usize..2 {
382                if self.labels_a[a] == label_a && self.labels_b[b] == label_b {
383                    let out = toy_encrypt(label_a, label_b, self.table[a][b]);
384                    return Some(out);
385                }
386            }
387        }
388        None
389    }
390    /// Check if the recovered output label corresponds to output value 1.
391    pub fn is_output_one(&self, output_label: u64) -> bool {
392        output_label == self.labels_out[1]
393    }
394}
395/// Pedersen commitment parameters: group order q, generators g and h.
396///
397/// Commit(m, r) = g^m * h^r mod p.
398/// - Perfectly hiding
399/// - Computationally binding (under DL assumption)
400///
401/// # WARNING
402/// Educational toy with tiny parameters.
403#[derive(Debug, Clone)]
404pub struct PedersenParams {
405    /// Prime modulus p
406    pub p: u64,
407    /// Group order q (prime, q | p-1)
408    pub q: u64,
409    /// First generator g
410    pub g: u64,
411    /// Second independent generator h (log_g(h) unknown to committer)
412    pub h: u64,
413}
414impl PedersenParams {
415    /// Create a commitment to value m with randomness r.
416    pub fn commit(&self, m: u64, r: u64) -> u64 {
417        let gm = mod_exp(self.g, m % self.q, self.p);
418        let hr = mod_exp(self.h, r % self.q, self.p);
419        (gm as u128 * hr as u128 % self.p as u128) as u64
420    }
421    /// Verify that commitment c opens to (m, r).
422    pub fn verify(&self, c: u64, m: u64, r: u64) -> bool {
423        self.commit(m, r) == c
424    }
425    /// Homomorphic addition: Commit(m1+m2, r1+r2) = Commit(m1,r1) * Commit(m2,r2) mod p.
426    pub fn add_commitments(&self, c1: u64, c2: u64) -> u64 {
427        (c1 as u128 * c2 as u128 % self.p as u128) as u64
428    }
429}
430/// A toy 2-party XOR secret-sharing based MPC for boolean functions.
431///
432/// Each party holds a share; shares XOR to the actual value.
433/// Only XOR gates can be computed locally; AND requires interaction.
434///
435/// # WARNING
436/// Educational GMW-style sketch. Not secure or complete.
437#[derive(Debug, Clone)]
438pub struct MpcShare {
439    /// Party index (0 or 1)
440    pub party: u8,
441    /// Boolean share of some wire value
442    pub share: bool,
443}
444impl MpcShare {
445    /// XOR gate: locally XOR the two shares.
446    pub fn xor_gate(a: &MpcShare, b: &MpcShare) -> MpcShare {
447        assert_eq!(a.party, b.party);
448        MpcShare {
449            party: a.party,
450            share: a.share ^ b.share,
451        }
452    }
453    /// Reconstruct wire value from two parties' shares.
454    pub fn reconstruct(s0: &MpcShare, s1: &MpcShare) -> bool {
455        s0.share ^ s1.share
456    }
457}
458#[allow(dead_code)]
459#[derive(Debug, Clone, PartialEq)]
460pub enum OTVariant {
461    OneOutOfTwo,
462    OneOutOfN(usize),
463    RandomOT,
464}
465/// Commitment scheme (hiding and binding).
466#[allow(dead_code)]
467#[derive(Debug, Clone)]
468pub struct CommitmentScheme {
469    pub name: String,
470    pub is_perfectly_hiding: bool,
471    pub is_computationally_binding: bool,
472    pub is_homomorphic: bool,
473}
474#[allow(dead_code)]
475impl CommitmentScheme {
476    pub fn new(name: &str) -> Self {
477        CommitmentScheme {
478            name: name.to_string(),
479            is_perfectly_hiding: false,
480            is_computationally_binding: false,
481            is_homomorphic: false,
482        }
483    }
484    pub fn pedersen() -> Self {
485        CommitmentScheme {
486            name: "Pedersen".to_string(),
487            is_perfectly_hiding: true,
488            is_computationally_binding: true,
489            is_homomorphic: true,
490        }
491    }
492    pub fn sha256_hash() -> Self {
493        CommitmentScheme {
494            name: "SHA256-hash".to_string(),
495            is_perfectly_hiding: false,
496            is_computationally_binding: true,
497            is_homomorphic: false,
498        }
499    }
500    pub fn satisfies_binding_hiding_tradeoff(&self) -> bool {
501        !self.is_perfectly_hiding || !self.is_computationally_binding
502    }
503}
504/// Multiparty computation (MPC) protocol.
505#[allow(dead_code)]
506#[derive(Debug, Clone)]
507pub struct MPCProtocol {
508    pub name: String,
509    pub n_parties: usize,
510    pub threshold_corruption: usize,
511    pub security_model: MPCSecurityModel,
512}
513#[allow(dead_code)]
514impl MPCProtocol {
515    pub fn new(name: &str, n: usize, t: usize, model: MPCSecurityModel) -> Self {
516        MPCProtocol {
517            name: name.to_string(),
518            n_parties: n,
519            threshold_corruption: t,
520            security_model: model,
521        }
522    }
523    pub fn bgw(n: usize, t: usize) -> Self {
524        MPCProtocol::new("BGW", n, t, MPCSecurityModel::Malicious)
525    }
526    pub fn is_secure_against_majority_corruption(&self) -> bool {
527        self.threshold_corruption * 2 < self.n_parties
528    }
529    pub fn is_optimal_corruption_threshold(&self) -> bool {
530        match self.security_model {
531            MPCSecurityModel::Malicious => self.threshold_corruption * 3 < self.n_parties,
532            MPCSecurityModel::SemiHonest => self.threshold_corruption * 2 < self.n_parties,
533            MPCSecurityModel::Covert => self.threshold_corruption * 2 < self.n_parties,
534        }
535    }
536}
537/// Secret sharing scheme (Shamir's).
538#[allow(dead_code)]
539#[derive(Debug, Clone)]
540pub struct SecretSharing {
541    pub threshold: usize,
542    pub n_shares: usize,
543    pub field_size_bits: usize,
544}
545#[allow(dead_code)]
546impl SecretSharing {
547    pub fn new(t: usize, n: usize, field_bits: usize) -> Self {
548        assert!(t <= n);
549        SecretSharing {
550            threshold: t,
551            n_shares: n,
552            field_size_bits: field_bits,
553        }
554    }
555    pub fn shamir_2_of_3() -> Self {
556        SecretSharing::new(2, 3, 256)
557    }
558    pub fn is_perfect(&self) -> bool {
559        true
560    }
561    pub fn min_shares_needed(&self) -> usize {
562        self.threshold
563    }
564    pub fn share_size_bits(&self) -> usize {
565        self.field_size_bits
566    }
567}
568/// A Schnorr proof transcript (commitment, challenge, response).
569#[derive(Debug, Clone, PartialEq, Eq)]
570pub struct SchnorrTranscript {
571    /// Commitment: a = g^r mod p
572    pub commitment: u64,
573    /// Challenge: e (random element in Z_q)
574    pub challenge: u64,
575    /// Response: z = r + e*x mod q
576    pub response: u64,
577}
578/// Toy Paillier encryption over Z_n^2 with homomorphic addition.
579///
580/// For small n = p*q; the real scheme requires RSA-sized moduli (2048+ bits).
581///
582/// # WARNING
583/// Educational only. The small parameters make this completely insecure.
584#[derive(Debug, Clone)]
585pub struct PaillierHomomorphic {
586    /// n = p*q (RSA modulus; tiny here for education)
587    pub n: u64,
588    /// n^2 = n*n
589    pub n_sq: u128,
590    /// Public generator g (usually g = n+1)
591    pub g: u64,
592    /// λ = lcm(p-1, q-1) (private key component)
593    pub lambda: u64,
594    /// μ = (L(g^λ mod n^2))^{-1} mod n where L(x) = (x-1)/n
595    pub mu: u64,
596}
597impl PaillierHomomorphic {
598    /// Encrypt plaintext m ∈ [0, n) with randomness r (gcd(r,n)=1).
599    /// Enc(m, r) = g^m * r^n mod n^2
600    pub fn encrypt(&self, m: u64, r: u64) -> u128 {
601        let n_sq = self.n_sq;
602        let gm = {
603            let base = (self.g as u128).pow(1) % n_sq;
604            let mut result: u128 = 1;
605            let mut exp = m;
606            let mut b = self.g as u128 % n_sq;
607            while exp > 0 {
608                if exp & 1 == 1 {
609                    result = result * b % n_sq;
610                }
611                exp >>= 1;
612                b = b * b % n_sq;
613            }
614            let _ = base;
615            result
616        };
617        let rn = {
618            let mut result: u128 = 1;
619            let mut exp = self.n;
620            let mut b = r as u128 % n_sq;
621            while exp > 0 {
622                if exp & 1 == 1 {
623                    result = result * b % n_sq;
624                }
625                exp >>= 1;
626                b = b * b % n_sq;
627            }
628            result
629        };
630        gm * rn % n_sq
631    }
632    /// Homomorphic addition: Enc(m1) * Enc(m2) mod n^2 = Enc(m1+m2).
633    pub fn add_ciphertexts(&self, c1: u128, c2: u128) -> u128 {
634        c1 * c2 % self.n_sq
635    }
636    /// L function: L(x) = (x - 1) / n
637    fn l_func(&self, x: u128) -> u64 {
638        ((x - 1) / self.n as u128) as u64
639    }
640    /// Decrypt ciphertext c: m = L(c^λ mod n^2) * μ mod n
641    pub fn decrypt(&self, c: u128) -> u64 {
642        let n_sq = self.n_sq;
643        let mut result: u128 = 1;
644        let mut exp = self.lambda;
645        let mut b = c % n_sq;
646        while exp > 0 {
647            if exp & 1 == 1 {
648                result = result * b % n_sq;
649            }
650            exp >>= 1;
651            b = b * b % n_sq;
652        }
653        let lval = self.l_func(result);
654        (lval as u128 * self.mu as u128 % self.n as u128) as u64
655    }
656}
657/// Simplified Chaum blind signature protocol over Z_p*.
658///
659/// Protocol:
660/// 1. Signer has key (d, e, n): d=private, e=public, n=modulus (RSA-like but tiny)
661/// 2. User blinds message m: c = r^e * m mod n (r is blinding factor)
662/// 3. Signer signs blinded message: s' = c^d mod n
663/// 4. User unblinds: s = s' * r^{-1} mod n
664/// 5. Verify: s^e = m mod n
665///
666/// # WARNING
667/// Educational only. Real blind RSA requires SHA-based full-domain hash + PKCS1v2.1.
668#[derive(Debug, Clone)]
669pub struct BlindSignatureScheme {
670    /// RSA-like modulus n = p*q (tiny, insecure)
671    pub n: u64,
672    /// Public exponent e
673    pub e: u64,
674    /// Private exponent d (e*d ≡ 1 mod λ(n))
675    pub d: u64,
676}
677impl BlindSignatureScheme {
678    /// User blinds message m with factor r: returns blinded = r^e * m mod n.
679    pub fn blind(&self, m: u64, r: u64) -> u64 {
680        let re = mod_exp(r, self.e, self.n);
681        (re as u128 * m as u128 % self.n as u128) as u64
682    }
683    /// Signer signs blinded message: s_prime = blinded^d mod n.
684    pub fn sign_blinded(&self, blinded: u64) -> u64 {
685        mod_exp(blinded, self.d, self.n)
686    }
687    /// User unblinds: s = s_prime * r^{-1} mod n.
688    pub fn unblind(&self, s_prime: u64, r: u64) -> u64 {
689        let r_inv = mod_inv(r, self.n).unwrap_or(1);
690        (s_prime as u128 * r_inv as u128 % self.n as u128) as u64
691    }
692    /// Verify signature: check s^e ≡ m (mod n).
693    pub fn verify(&self, m: u64, s: u64) -> bool {
694        mod_exp(s, self.e, self.n) == m % self.n
695    }
696}