Skip to main content

confium_tc_cmp20/
mta.rs

1//! Multiplicative-to-Additive (MtA) sub-round.
2//!
3//! CMP20 signing needs to convert, for every pair `(i, j)`, the product
4//! `k_i * x_j` into additive shares `alpha_{ij}` (held by `i`) and
5//! `beta_{ji}` (held by `j`) such that `alpha_{ij} + beta_{ji} = k_i x_j`.
6//!
7//! The cryptographic way is Paillier homomorphic encryption (CMP20's
8//! "key generation" already produces a Paillier keypair per party).
9//! This crate does not depend on a Paillier backend, so the MtA is
10//! computed **in the clear** inside the trusted test harness:
11//! `alpha_{ij} = 0` and `beta_{ji} = k_i x_j`. The arithmetic outcome
12//! is identical to a real MtA; only the cryptographic hiding is lost.
13//! See the crate-level docs for what a production replacement requires.
14//!
15//! CMP20 folds the MtA products into the nonce-reveal round (round 2),
16//! collapsing what would be two separate GG18 sub-rounds into one.
17
18use p256::Scalar;
19
20/// Collected per-party inputs for one signing session's MtA products.
21#[derive(Clone, Debug)]
22pub struct MtaInputs {
23    pub ks: Vec<Scalar>,
24    pub xs: Vec<Scalar>,
25    pub indices: Vec<u64>,
26}
27
28impl MtaInputs {
29    /// Compute additive MtA products for every ordered pair `(i, j)`.
30    ///
31    /// Simplified path: `alphas[i][j] = 0`, `betas[j][i] = k_i * x_j`.
32    pub fn products(&self) -> (Vec<Vec<Scalar>>, Vec<Vec<Scalar>>) {
33        let n = self.ks.len();
34        let alphas = vec![vec![Scalar::ZERO; n]; n];
35        let mut betas = vec![vec![Scalar::ZERO; n]; n];
36        // Indexed matrix access (betas[j][i]) is clearer here than
37        // an iterator rewrite; suppress the needless_range_loop hint.
38        #[allow(clippy::needless_range_loop)]
39        for i in 0..n {
40            #[allow(clippy::needless_range_loop)]
41            for j in 0..n {
42                if i == j {
43                    continue;
44                }
45                betas[j][i] = self.ks[i] * self.xs[j];
46            }
47        }
48        (alphas, betas)
49    }
50}
51
52/// Party `i`'s total MtA adjustment:
53/// `delta_i = k_i x_i + sum_{j != i} (alphas[j][i] + betas[i][j])`.
54pub fn party_mta_sum(
55    i: usize,
56    ks: &[Scalar],
57    xs: &[Scalar],
58    alphas: &[Vec<Scalar>],
59    betas: &[Vec<Scalar>],
60) -> Scalar {
61    let n = ks.len();
62    let mut acc = ks[i] * xs[i];
63    for j in 0..n {
64        if j == i {
65            continue;
66        }
67        acc += alphas[j][i];
68        acc += betas[i][j];
69    }
70    acc
71}
72
73#[cfg(test)]
74mod tests {
75    use super::*;
76
77    #[test]
78    fn mta_products_sum_to_ki_xj() {
79        let ks = vec![Scalar::from(2u64), Scalar::from(3u64), Scalar::from(5u64)];
80        let xs = vec![Scalar::from(7u64), Scalar::from(11u64), Scalar::from(13u64)];
81        let inputs = MtaInputs {
82            ks: ks.clone(),
83            xs: xs.clone(),
84            indices: vec![1, 2, 3],
85        };
86        let (alphas, betas) = inputs.products();
87        for i in 0..3 {
88            for j in 0..3 {
89                if i == j {
90                    continue;
91                }
92                let sum = alphas[i][j] + betas[j][i];
93                assert_eq!(sum, ks[i] * xs[j], "pair ({}, {})", i, j);
94            }
95        }
96    }
97
98    #[test]
99    fn party_mta_sum_matches_xi_sum_kj() {
100        let ks = vec![Scalar::from(2u64), Scalar::from(3u64), Scalar::from(5u64)];
101        let xs = vec![Scalar::from(7u64), Scalar::from(11u64), Scalar::from(13u64)];
102        let inputs = MtaInputs {
103            ks: ks.clone(),
104            xs: xs.clone(),
105            indices: vec![1, 2, 3],
106        };
107        let (alphas, betas) = inputs.products();
108        let k_sum: Scalar = ks.iter().copied().fold(Scalar::ZERO, |a, b| a + b);
109        for i in 0..3 {
110            let got = party_mta_sum(i, &ks, &xs, &alphas, &betas);
111            assert_eq!(got, xs[i] * k_sum, "party {}", i);
112        }
113    }
114}