umbral_pre/
evidence.rs

1use alloc::string::{String, ToString};
2
3use sha2::digest::Digest;
4
5#[cfg(feature = "serde")]
6use serde::{Deserialize, Serialize};
7
8use crate::curve::CurvePoint;
9use crate::hashing::BackendDigestOutput;
10use crate::hashing_ds::{hash_to_cfrag_verification, kfrag_signature_message};
11use crate::keys::digest_for_signing;
12use crate::params::Parameters;
13use crate::{Capsule, PublicKey, VerifiedCapsuleFrag};
14
15// These imports are only used in docstrings.
16#[cfg(docsrs)]
17use crate::CapsuleFrag;
18
19#[cfg(feature = "default-serialization")]
20use crate::{DefaultDeserialize, DefaultSerialize};
21
22/// A collection of data to prove the validity of reencryption.
23///
24/// In combination with the return values of [`Capsule::to_bytes_simple`] and
25/// [`CapsuleFrag::to_bytes_simple`], it can be used to perform the following checks:
26///
27/// 1. Check that Alice's verifying key (or the corresponding Ethereum address)
28///    can be derived from `CapsuleFrag::kfrag_signature`, `kfrag_validity_message_hash`, and
29///    the recovery byte `kfrag_signature_v` (`true` corresponds to `0x01` and `false` to `0x00`).
30///
31/// 2. Zero-knowledge verification (performed in [`CapsuleFrag::verify`]):
32///    - `z * e == h * e1 + e2` (correct re-encryption of `e`);
33///    - `z * v == h * v1 + v2` (correct re-encryption of `v`);
34///    - `z * u == h * u1 + u2` (correct re-encryption key commitment).
35///
36/// Here `z == CapsuleFrag::signature`, `u` is the constant scheme parameter
37/// (can be hardcoded in the contract performing the check, see [`Parameters::u`]
38/// for the value), `e` and `v` are from [`Capsule::to_bytes_simple`],
39/// and `e1`, `e2`, `v1`, `v2`, `u1`, `u2` are from [`CapsuleFrag::to_bytes_simple`].
40///
41/// The serialized capsule and cfrag have these points in the compressed form, so this struct
42/// provides both coordinates to let the user avoid uncompressing the point.
43/// Instead one can just check that the the `y` coordinate corresponds to the sign
44/// in the compressed point, and that the whole point is on the curve.
45///
46/// `h` is the challenge scalar, see [`hash_to_cfrag_verification`]
47/// for the details on how to reproduce its calculation.
48#[derive(Clone, Debug)]
49#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
50pub struct ReencryptionEvidence {
51    /// Same as `e` in [`Capsule::to_bytes_simple`].
52    pub e: CurvePoint,
53    /// Precalculated `z * e`, where `z == CapsuleFrag::signature`
54    /// in [`CapsuleFrag::to_bytes_simple`].
55    pub ez: CurvePoint,
56    /// Same as `e1` in [`CapsuleFrag::to_bytes_simple`].
57    pub e1: CurvePoint,
58    /// Precalculated `h * e1`, where `h` is obtained from [`hash_to_cfrag_verification`].
59    pub e1h: CurvePoint,
60    /// Same as `e2` in [`CapsuleFrag::to_bytes_simple`].
61    pub e2: CurvePoint,
62    /// Same as `v` in [`Capsule::to_bytes_simple`].
63    pub v: CurvePoint,
64    /// Precalculated `z * v`, where `z == CapsuleFrag::signature`
65    /// in [`CapsuleFrag::to_bytes_simple`].
66    pub vz: CurvePoint,
67    /// Same as `v1` in [`CapsuleFrag::to_bytes_simple`].
68    pub v1: CurvePoint,
69    /// Precalculated `h * v1`, where `h` is obtained from [`hash_to_cfrag_verification`].
70    pub v1h: CurvePoint,
71    /// Same as `v2` in [`CapsuleFrag::to_bytes_simple`].
72    pub v2: CurvePoint,
73    /// Precalculated `z * u`, where `z == CapsuleFrag::signature`
74    /// in [`CapsuleFrag::to_bytes_simple`], and `u` is [`Parameters::u`].
75    pub uz: CurvePoint,
76    /// Same as `u1` in [`CapsuleFrag::to_bytes_simple`].
77    pub u1: CurvePoint,
78    /// Precalculated `h * u1`, where `h` is obtained from [`hash_to_cfrag_verification`].
79    pub u1h: CurvePoint,
80    /// Same as `u2` in [`CapsuleFrag::to_bytes_simple`].
81    pub u2: CurvePoint,
82    /// The hashed message used to create `kfrag_signature` in
83    /// [`CapsuleFrag::to_bytes_simple`].
84    #[cfg_attr(feature = "serde", serde(with = "crate::serde_bytes::as_hex"))]
85    pub kfrag_validity_message_hash: BackendDigestOutput,
86    /// The recovery byte corresponding to `kfrag_signature` in [`CapsuleFrag::to_bytes_simple`]
87    /// (`true` corresponds to `0x01` and `false` to `0x00`).
88    pub kfrag_signature_v: bool,
89}
90
91impl ReencryptionEvidence {
92    /// Creates the new evidence given the capsule and the reencrypted capsule frag.
93    pub fn new(
94        capsule: &Capsule,
95        vcfrag: &VerifiedCapsuleFrag,
96        verifying_pk: &PublicKey,
97        delegating_pk: &PublicKey,
98        receiving_pk: &PublicKey,
99    ) -> Result<Self, String> {
100        let params = Parameters::new();
101
102        let cfrag = vcfrag.clone().unverify();
103
104        let u = params.u;
105        let u1 = cfrag.proof.kfrag_commitment;
106        let u2 = cfrag.proof.kfrag_pok;
107
108        let h = hash_to_cfrag_verification(
109            &capsule.point_e,
110            &cfrag.point_e1,
111            &cfrag.proof.point_e2,
112            &capsule.point_v,
113            &cfrag.point_v1,
114            &cfrag.proof.point_v2,
115            &params.u,
116            &cfrag.proof.kfrag_commitment,
117            &cfrag.proof.kfrag_pok,
118        );
119
120        let e1h = &cfrag.point_e1 * &h;
121        let v1h = &cfrag.point_v1 * &h;
122        let u1h = &u1 * &h;
123
124        let z = cfrag.proof.signature;
125        let ez = &capsule.point_e * &z;
126        let vz = &capsule.point_v * &z;
127        let uz = &u * &z;
128
129        let kfrag_message = kfrag_signature_message(
130            &cfrag.kfrag_id,
131            &u1,
132            &cfrag.precursor,
133            Some(delegating_pk),
134            Some(receiving_pk),
135        );
136
137        let kfrag_message_hash = digest_for_signing(&kfrag_message).finalize();
138
139        let recovery_id = cfrag
140            .proof
141            .kfrag_signature
142            .get_recovery_id(verifying_pk, &kfrag_message)
143            .ok_or_else(|| {
144                "Could not find the recovery ID for the kfrag signature: mismatched verifying key?"
145                    .to_string()
146            })?;
147
148        // Note that there is also `is_x_reduced`, but it is currently not handled by `ecdsa` crate.
149        let kfrag_signature_v = recovery_id.is_y_odd();
150
151        // TODO: we can also expose `precursor` here to allow the user to calculate
152        // `kfrag_message_hash` by themselves. Is it necessary?
153
154        Ok(Self {
155            e: capsule.point_e,
156            ez,
157            e1: cfrag.point_e1,
158            e1h,
159            e2: cfrag.proof.point_e2,
160            v: capsule.point_v,
161            vz,
162            v1: cfrag.point_v1,
163            v1h,
164            v2: cfrag.proof.point_v2,
165            uz,
166            u1,
167            u1h,
168            u2,
169            kfrag_validity_message_hash: kfrag_message_hash,
170            kfrag_signature_v,
171        })
172    }
173}
174
175#[cfg(feature = "default-serialization")]
176impl DefaultSerialize for ReencryptionEvidence {}
177
178#[cfg(feature = "default-serialization")]
179impl<'de> DefaultDeserialize<'de> for ReencryptionEvidence {}
180
181#[cfg(test)]
182mod tests {
183    use super::ReencryptionEvidence;
184    use crate::{
185        curve::CurveScalar, encrypt, generate_kfrags, hash_to_cfrag_verification, reencrypt,
186        Parameters, PublicKey, RecoverableSignature, SecretKey, Signature, Signer,
187    };
188
189    fn assert_eq_byte_refs(x: &(impl AsRef<[u8]> + ?Sized), y: &(impl AsRef<[u8]> + ?Sized)) {
190        assert_eq!(x.as_ref(), y.as_ref());
191    }
192
193    #[test]
194    fn contract() {
195        let threshold: usize = 2;
196        let num_frags: usize = threshold + 1;
197
198        let delegating_sk = SecretKey::random();
199        let delegating_pk = delegating_sk.public_key();
200
201        let signer = Signer::new(SecretKey::random());
202        let verifying_pk = signer.verifying_key();
203
204        let receiving_sk = SecretKey::random();
205        let receiving_pk = receiving_sk.public_key();
206
207        let plaintext = b"peace at dawn";
208        let (capsule, _ciphertext) = encrypt(&delegating_pk, plaintext).unwrap();
209
210        let vkfrags = generate_kfrags(
211            &delegating_sk,
212            &receiving_pk,
213            &signer,
214            threshold,
215            num_frags,
216            true,
217            true,
218        );
219
220        let vcfrag = reencrypt(&capsule, vkfrags[0].clone());
221
222        let evidence = ReencryptionEvidence::new(
223            &capsule,
224            &vcfrag,
225            &verifying_pk,
226            &delegating_pk,
227            &receiving_pk,
228        )
229        .unwrap();
230
231        let capsule_bytes = capsule.to_bytes_simple();
232        let cfrag_bytes = vcfrag.to_bytes_simple();
233
234        // Recover and check components
235        assert_eq_byte_refs(&capsule_bytes[0..33], &evidence.e.to_compressed_array());
236        assert_eq_byte_refs(&capsule_bytes[33..66], &evidence.v.to_compressed_array());
237
238        assert_eq_byte_refs(&cfrag_bytes[0..33], &evidence.e1.to_compressed_array());
239        assert_eq_byte_refs(&cfrag_bytes[33..66], &evidence.v1.to_compressed_array());
240        assert_eq_byte_refs(&cfrag_bytes[131..164], &evidence.e2.to_compressed_array());
241        assert_eq_byte_refs(&cfrag_bytes[164..197], &evidence.v2.to_compressed_array());
242        assert_eq_byte_refs(&cfrag_bytes[197..230], &evidence.u1.to_compressed_array());
243        assert_eq_byte_refs(&cfrag_bytes[230..263], &evidence.u2.to_compressed_array());
244
245        let z = CurveScalar::try_from_bytes(&cfrag_bytes[263..(263 + 32)]).unwrap();
246
247        let sig = Signature::try_from_be_bytes(&cfrag_bytes[295..(295 + 64)]).unwrap();
248        let rsig = RecoverableSignature::from_normalized(sig, evidence.kfrag_signature_v);
249
250        // Check that the Alice's verifying key can be recovered from the signature
251        let vkey =
252            PublicKey::recover_from_prehash(&evidence.kfrag_validity_message_hash, &rsig).unwrap();
253        assert_eq!(vkey, verifying_pk);
254
255        // Check the ZKP identities
256
257        let params = Parameters::new();
258
259        let h = hash_to_cfrag_verification(
260            &evidence.e,
261            &evidence.e1,
262            &evidence.e2,
263            &evidence.v,
264            &evidence.v1,
265            &evidence.v2,
266            &params.u,
267            &evidence.u1,
268            &evidence.u2,
269        );
270
271        assert_eq!(&evidence.e * &z, &(&evidence.e1 * &h) + &evidence.e2);
272        assert_eq!(&evidence.v * &z, &(&evidence.v1 * &h) + &evidence.v2);
273        assert_eq!(&params.u * &z, &(&evidence.u1 * &h) + &evidence.u2);
274    }
275}