wamu_core/
share.rs

1//! Secret share and "sub-share" types, abstractions and utilities.
2
3use crypto_bigint::modular::constant_mod::ResidueParams;
4use crypto_bigint::{const_residue, U256};
5use zeroize::{Zeroize, ZeroizeOnDrop};
6
7use crate::crypto::{Random32Bytes, Secp256k1Order};
8use crate::errors::{ArithmeticError, Error};
9
10/// A "secret share" as defined by the Wamu protocol.
11///
12/// Ref: <https://wamu.tech/specification#share-splitting-and-reconstruction>.
13#[derive(Zeroize, ZeroizeOnDrop)]
14pub struct SecretShare([u8; 32]);
15
16impl From<Random32Bytes> for SecretShare {
17    /// Converts `Random32Bytes` into a "secret share".
18    fn from(value: Random32Bytes) -> Self {
19        Self(value.to_be_bytes())
20    }
21}
22
23impl From<U256> for SecretShare {
24    /// Converts a U256 into a "secret share".
25    fn from(value: U256) -> Self {
26        Self(Random32Bytes::from(value).to_be_bytes())
27    }
28}
29
30impl SecretShare {
31    /// Returns the underlying `U256` for "secret share".
32    pub fn as_u256(&self) -> U256 {
33        U256::from_be_slice(&self.0)
34    }
35
36    /// Returns 32 bytes representation of the "secret share".
37    pub fn to_be_bytes(&self) -> [u8; 32] {
38        self.0
39    }
40}
41
42impl TryFrom<&[u8]> for SecretShare {
43    type Error = Error;
44
45    /// Converts a slice of bytes into a "secret share".
46    fn try_from(slice: &[u8]) -> Result<Self, Self::Error> {
47        Ok(Self(Random32Bytes::try_from(slice)?.to_be_bytes()))
48    }
49}
50
51/// A "signing share" as defined by the Wamu protocol.
52///
53/// Ref: <https://wamu.tech/specification#share-splitting-and-reconstruction>.
54#[derive(Clone, Zeroize, ZeroizeOnDrop)]
55pub struct SigningShare([u8; 32]);
56
57impl SigningShare {
58    /// Generates a new "signing share" as a random 256 bit unsigned integer.
59    pub fn generate() -> Self {
60        Self(Random32Bytes::generate().to_be_bytes())
61    }
62
63    /// Returns underlying 32 bytes for "signing share".
64    pub fn to_be_bytes(&self) -> [u8; 32] {
65        self.0
66    }
67}
68
69impl From<Random32Bytes> for SigningShare {
70    /// Converts `Random32Bytes` into a "signing share".
71    fn from(value: Random32Bytes) -> Self {
72        Self(value.to_be_bytes())
73    }
74}
75
76impl TryFrom<&[u8]> for SigningShare {
77    type Error = Error;
78
79    /// Converts a slice of bytes into a "signing share".
80    fn try_from(slice: &[u8]) -> Result<Self, Self::Error> {
81        // Input slice must be 32 bytes long.
82        if slice.len() == 32 {
83            Ok(Self(slice.try_into().map_err(|_| Error::Encoding)?))
84        } else {
85            Err(Error::Encoding)
86        }
87    }
88}
89
90/// A "sub-share" as defined by the Wamu protocol.
91///
92/// Ref: <https://wamu.tech/specification#share-splitting-and-reconstruction>.
93#[derive(Clone, PartialEq, Eq, Zeroize, ZeroizeOnDrop)]
94pub struct SubShare {
95    x: U256,
96    y: U256,
97}
98
99impl SubShare {
100    /// Initializes a new "sub-share".
101    pub fn new(x: U256, y: U256) -> Result<Self, ArithmeticError> {
102        // `x` or `y` coordinates must be less than the order of the `Secp256k1` curve.
103        if x < Secp256k1Order::MODULUS && y < Secp256k1Order::MODULUS {
104            Ok(Self { x, y })
105        } else {
106            Err(ArithmeticError::ModulusOverflow)
107        }
108    }
109
110    /// Returns the `x` coordinate of the "sub-share".
111    pub fn x(&self) -> U256 {
112        self.x
113    }
114
115    /// Returns the `y` coordinate of the "sub-share".
116    pub fn y(&self) -> U256 {
117        self.y
118    }
119
120    /// Returns the `x` and `y` coordinates of the "sub-share" as an (`x`, `y`) tuple.
121    pub fn as_tuple(&self) -> (U256, U256) {
122        (self.x, self.y)
123    }
124}
125
126#[derive(Zeroize, ZeroizeOnDrop)]
127pub struct SubShareInterpolator {
128    gradient: U256,
129    intercept: U256,
130}
131
132impl SubShareInterpolator {
133    /// Given 2 "sub-shares" A and B, returns a "sub-share" interpolator.
134    ///
135    /// i.e a line (a polynomial of degree 1) such that A and B are both points on the line.
136    pub fn new(point_a: &SubShare, point_b: &SubShare) -> Self {
137        // dy/dx (mod q) is equivalent to dy * i where i is the modular multiplicative inverse of dx such that dx * i  ≡ 1 (mod q).
138        // Ref: <http://en.wikipedia.org/wiki/Modular_multiplicative_inverse#Computation>.
139        // NOTE: Since q is prime, gcd(dx, q) = 1, so a modular multiplicative inverse always exists and
140        // is equivalent to the Bézout's identity coefficient for dx.
141        // Ref: <https://en.wikipedia.org/wiki/B%C3%A9zout%27s_identity>.
142        let x_1 = point_a.x;
143        let y_1 = point_a.y;
144        let x_2 = point_b.x;
145        let y_2 = point_b.y;
146        let dy = const_residue!(y_1, Secp256k1Order) - const_residue!(y_2, Secp256k1Order);
147        let dx = const_residue!(x_1, Secp256k1Order) - const_residue!(x_2, Secp256k1Order);
148        let gradient = dy * dx.invert().0;
149
150        // From y = mx + c (mod q), we compute the intercept c = y - mx (mod q).
151        let intercept_mod =
152            const_residue!(y_1, Secp256k1Order) - (gradient * const_residue!(x_1, Secp256k1Order));
153
154        Self {
155            gradient: gradient.retrieve(),
156            intercept: intercept_mod.retrieve(),
157        }
158    }
159
160    /// Returns "secret share" for given "sub-shares".
161    pub fn secret(&self) -> U256 {
162        self.intercept
163    }
164
165    /// Returns a unique "sub-share" for the index.
166    pub fn sub_share(&self, idx: U256) -> Result<SubShare, ArithmeticError> {
167        // The "index" should be:
168        // - less than the order of the `Secp256k1` curve.
169        // - greater than zero (because the "sub-share" associated with the zero "index" is the "secret share").
170        if idx < Secp256k1Order::MODULUS && U256::ZERO < idx {
171            // Calculates the y-coordinate of the "sub-share".
172            let gradient = self.gradient;
173            let intercept = self.intercept;
174            let y_coord = (const_residue!(gradient, Secp256k1Order)
175                * const_residue!(idx, Secp256k1Order))
176                + const_residue!(intercept, Secp256k1Order);
177
178            Ok(SubShare {
179                x: idx,
180                y: y_coord.retrieve(),
181            })
182        } else {
183            Err(ArithmeticError::ModulusOverflow)
184        }
185    }
186}
187
188#[cfg(test)]
189mod tests {
190    use super::*;
191
192    #[test]
193    fn sub_share_interpolator_works() {
194        // Take line, y = x + 1 (mod q).
195        // The "secret share" is 1 i.e at index 0, x = 0 and y = 1.
196        let secret_share = U256::ONE;
197        let sub_share_0 = SubShare::new(U256::ZERO, secret_share).unwrap();
198
199        // The "sub-share" at index 1 is (1, 2) i.e when x = 1 and y = 2.
200        let sub_share_1 = SubShare::new(U256::ONE, U256::from(2u8)).unwrap();
201
202        // Initializes the "sub-share" interpolator for share splitting with "sub-shares" at index 0 and 1.
203        let split_sub_share_interpolator = SubShareInterpolator::new(&sub_share_0, &sub_share_1);
204
205        // The "sub-share" at index 2 is (2, 3),i.e when x = 2, y = 3.
206        let sub_share_2 = SubShare::new(U256::from(2u8), U256::from(3u8)).unwrap();
207
208        // Verify that the "sub-share" interpolator returns the right "sub-share" at index 2.
209        assert_eq!(
210            split_sub_share_interpolator
211                .sub_share(U256::from(2u8))
212                .unwrap()
213                .as_tuple(),
214            sub_share_2.as_tuple()
215        );
216
217        // Initializes the "sub-share" interpolator for share reconstruction with "sub-shares" at index 1 and 2.
218        let reconstruct_sub_share_interpolator =
219            SubShareInterpolator::new(&sub_share_1, &sub_share_2);
220
221        // Verify that the "sub-share" interpolator returns the right "secret share".
222        assert_eq!(&reconstruct_sub_share_interpolator.secret(), &secret_share);
223    }
224}