Skip to main content

confium_tc_gg18/
mta.rs

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