From 8cae48f1de72ebefa565c700c2ad25b58a1e66af Mon Sep 17 00:00:00 2001 From: lovesh Date: Mon, 15 Jul 2024 15:28:06 +0530 Subject: [PATCH] BBS# Signed-off-by: lovesh --- README.md | 1 + kvac/Cargo.toml | 2 + kvac/README.md | 3 +- kvac/src/bbdt_2016/README.md | 7 + kvac/src/bbdt_2016/keyed_proof.rs | 14 +- kvac/src/bbdt_2016/mac.rs | 2 +- kvac/src/bbdt_2016/mod.rs | 2 + kvac/src/bbdt_2016/setup.rs | 4 +- kvac/src/bbs_sharp/README.md | 13 + kvac/src/bbs_sharp/mac.rs | 210 +++++++++ kvac/src/bbs_sharp/mod.rs | 17 + kvac/src/bbs_sharp/proof.rs | 737 ++++++++++++++++++++++++++++++ kvac/src/bbs_sharp/setup.rs | 157 +++++++ kvac/src/lib.rs | 1 + utils/Cargo.toml | 1 + utils/src/lib.rs | 1 + utils/src/schnorr_signature.rs | 70 +++ 17 files changed, 1236 insertions(+), 6 deletions(-) create mode 100644 kvac/src/bbdt_2016/README.md create mode 100644 kvac/src/bbs_sharp/README.md create mode 100644 kvac/src/bbs_sharp/mac.rs create mode 100644 kvac/src/bbs_sharp/mod.rs create mode 100644 kvac/src/bbs_sharp/proof.rs create mode 100644 kvac/src/bbs_sharp/setup.rs create mode 100644 utils/src/schnorr_signature.rs diff --git a/README.md b/README.md index 5990db09..4c10b618 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ Library providing privacy enhancing cryptographic primitives. 9. [LegoGroth16](./legogroth16/). LegoGroth16, the [LegoSNARK](https://eprint.iacr.org/2019/142) variant of [Groth16](https://eprint.iacr.org/2016/260) zkSNARK proof system 10. [Oblivious Transfer (OT) and Oblivious Transfer Extensions (OTE)](./oblivious_transfer). 11. [Short group signatures](./short_group_sig/). BB signature and weak-BB signature and their proofs of knowledge based on the papers [Short Signatures Without Random Oracles](https://eprint.iacr.org/2004/171) and [Scalable Revocation Scheme for Anonymous Credentials Based on n-times Unlinkable Proofs](http://library.usc.edu.ph/ACM/SIGSAC%202017/wpes/p123.pdf). +12. [Keyed-Verification Anonymous Credentials (KVAC)](./kvac). Implements Keyed-Verification Anonymous Credentials (KVAC) schemes. ## Composite proof system diff --git a/kvac/Cargo.toml b/kvac/Cargo.toml index a5a0b27b..3807bf5a 100644 --- a/kvac/Cargo.toml +++ b/kvac/Cargo.toml @@ -27,6 +27,8 @@ ark-bls12-381.workspace = true ark-ed25519 = { version = "^0.4.0", default-features = false } ark-curve25519 = { version = "^0.4.0", default-features = false } ark-secp256k1 = { version = "^0.4.0", default-features = false } +ark-secp256r1 = { version = "^0.4.0", default-features = false } +sha2 = {version = "0.10.8", default-features = false} [features] default = [ "parallel"] diff --git a/kvac/README.md b/kvac/README.md index 9c0f0f1e..102d7903 100644 --- a/kvac/README.md +++ b/kvac/README.md @@ -11,6 +11,7 @@ KVACs are supposed to be verified by the issuer only (or anyone who shares the i Both implementations support additional verification methods that allow joint verification of proof of possession of credentials where one of the verifier is the issuer who knows the secret key and another verifier does not know secret key but learns the revealed attributes which are not shared with the issuer. This lets us build for a use-case where issuer wants to allow anytime its issued credential is used -(eg. to get paid by the verifier) while still not harming the user's privacy as it doesn't learn any revealed attributes. +(eg. to get paid by the verifier) while still not harming the user's privacy as it doesn't learn any revealed attributes. The first +verifier, i.e. the issuer can also provide a proof of validity or invalidity to the second verifier. diff --git a/kvac/src/bbdt_2016/README.md b/kvac/src/bbdt_2016/README.md new file mode 100644 index 00000000..75c2bfc3 --- /dev/null +++ b/kvac/src/bbdt_2016/README.md @@ -0,0 +1,7 @@ +Implements KVAC from [Improved Algebraic MACs and Practical Keyed-Verification Anonymous Credentials](https://link.springer.com/chapter/10.1007/978-3-319-69453-5_20) + +An alternate implementation of proof of knowledge of MAC is added which is adapted from the protocol to prove knowledge of +BBS+ signatures described in section 4.5 of the paper [Anonymous Attestation Using the Strong Diffie Hellman Assumption Revisited](https://eprint.iacr.org/2016/663) + +In addition, it supports generating proof of validity or invalidity of keyed-proofs, i.e. the proof verifying which requires the knowledge of +secret key. \ No newline at end of file diff --git a/kvac/src/bbdt_2016/keyed_proof.rs b/kvac/src/bbdt_2016/keyed_proof.rs index e5bea47b..da140e68 100644 --- a/kvac/src/bbdt_2016/keyed_proof.rs +++ b/kvac/src/bbdt_2016/keyed_proof.rs @@ -174,6 +174,16 @@ impl ProofOfValidityOfKeyedProof { proof: &KeyedProof, pk: impl Into<&'a G>, g_0: impl Into<&'a G>, + ) -> Result<(), KVACError> { + self.verify_given_destructured::(&proof.B_0, &proof.C, pk, g_0) + } + + pub fn verify_given_destructured<'a, D: Digest>( + &self, + B_0: &G, + C: &G, + pk: impl Into<&'a G>, + g_0: impl Into<&'a G>, ) -> Result<(), KVACError> { if self.sc_proof.response != self.sc_pk.response { return Err(KVACError::InvalidKeyedProof); @@ -185,13 +195,13 @@ impl ProofOfValidityOfKeyedProof { .challenge_contribution(g_0, pk, &mut challenge_bytes) .unwrap(); self.sc_proof - .challenge_contribution(&proof.B_0, &proof.C, &mut challenge_bytes) + .challenge_contribution(B_0, C, &mut challenge_bytes) .unwrap(); let challenge = compute_random_oracle_challenge::(&challenge_bytes); if !self.sc_pk.verify(pk, g_0, &challenge) { return Err(KVACError::InvalidKeyedProof); } - if !self.sc_proof.verify(&proof.C, &proof.B_0, &challenge) { + if !self.sc_proof.verify(C, B_0, &challenge) { return Err(KVACError::InvalidKeyedProof); } Ok(()) diff --git a/kvac/src/bbdt_2016/mac.rs b/kvac/src/bbdt_2016/mac.rs index cb6a1115..1798837a 100644 --- a/kvac/src/bbdt_2016/mac.rs +++ b/kvac/src/bbdt_2016/mac.rs @@ -208,7 +208,7 @@ impl ProofOfValidityOfMAC { return Err(KVACError::InvalidMACProof); } let params = params.as_ref(); - // B = h + g * s + g_1 * m_1 + g_2 * m_2 + ... g_n * m_n + // B = h + g * s + g_1 * m_1 + g_2 * m_2 + ... g_n * m_n - A * e let B = (params.b(messages.iter().enumerate(), &mac.s)? + mac.A * mac.e.neg()).into_affine(); diff --git a/kvac/src/bbdt_2016/mod.rs b/kvac/src/bbdt_2016/mod.rs index 6969913b..78e92f2e 100644 --- a/kvac/src/bbdt_2016/mod.rs +++ b/kvac/src/bbdt_2016/mod.rs @@ -1,6 +1,8 @@ //! Implements KVAC from [Improved Algebraic MACs and Practical Keyed-Verification Anonymous Credentials](https://link.springer.com/chapter/10.1007/978-3-319-69453-5_20) +//! //! An alternate implementation of proof of knowledge of MAC is added which is adapted from the protocol to prove knowledge of //! BBS+ signatures described in section 4.5 of the paper [Anonymous Attestation Using the Strong Diffie Hellman Assumption Revisited](https://eprint.iacr.org/2016/663) +//! //! In addition it supports generating proof of validity or invalidity of keyed-proofs, i.e. the proof verifying which requires the knowledge of //! secret key. diff --git a/kvac/src/bbdt_2016/setup.rs b/kvac/src/bbdt_2016/setup.rs index b7ddd91e..4cb2c3fb 100644 --- a/kvac/src/bbdt_2016/setup.rs +++ b/kvac/src/bbdt_2016/setup.rs @@ -73,12 +73,12 @@ impl MACParams { affine_group_element_from_byte_slices!(label, b" : g"), affine_group_element_from_byte_slices!(label, b" : h"), { - let h: Vec<_> = n_projective_group_elements::( + let g: Vec<_> = n_projective_group_elements::( 1..message_count + 1, &concat_slices!(label, b" : g_"), ) .collect(); - G::Group::normalize_batch(&h) + G::Group::normalize_batch(&g) } ); diff --git a/kvac/src/bbs_sharp/README.md b/kvac/src/bbs_sharp/README.md new file mode 100644 index 00000000..456adbd8 --- /dev/null +++ b/kvac/src/bbs_sharp/README.md @@ -0,0 +1,13 @@ +BBS# as described [here](https://github.com/user-attachments/files/15905230/BBS_Sharp_Short_TR.pdf) + +This assumes that the messages/attributes have already been prepared before signing, i.e. attributes are hashed +with public salts, etc and whats called `H_i` in the paper is already created. + +Assumes that a Schnorr Signature will be generated by the user's secure hardware. + +Implements both the offline and half-offline (HOL) mode. +In the former, the verifier is either the signer (has the secret key) or can ask the signer to verify the proof without revealing any user-specific info. +In the latter, the user needs to communicate with the signer before creating a proof and get "some helper data" +to create a proof which the verifier can check without needing the secret key or interacting with the issuer. +For efficiency and avoiding correlation (when signer and verifier collude), the user gets a batch of +"helper data" to let him create several proofs. \ No newline at end of file diff --git a/kvac/src/bbs_sharp/mac.rs b/kvac/src/bbs_sharp/mac.rs new file mode 100644 index 00000000..c940e282 --- /dev/null +++ b/kvac/src/bbs_sharp/mac.rs @@ -0,0 +1,210 @@ +use crate::{ + bbs_sharp::setup::{MACParams, PublicKey, SecretKey}, + error::KVACError, +}; +use ark_ec::{AffineRepr, CurveGroup}; +use ark_ff::{Field, Zero}; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; +use ark_std::{ops::Neg, rand::RngCore, vec, vec::Vec, UniformRand}; +use digest::Digest; +use dock_crypto_utils::{ + expect_equality, serde_utils::ArkObjectBytes, signature::MultiMessageSignatureParams, +}; +use schnorr_pok::{ + compute_random_oracle_challenge, + discrete_log::{PokDiscreteLog, PokDiscreteLogProtocol}, +}; +use serde::{Deserialize, Serialize}; +use serde_with::serde_as; +use zeroize::{Zeroize, ZeroizeOnDrop}; + +#[serde_as] +#[derive( + Clone, + PartialEq, + Eq, + Debug, + CanonicalSerialize, + CanonicalDeserialize, + Serialize, + Deserialize, + Zeroize, + ZeroizeOnDrop, +)] +pub struct MAC { + #[serde_as(as = "ArkObjectBytes")] + pub A: G, + #[serde_as(as = "ArkObjectBytes")] + pub e: G::ScalarField, +} + +/// A proof corresponding to a MAC that it is correctly created, i.e. can be verified successfully by someone possessing +/// the secret key. Verifying the proof does not require the secret key. +/// Consists of 2 protocols for discrete log relations, and both have the same discrete log +/// +#[serde_as] +#[derive( + Clone, PartialEq, Eq, Debug, CanonicalSerialize, CanonicalDeserialize, Serialize, Deserialize, +)] +pub struct ProofOfValidityOfMAC { + /// For proving `B = A * sk` where `sk` is the secret key and `B = g_0 + signer_pk + g_1 * m_1 + g_2 * m_2 + ... g_n * m_n` + pub sc_B: PokDiscreteLog, + /// For proving knowledge of secret key, i.e. `pk = g * sk` + pub sc_pk: PokDiscreteLog, +} + +impl MAC { + pub fn new( + rng: &mut R, + messages: &[G::ScalarField], + user_public_key: &PublicKey, + signer_secret_key: &SecretKey, + params: impl AsRef>, + ) -> Result { + if messages.is_empty() { + return Err(KVACError::NoMessageGiven); + } + let params = params.as_ref(); + expect_equality!( + messages.len(), + params.supported_message_count(), + KVACError::MessageCountIncompatibleWithMACParams + ); + let mut e = G::ScalarField::rand(rng); + while (e + signer_secret_key.0).is_zero() { + e = G::ScalarField::rand(rng) + } + // 1/(e+x) + let e_plus_x_inv = (e + signer_secret_key.0).inverse().unwrap(); + let A = params.b(messages.iter().enumerate(), user_public_key)? * e_plus_x_inv; + Ok(Self { + A: A.into_affine(), + e, + }) + } + + pub fn verify( + &self, + messages: &[G::ScalarField], + user_public_key: &PublicKey, + sk: impl AsRef, + params: impl AsRef>, + ) -> Result<(), KVACError> { + if messages.is_empty() { + return Err(KVACError::NoMessageGiven); + } + let params = params.as_ref(); + expect_equality!( + messages.len(), + params.supported_message_count(), + KVACError::MessageCountIncompatibleWithMACParams + ); + let b = params.b(messages.iter().enumerate(), user_public_key)?; + let e_plus_x_inv = (self.e + sk.as_ref()) + .inverse() + .ok_or(KVACError::CannotInvert0)?; + if (b * e_plus_x_inv).into_affine() != self.A { + return Err(KVACError::InvalidMAC); + } + Ok(()) + } +} + +impl ProofOfValidityOfMAC { + pub fn new( + rng: &mut R, + mac: &MAC, + secret_key: &SecretKey, + public_key: &PublicKey, + params: impl AsRef>, + ) -> Self { + let witness = secret_key.0; + let blinding = G::ScalarField::rand(rng); + let B = (mac.A * witness).into_affine(); + let params = params.as_ref(); + let mut challenge_bytes = vec![]; + // As witness has to be proven same in both protocols. + let p1 = PokDiscreteLogProtocol::init(witness, blinding, &mac.A); + let p2 = PokDiscreteLogProtocol::init(witness, blinding, ¶ms.g); + p1.challenge_contribution(&mac.A, &B, &mut challenge_bytes) + .unwrap(); + p2.challenge_contribution(¶ms.g, &public_key.0, &mut challenge_bytes) + .unwrap(); + let challenge = compute_random_oracle_challenge::(&challenge_bytes); + Self { + sc_B: p1.gen_proof(&challenge), + sc_pk: p2.gen_proof(&challenge), + } + } + + pub fn verify( + &self, + mac: &MAC, + messages: &[G::ScalarField], + user_public_key: &PublicKey, + signer_public_key: &PublicKey, + params: impl AsRef>, + ) -> Result<(), KVACError> { + if self.sc_B.response != self.sc_pk.response { + return Err(KVACError::InvalidMACProof); + } + let params = params.as_ref(); + // B = g_0 + user_pk + g_1 * m_1 + g_2 * m_2 + ... g_n * m_n - A * e + let B = (params.b(messages.iter().enumerate(), user_public_key)? + mac.A * mac.e.neg()) + .into_affine(); + + let mut challenge_bytes = vec![]; + self.sc_B + .challenge_contribution(&mac.A, &B, &mut challenge_bytes) + .unwrap(); + self.sc_pk + .challenge_contribution(¶ms.g, &signer_public_key.0, &mut challenge_bytes) + .unwrap(); + let challenge = compute_random_oracle_challenge::(&challenge_bytes); + if !self.sc_B.verify(&B, &mac.A, &challenge) { + return Err(KVACError::InvalidMACProof); + } + if !self + .sc_pk + .verify(&signer_public_key.0, ¶ms.g, &challenge) + { + return Err(KVACError::InvalidMACProof); + } + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use ark_secp256r1::{Affine, Fr}; + use ark_std::rand::{prelude::StdRng, SeedableRng}; + use sha2::Sha256; + + #[test] + fn mac_verification() { + let mut rng = StdRng::seed_from_u64(0u64); + let message_count = 10; + let messages = (0..message_count) + .map(|_| Fr::rand(&mut rng)) + .collect::>(); + let params = MACParams::::new::(b"test", message_count); + let signer_sk = SecretKey::new(&mut rng); + let signer_pk = PublicKey::new(&signer_sk, ¶ms.g); + + let user_sk = SecretKey::new(&mut rng); + let user_pk = PublicKey::new(&user_sk, ¶ms.g); + + // Signer sends the following 2 items to the user + let mac = MAC::new(&mut rng, &messages, &user_pk, &signer_sk, ¶ms).unwrap(); + let proof = + ProofOfValidityOfMAC::new::<_, Sha256>(&mut rng, &mac, &signer_sk, &signer_pk, ¶ms); + + // User verifies both + mac.verify(&messages, &user_pk, &signer_sk, ¶ms) + .unwrap(); + proof + .verify::(&mac, &messages, &user_pk, &signer_pk, params) + .unwrap(); + } +} diff --git a/kvac/src/bbs_sharp/mod.rs b/kvac/src/bbs_sharp/mod.rs new file mode 100644 index 00000000..86a92d12 --- /dev/null +++ b/kvac/src/bbs_sharp/mod.rs @@ -0,0 +1,17 @@ +//! BBS# as described [here](https://github.com/user-attachments/files/15905230/BBS_Sharp_Short_TR.pdf) +//! +//! This assumes that the messages/attributes have already been prepared before signing, i.e. attributes are hashed +//! with public salts, etc and whats called `H_i` in the paper is already created. +//! +//! Assumes that a Schnorr Signature will be generated by the user's secure hardware. +//! +//! Implements both the offline and half-offline (HOL) mode. In the former, the verifier is either the +//! signer (has the secret key) or can ask the signer to verify the proof without revealing any user-specific info +//! In the latter, the user needs to communicate with the signer before creating a proof and get "some helper data" +//! to create a proof which the verifier can check without needing the secret key or interacting with the issuer. +//! For efficiency and avoiding correlation (when signer and verifier collude), the user gets a batch of +//! "helper data" to let him create several proofs. + +pub mod mac; +pub mod proof; +pub mod setup; diff --git a/kvac/src/bbs_sharp/proof.rs b/kvac/src/bbs_sharp/proof.rs new file mode 100644 index 00000000..c59e2e66 --- /dev/null +++ b/kvac/src/bbs_sharp/proof.rs @@ -0,0 +1,737 @@ +use crate::{ + bbdt_2016::keyed_proof::{KeyedProof, ProofOfValidityOfKeyedProof}, + bbs_sharp::{ + mac::MAC, + setup::{MACParams, PublicKey, SecretKey}, + }, + error::KVACError, +}; +use ark_ec::{AffineRepr, CurveGroup, VariableBaseMSM}; +use ark_ff::{Field, Zero}; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; +use ark_std::{collections::BTreeMap, io::Write, rand::RngCore, vec::Vec, UniformRand}; +use core::mem; +use digest::Digest; +use dock_crypto_utils::{ + msm::WindowTable, + schnorr_signature::Signature, + serde_utils::ArkObjectBytes, + signature::{split_messages_and_blindings, MessageOrBlinding, MultiMessageSignatureParams}, +}; +use itertools::multiunzip; +use schnorr_pok::{ + discrete_log::{PokTwoDiscreteLogs, PokTwoDiscreteLogsProtocol}, + SchnorrCommitment, SchnorrResponse, +}; +use serde::{Deserialize, Serialize}; +use serde_with::serde_as; +use zeroize::{Zeroize, ZeroizeOnDrop}; + +#[serde_as] +#[derive( + Clone, + PartialEq, + Eq, + Debug, + Zeroize, + ZeroizeOnDrop, + CanonicalSerialize, + CanonicalDeserialize, + Serialize, + Deserialize, +)] +pub struct PoKOfMACProtocol { + /// Randomized MAC `A_hat = A * r1 * r2` + #[zeroize(skip)] + #[serde_as(as = "ArkObjectBytes")] + pub A_hat: G, + /// `D = B * r2` + #[zeroize(skip)] + #[serde_as(as = "ArkObjectBytes")] + pub D: G, + /// `B_bar = D * r1 - A_hat * e` + #[zeroize(skip)] + #[serde_as(as = "ArkObjectBytes")] + pub B_bar: G, + /// The randomized public key + #[zeroize(skip)] + #[serde_as(as = "ArkObjectBytes")] + pub blinded_pk: G, + /// The blinding used to randomize the public key + #[serde_as(as = "ArkObjectBytes")] + pub blinding_pk: G::ScalarField, + /// For proving relation `B_bar = A_hat * -e + D * r1` + pub sc_B_bar: PokTwoDiscreteLogsProtocol, + /// For proving relation `g_0 + user_pk + \sum_{i in D}(g_vec_i*m_i)` = `d*r3 + sum_{j notin D}(g_vec_j * -m_j) + g * blinding_pk` + pub sc_comm_msgs: SchnorrCommitment, + #[serde_as(as = "Vec")] + sc_wits_msgs: Vec, +} + +/// Proof of knowledge of a MAC. +#[serde_as] +#[derive( + Clone, PartialEq, Eq, Debug, CanonicalSerialize, CanonicalDeserialize, Serialize, Deserialize, +)] +#[serde(bound = "")] +pub struct PoKOfMAC { + #[serde_as(as = "ArkObjectBytes")] + pub A_hat: G, + #[serde_as(as = "ArkObjectBytes")] + pub B_bar: G, + #[serde_as(as = "ArkObjectBytes")] + pub D: G, + #[serde_as(as = "ArkObjectBytes")] + pub blinded_pk: G, + /// For proving relation `B_bar = A_hat * -e + D * r1` + pub sc_B_bar: PokTwoDiscreteLogs, + /// For proving relation `g_0 + user_pk + \sum_{i in D}(g_vec_i*m_i)` = `d*r3 + sum_{j notin D}(g_vec_j * -m_j) + g * blinding_pk` + #[serde_as(as = "ArkObjectBytes")] + pub t_msgs: G, + pub sc_resp_msgs: SchnorrResponse, +} + +/// Private data of the user when it requests a proof of validity of the keyed-proof, i.e. `(A_hat * sk = B_bar)` +#[serde_as] +#[derive( + Clone, + PartialEq, + Eq, + Debug, + Zeroize, + ZeroizeOnDrop, + CanonicalSerialize, + CanonicalDeserialize, + Serialize, + Deserialize, +)] +pub struct KeyedProofRequestPrivateData { + /// `D = B * r2` + #[zeroize(skip)] + #[serde_as(as = "ArkObjectBytes")] + pub D: G, + #[serde_as(as = "ArkObjectBytes")] + pub r1: G::ScalarField, + #[serde_as(as = "ArkObjectBytes")] + pub r3: G::ScalarField, + #[serde_as(as = "ArkObjectBytes")] + pub minus_e: G::ScalarField, +} + +impl PoKOfMACProtocol { + pub fn init<'a, MBI, R: RngCore>( + rng: &mut R, + mac: &MAC, + params: &MACParams, + messages_and_blindings: MBI, + user_public_key: &PublicKey, + ) -> Result + where + MBI: IntoIterator>, + { + let (messages, indexed_blindings) = + match split_messages_and_blindings(rng, messages_and_blindings, params) { + Ok(t) => t, + Err(l) => { + return Err(KVACError::MessageCountIncompatibleWithMACParams( + l, + params.supported_message_count(), + )) + } + }; + + let r1 = G::ScalarField::rand(rng); + let mut r2 = G::ScalarField::rand(rng); + while r2.is_zero() { + r2 = G::ScalarField::rand(rng); + } + let r3 = r2.inverse().unwrap(); + + let A_hat = mac.A * (r1 * r2); + // B = (e+x) * A = g_0 + user_pk + \sum(g_vec_i*m_i) for all i in I + let B = params.b(messages.iter().enumerate(), &user_public_key)?; + let D = B * r2; + + let minus_e = -mac.e; + let B_bar = D * r1 + A_hat * minus_e; + Self::_init( + rng, + A_hat.into(), + B_bar.into(), + D.into(), + r1, + r3, + minus_e, + messages, + indexed_blindings, + params, + user_public_key, + ) + } + + pub fn init_using_keyed_proof_private_data<'a, MBI, R: RngCore>( + rng: &mut R, + private_data: KeyedProofRequestPrivateData, + keyed_proof: KeyedProof, + params: &MACParams, + messages_and_blindings: MBI, + user_public_key: &PublicKey, + ) -> Result + where + MBI: IntoIterator>, + { + let (messages, indexed_blindings) = + match split_messages_and_blindings(rng, messages_and_blindings, params) { + Ok(t) => t, + Err(l) => { + return Err(KVACError::MessageCountIncompatibleWithMACParams( + l, + params.supported_message_count(), + )) + } + }; + + let KeyedProofRequestPrivateData { D, r1, r3, minus_e } = private_data; + let KeyedProof { + B_0: A_hat, + C: B_bar, + } = keyed_proof; + Self::_init( + rng, + A_hat, + B_bar, + D, + r1, + r3, + minus_e, + messages, + indexed_blindings, + params, + user_public_key, + ) + } + + pub fn challenge_contribution( + &self, + revealed_msgs: &BTreeMap, + params: &MACParams, + writer: W, + ) -> Result<(), KVACError> { + Self::compute_challenge_contribution( + &self.A_hat, + &self.B_bar, + &self.D, + &self.blinded_pk, + &self.sc_B_bar.t, + &self.sc_comm_msgs.t, + revealed_msgs, + params, + writer, + ) + } + + pub fn gen_proof(mut self, challenge: &G::ScalarField) -> Result, KVACError> { + let sc_B_bar = mem::take(&mut self.sc_B_bar).gen_proof(challenge); + let sc_resp_msgs = self.sc_comm_msgs.response(&self.sc_wits_msgs, challenge)?; + Ok(PoKOfMAC { + A_hat: self.A_hat, + B_bar: self.B_bar, + D: self.D, + blinded_pk: self.blinded_pk, + sc_B_bar, + t_msgs: self.sc_comm_msgs.t, + sc_resp_msgs, + }) + } + + /// Transform the Schnorr signature received from user (likely from the secure hardware) to be verifiable + /// by the blinded public key. + pub fn transform_schnorr_sig(&self, sig: Signature) -> Signature { + Signature { + response: sig.response + self.blinding_pk * sig.challenge, + challenge: sig.challenge, + } + } + + pub fn compute_challenge_contribution( + A_bat: &G, + B_bar: &G, + D: &G, + blinded_pk: &G, + t_B_bar: &G, + t_msgs: &G, + revealed_msgs: &BTreeMap, + params: &MACParams, + mut writer: W, + ) -> Result<(), KVACError> { + A_bat.serialize_compressed(&mut writer)?; + B_bar.serialize_compressed(&mut writer)?; + D.serialize_compressed(&mut writer)?; + blinded_pk.serialize_compressed(&mut writer)?; + params.g.serialize_compressed(&mut writer)?; + t_B_bar.serialize_compressed(&mut writer)?; + t_msgs.serialize_compressed(&mut writer)?; + for i in 0..params.g_vec.len() { + params.g_vec[i].serialize_compressed(&mut writer)?; + if let Some(m) = revealed_msgs.get(&i) { + m.serialize_compressed(&mut writer)?; + } + } + Ok(()) + } + + fn _init( + rng: &mut R, + A_hat: G, + B_bar: G, + D: G, + r1: G::ScalarField, + r3: G::ScalarField, + minus_e: G::ScalarField, + messages: Vec, + indexed_blindings: impl IntoIterator, + params: &MACParams, + user_public_key: &PublicKey, + ) -> Result { + let blinding_pk = G::ScalarField::rand(rng); + let blinded_pk = user_public_key.get_blinded(&blinding_pk, ¶ms.g); + + let sc_C_bar = PokTwoDiscreteLogsProtocol::init( + minus_e, + G::ScalarField::rand(rng), + &A_hat, + r1, + G::ScalarField::rand(rng), + &D, + ); + + // Iterator of tuples of form `(g_vec_i, blinding_i, message_i)` + let msg_comm_iter = indexed_blindings + .into_iter() + .map(|(idx, blinding)| (params.g_vec[idx], blinding, messages[idx])); + let (bases, randomness, sc_wits_msgs): (Vec<_>, Vec<_>, Vec<_>) = multiunzip( + msg_comm_iter.chain( + [ + (D, G::ScalarField::rand(rng), -r3), + (params.g, G::ScalarField::rand(rng), -blinding_pk), + ] + .into_iter(), + ), + ); + let sc_comm_msgs = SchnorrCommitment::new(&bases, randomness); + Ok(Self { + A_hat, + B_bar, + D, + blinded_pk: blinded_pk.0, + blinding_pk, + sc_B_bar: sc_C_bar, + sc_comm_msgs, + sc_wits_msgs, + }) + } +} + +impl PoKOfMAC { + /// Verify the proof of knowledge of MAC. Requires the knowledge of the signer's secret key. It can be seen as composed of 2 parts, + /// one requiring knowledge of secret key and the other not requiring it. The latter can thus be verified by anyone. + /// The former doesn't contain any revealed messages and contains no user specific data. + pub fn verify( + &self, + revealed_msgs: &BTreeMap, + challenge: &G::ScalarField, + secret_key: &SecretKey, + params: &MACParams, + ) -> Result<(), KVACError> { + if self.B_bar != (self.A_hat * secret_key.0).into() { + return Err(KVACError::InvalidRandomizedMAC); + } + self.verify_schnorr_proofs(revealed_msgs, challenge, params)?; + Ok(()) + } + + /// Verify the proof of knowledge of MAC. Doesn't require the knowledge of the signer's secret key + /// but consists of proof of correctness of randomized MAC given by the signer + /// NOTE: The pair (PoKOfMAC, ProofOfValidityOfKeyedProof) could be combined in a single struct. + pub fn verify_given_proof_of_validity_of_keyed_proof( + &self, + revealed_msgs: &BTreeMap, + challenge: &G::ScalarField, + proof_of_validity: &ProofOfValidityOfKeyedProof, + signer_pk: &PublicKey, + params: &MACParams, + ) -> Result<(), KVACError> { + proof_of_validity.verify_given_destructured::( + &self.A_hat, + &self.B_bar, + &signer_pk.0, + ¶ms.g, + )?; + self.verify_schnorr_proofs(revealed_msgs, challenge, params)?; + Ok(()) + } + + /// Create a new sub-proof that can be verified by someone with the secret key + pub fn to_keyed_proof(&self) -> KeyedProof { + KeyedProof { + B_0: self.A_hat, + C: self.B_bar, + } + } + + pub fn verify_schnorr_proofs( + &self, + revealed_msgs: &BTreeMap, + challenge: &G::ScalarField, + params: &MACParams, + ) -> Result<(), KVACError> { + if !self + .sc_B_bar + .verify(&self.B_bar, &self.A_hat, &self.D, challenge) + { + return Err(KVACError::InvalidSchnorrProof); + } + let mut bases = Vec::with_capacity(2 + params.g_vec.len() - revealed_msgs.len()); + let mut bases_revealed = Vec::with_capacity(revealed_msgs.len()); + let mut exponents = Vec::with_capacity(revealed_msgs.len()); + for i in 0..params.g_vec.len() { + if revealed_msgs.contains_key(&i) { + let message = revealed_msgs.get(&i).unwrap(); + bases_revealed.push(params.g_vec[i]); + exponents.push(*message); + } else { + bases.push(params.g_vec[i]); + } + } + bases.push(self.D); + bases.push(params.g); + let y = + -(G::Group::msm_unchecked(&bases_revealed, &exponents) + params.g_0 + self.blinded_pk); + self.sc_resp_msgs + .is_valid(&bases, &y.into(), &self.t_msgs, challenge)?; + Ok(()) + } + + pub fn challenge_contribution( + &self, + revealed_msgs: &BTreeMap, + params: &MACParams, + writer: W, + ) -> Result<(), KVACError> { + PoKOfMACProtocol::compute_challenge_contribution( + &self.A_hat, + &self.B_bar, + &self.D, + &self.blinded_pk, + &self.sc_B_bar.t, + &self.t_msgs, + revealed_msgs, + params, + writer, + ) + } +} + +/// Called by the user to create requests to send to the signer in half-offline mode. `count` denotes the +/// number of requests to create and each request can be used to create one proof of knowledge. +/// Outputs a pair where the first items is kept private by the user and later used to create the proof +/// of knowledge and the second item is sent to the signer. +/// NOTE: These requests likely should be accompanied by a proof that the user possesses a valid +/// MAC and the randomized MAC in the keyed proof is the randomization of the possessed MAC. +pub fn generate_keyed_proof_requests( + rng: &mut R, + count: usize, + mac: &MAC, + messages: &[G::ScalarField], + user_public_key: &PublicKey, + params: &MACParams, +) -> Result<(Vec>, Vec>), KVACError> { + let minus_e = -mac.e; + // B = (e+x) * A = g_0 + user_pk + \sum(g_vec_i*m_i) for all i in I + let B = params.b(messages.iter().enumerate(), &user_public_key)?; + let A_table = WindowTable::new(count, mac.A.into_group()); + let B_table = WindowTable::new(count, B); + + let mut kp_req = Vec::with_capacity(count); + let mut kp = Vec::with_capacity(count); + for _ in 0..count { + let r1 = G::ScalarField::rand(rng); + let mut r2 = G::ScalarField::rand(rng); + while r2.is_zero() { + r2 = G::ScalarField::rand(rng); + } + let r3 = r2.inverse().unwrap(); + + let r1_times_r2 = r1 * r2; + let A_hat = A_table.multiply(&r1_times_r2); + let D = B_table.multiply(&r2).into_affine(); + // D * r1 = B * r2 * r1 + let B_bar = B_table.multiply(&r1_times_r2) + A_hat * minus_e; + kp_req.push(KeyedProofRequestPrivateData { D, r1, r3, minus_e }); + kp.push(KeyedProof { + B_0: A_hat.into_affine(), + C: B_bar.into_affine(), + }); + } + Ok((kp_req, kp)) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::bbs_sharp::{mac::ProofOfValidityOfMAC, setup::SecretKey}; + use ark_secp256r1::{Affine, Fr}; + use ark_std::rand::{prelude::StdRng, SeedableRng}; + use schnorr_pok::compute_random_oracle_challenge; + use sha2::Sha256; + use std::{ + collections::BTreeSet, + time::{Duration, Instant}, + }; + + #[test] + fn proof_of_knowledge_of_MAC() { + let mut rng = StdRng::seed_from_u64(0u64); + let message_count = 10; + let messages = (0..message_count) + .map(|_| Fr::rand(&mut rng)) + .collect::>(); + let params = MACParams::::new::(b"test", message_count); + let signer_sk = SecretKey::new(&mut rng); + let signer_pk = PublicKey::new(&signer_sk, ¶ms.g); + + let user_sk = SecretKey::new(&mut rng); + let user_pk = PublicKey::new(&user_sk, ¶ms.g); + + let mac = MAC::new(&mut rng, &messages, &user_pk, &signer_sk, ¶ms).unwrap(); + let proof = + ProofOfValidityOfMAC::new::<_, Sha256>(&mut rng, &mac, &signer_sk, &signer_pk, ¶ms); + + mac.verify(&messages, &user_pk, &signer_sk, ¶ms) + .unwrap(); + proof + .verify::(&mac, &messages, &user_pk, &signer_pk, params.clone()) + .unwrap(); + + let user_auth_message = [1, 2, 3, 4, 5]; + let schnorr_signature = + Signature::new::<_, Sha256>(&mut rng, &user_auth_message, &user_sk.0, ¶ms.g); + assert!(schnorr_signature.verify::(&user_auth_message, &user_pk.0, ¶ms.g)); + + let mut revealed_indices = BTreeSet::new(); + revealed_indices.insert(0); + revealed_indices.insert(2); + + let mut revealed_msgs = BTreeMap::new(); + for i in revealed_indices.iter() { + revealed_msgs.insert(*i, messages[*i]); + } + + let mut proof_create_duration = Duration::default(); + let start = Instant::now(); + let pok = PoKOfMACProtocol::init( + &mut rng, + &mac, + ¶ms, + messages.iter().enumerate().map(|(idx, msg)| { + if revealed_indices.contains(&idx) { + MessageOrBlinding::RevealMessage(msg) + } else { + MessageOrBlinding::BlindMessageRandomly(msg) + } + }), + &user_pk, + ) + .unwrap(); + let mut chal_bytes_prover = vec![]; + pok.challenge_contribution(&revealed_msgs, ¶ms, &mut chal_bytes_prover) + .unwrap(); + let challenge_prover = compute_random_oracle_challenge::(&chal_bytes_prover); + let transformed_schnorr_sig = pok.transform_schnorr_sig(schnorr_signature); + let proof = pok.gen_proof(&challenge_prover).unwrap(); + proof_create_duration += start.elapsed(); + + let mut proof_verif_duration = Duration::default(); + let start = Instant::now(); + let mut chal_bytes_verifier = vec![]; + proof + .challenge_contribution(&revealed_msgs, ¶ms, &mut chal_bytes_verifier) + .unwrap(); + let challenge_verifier = + compute_random_oracle_challenge::(&chal_bytes_verifier); + + assert_eq!(challenge_prover, challenge_verifier); + + // The verifier needs to check that the Schnorr signature is valid + assert!(transformed_schnorr_sig.verify::( + &user_auth_message, + &proof.blinded_pk, + ¶ms.g + )); + // This is an example where the verifier has the secret key + proof + .verify(&revealed_msgs, &challenge_verifier, &signer_sk, ¶ms) + .unwrap(); + proof_verif_duration += start.elapsed(); + + println!( + "Time to create proof with message size {} and revealing {} messages is {:?}", + message_count, + revealed_indices.len(), + proof_create_duration + ); + println!( + "Time to verify proof with message size {} and revealing {} messages is {:?}", + message_count, + revealed_indices.len(), + proof_verif_duration + ); + + // This is an example where the verifier does not have the secret key but creates the keyed proof + // which will be verified by the signer and the verifier checks the part of proof that contains the + // revealed messages + let keyed_proof = proof.to_keyed_proof(); + keyed_proof.verify(signer_sk.as_ref()).unwrap(); + proof + .verify_schnorr_proofs(&revealed_msgs, &challenge_verifier, ¶ms) + .unwrap(); + } + + #[test] + fn proof_of_knowledge_of_MAC_in_half_offline_mode() { + let mut rng = StdRng::seed_from_u64(0u64); + let message_count = 10; + let messages = (0..message_count) + .map(|_| Fr::rand(&mut rng)) + .collect::>(); + let params = MACParams::::new::(b"test", message_count); + let signer_sk = SecretKey::new(&mut rng); + let signer_pk = PublicKey::new(&signer_sk, ¶ms.g); + + let user_sk = SecretKey::new(&mut rng); + let user_pk = PublicKey::new(&user_sk, ¶ms.g); + + let mac = MAC::new(&mut rng, &messages, &user_pk, &signer_sk, ¶ms).unwrap(); + let proof = + ProofOfValidityOfMAC::new::<_, Sha256>(&mut rng, &mac, &signer_sk, &signer_pk, ¶ms); + + mac.verify(&messages, &user_pk, &signer_sk, ¶ms) + .unwrap(); + proof + .verify::(&mac, &messages, &user_pk, &signer_pk, params.clone()) + .unwrap(); + + // User generates several requests of keyed-proofs and sends them to the signer and gets their proof of validity. + // These will be used later to create proof of knowledge of MAC + let count = 10; + let start = Instant::now(); + let (private_data, keyed_proofs) = + generate_keyed_proof_requests(&mut rng, count, &mac, &messages, &user_pk, ¶ms) + .unwrap(); + println!( + "Time to create {} requests with each request of {} messages is {:?}", + count, + message_count, + start.elapsed() + ); + + // Signer on getting the requests, creates a proof of validity of each request + let mut proofs_of_validity_of_keyed_proofs = vec![]; + let start = Instant::now(); + for i in 0..count { + proofs_of_validity_of_keyed_proofs.push( + keyed_proofs[i].create_proof_of_validity::<_, Sha256>( + &mut rng, + signer_sk.0, + &signer_pk.0, + ¶ms.g, + ), + ); + } + println!("Time to serve {} requests is {:?}", count, start.elapsed()); + + for i in 0..count { + let user_auth_message = [1, 2, 3, 4, 5]; + let schnorr_signature = + Signature::new::<_, Sha256>(&mut rng, &user_auth_message, &user_sk.0, ¶ms.g); + assert!(schnorr_signature.verify::(&user_auth_message, &user_pk.0, ¶ms.g)); + let mut revealed_indices = BTreeSet::new(); + revealed_indices.insert(0); + revealed_indices.insert(2); + + let mut revealed_msgs = BTreeMap::new(); + for i in revealed_indices.iter() { + revealed_msgs.insert(*i, messages[*i]); + } + + let mut proof_create_duration = Duration::default(); + let start = Instant::now(); + let pok = PoKOfMACProtocol::init_using_keyed_proof_private_data( + &mut rng, + private_data[i].clone(), + keyed_proofs[i].clone(), + ¶ms, + messages.iter().enumerate().map(|(idx, msg)| { + if revealed_indices.contains(&idx) { + MessageOrBlinding::RevealMessage(msg) + } else { + MessageOrBlinding::BlindMessageRandomly(msg) + } + }), + &user_pk, + ) + .unwrap(); + let mut chal_bytes_prover = vec![]; + pok.challenge_contribution(&revealed_msgs, ¶ms, &mut chal_bytes_prover) + .unwrap(); + let challenge_prover = + compute_random_oracle_challenge::(&chal_bytes_prover); + let transformed_schnorr_sig = pok.transform_schnorr_sig(schnorr_signature); + let proof = pok.gen_proof(&challenge_prover).unwrap(); + proof_create_duration += start.elapsed(); + + let mut proof_verif_duration = Duration::default(); + let start = Instant::now(); + let mut chal_bytes_verifier = vec![]; + proof + .challenge_contribution(&revealed_msgs, ¶ms, &mut chal_bytes_verifier) + .unwrap(); + let challenge_verifier = + compute_random_oracle_challenge::(&chal_bytes_verifier); + + assert_eq!(challenge_prover, challenge_verifier); + + // This is an example where the verifier has the secret key + assert!(transformed_schnorr_sig.verify::( + &user_auth_message, + &proof.blinded_pk, + ¶ms.g + )); + proof + .verify_given_proof_of_validity_of_keyed_proof::( + &revealed_msgs, + &challenge_verifier, + &proofs_of_validity_of_keyed_proofs[i], + &signer_pk, + ¶ms, + ) + .unwrap(); + proof_verif_duration += start.elapsed(); + + if i == 0 { + println!( + "Time to create proof with message size {} and revealing {} messages is {:?}", + message_count, + revealed_indices.len(), + proof_create_duration + ); + println!( + "Time to verify proof with message size {} and revealing {} messages is {:?}", + message_count, + revealed_indices.len(), + proof_verif_duration + ); + } + } + } +} diff --git a/kvac/src/bbs_sharp/setup.rs b/kvac/src/bbs_sharp/setup.rs new file mode 100644 index 00000000..e3c667e4 --- /dev/null +++ b/kvac/src/bbs_sharp/setup.rs @@ -0,0 +1,157 @@ +use crate::error::KVACError; +use ark_ec::{AffineRepr, CurveGroup, VariableBaseMSM}; +use ark_ff::PrimeField; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; +use ark_std::{rand::RngCore, vec::Vec}; +use digest::Digest; +use dock_crypto_utils::{ + affine_group_element_from_byte_slices, concat_slices, + iter::pair_valid_items_with_slice, + join, + misc::{n_projective_group_elements, seq_pairs_satisfy}, + serde_utils::ArkObjectBytes, + signature::MultiMessageSignatureParams, + try_iter::CheckLeft, +}; +use itertools::process_results; +use serde::{Deserialize, Serialize}; +use serde_with::serde_as; +use zeroize::{Zeroize, ZeroizeOnDrop}; + +#[cfg(feature = "parallel")] +use rayon::prelude::*; + +/// Public parameters used by the MAC creator and verifier +#[serde_as] +#[derive( + Clone, PartialEq, Eq, Debug, CanonicalSerialize, CanonicalDeserialize, Serialize, Deserialize, +)] +pub struct MACParams { + #[serde_as(as = "ArkObjectBytes")] + pub g_0: G, + #[serde_as(as = "ArkObjectBytes")] + pub g: G, + #[serde_as(as = "Vec")] + pub g_vec: Vec, +} + +#[serde_as] +#[derive( + Clone, + PartialEq, + Eq, + Debug, + CanonicalSerialize, + CanonicalDeserialize, + Serialize, + Deserialize, + Zeroize, + ZeroizeOnDrop, +)] +pub struct SecretKey(#[serde_as(as = "ArkObjectBytes")] pub F); + +#[serde_as] +#[derive( + Clone, PartialEq, Eq, Debug, CanonicalSerialize, CanonicalDeserialize, Serialize, Deserialize, +)] +pub struct PublicKey(#[serde_as(as = "ArkObjectBytes")] pub G); + +impl MACParams { + pub fn new(label: &[u8], message_count: u32) -> Self { + assert_ne!(message_count, 0); + // Group element by hashing `label`||`g_0`, `label`||`g`, and `label`||`g_i` for i in 1 to message_count. + let (g_0, g, g_vec) = join!( + affine_group_element_from_byte_slices!(label, b" : g_0"), + affine_group_element_from_byte_slices!(label, b" : g"), + { + let g: Vec<_> = n_projective_group_elements::( + 1..message_count + 1, + &concat_slices!(label, b" : g_"), + ) + .collect(); + G::Group::normalize_batch(&g) + } + ); + + Self { g_0, g, g_vec } + } + + pub fn commit_to_messages<'a, MI>( + &self, + indexed_messages_sorted_by_index: MI, + ) -> Result + where + MI: IntoIterator, + { + let (bases, scalars): (Vec<_>, Vec<_>) = process_results( + pair_valid_items_with_slice::<_, _, _, KVACError, _>( + indexed_messages_sorted_by_index, + CheckLeft(seq_pairs_satisfy(|a, b| a < b)), + &self.g_vec, + ), + |iter| iter.unzip(), + )?; + + Ok(G::Group::msm_unchecked(&bases, &scalars).into_affine()) + } + + /// Used to create whats called `B` in the paper. `B = g_0 + user_public_key + \sum_i{g_i * m_i}` + pub fn b<'a, MI>( + &self, + indexed_messages_sorted_by_index: MI, + user_public_key: &'a PublicKey, + ) -> Result + where + MI: IntoIterator, + { + let commitment = self.commit_to_messages(indexed_messages_sorted_by_index)?; + Ok(commitment + self.g_0 + user_public_key.0) + } +} + +impl MultiMessageSignatureParams for MACParams { + fn supported_message_count(&self) -> usize { + self.g_vec.len() + } +} + +impl MultiMessageSignatureParams for &MACParams { + fn supported_message_count(&self) -> usize { + self.g_vec.len() + } +} + +impl SecretKey { + pub fn new(rng: &mut R) -> Self { + Self(F::rand(rng)) + } +} + +impl PublicKey { + pub fn new<'a>(sk: &SecretKey, g: impl Into<&'a G>) -> Self { + Self((g.into().mul_bigint(sk.0.into_bigint())).into_affine()) + } + + /// Return `pk + g * blinding` + pub fn get_blinded<'a>(&self, blinding: &G::ScalarField, g: impl Into<&'a G>) -> Self { + Self((g.into().mul_bigint(blinding.into_bigint()) + self.0).into_affine()) + } +} + +impl AsRef for SecretKey { + fn as_ref(&self) -> &F { + &self.0 + } +} + +impl AsRef for PublicKey { + fn as_ref(&self) -> &G { + &self.0 + } +} + +impl AsRef> for MACParams { + fn as_ref(&self) -> &MACParams { + &self + } +} diff --git a/kvac/src/lib.rs b/kvac/src/lib.rs index 497abd31..02e8e62c 100644 --- a/kvac/src/lib.rs +++ b/kvac/src/lib.rs @@ -14,5 +14,6 @@ //! verifier, i.e. the issuer can also provide a proof of validity or invalidity to the second verifier. pub mod bbdt_2016; +pub mod bbs_sharp; pub mod cddh_2019; pub mod error; diff --git a/utils/Cargo.toml b/utils/Cargo.toml index fa2f89f7..b8126a02 100644 --- a/utils/Cargo.toml +++ b/utils/Cargo.toml @@ -33,6 +33,7 @@ aead = {version = "0.5.2", default-features = false, features = [ "alloc" ]} blake2.workspace = true ark-bls12-381.workspace = true chacha20poly1305 = {version = "0.10.1", default-features = false} +ark-secp256r1 = { version = "^0.4.0", default-features = false } [features] default = ["parallel"] diff --git a/utils/src/lib.rs b/utils/src/lib.rs index 4c9d5b7f..92390aa2 100644 --- a/utils/src/lib.rs +++ b/utils/src/lib.rs @@ -45,6 +45,7 @@ pub mod poly; /// An efficient way to check several equality relations involving pairings by combining the relations /// in a random linear combination and doing a multi-pairing check. Relies on Schwartz–Zippel lemma. pub mod randomized_pairing_check; +pub mod schnorr_signature; pub mod signature; /// Solving discrete log using Baby Step Giant Step pub mod solve_discrete_log; diff --git a/utils/src/schnorr_signature.rs b/utils/src/schnorr_signature.rs new file mode 100644 index 00000000..916ee8e7 --- /dev/null +++ b/utils/src/schnorr_signature.rs @@ -0,0 +1,70 @@ +use crate::{hashing_utils::field_elem_from_try_and_incr, serde_utils::ArkObjectBytes}; +use ark_ec::{AffineRepr, CurveGroup}; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; +use ark_std::{rand::RngCore, vec, vec::Vec, UniformRand}; +use digest::Digest; +use serde::{Deserialize, Serialize}; +use serde_with::serde_as; + +#[serde_as] +#[derive( + Clone, PartialEq, Eq, Debug, CanonicalSerialize, CanonicalDeserialize, Serialize, Deserialize, +)] +pub struct Signature { + #[serde_as(as = "ArkObjectBytes")] + pub response: G::ScalarField, + #[serde_as(as = "ArkObjectBytes")] + pub challenge: G::ScalarField, +} + +impl Signature { + pub fn new( + rng: &mut R, + message: &[u8], + secret_key: &G::ScalarField, + gen: &G, + ) -> Self { + let r = G::ScalarField::rand(rng); + let t = (*gen * r).into_affine(); + let challenge = Self::compute_challenge::(&t, message); + let response = r + challenge * secret_key; + Self { + response, + challenge, + } + } + + pub fn verify(&self, message: &[u8], public_key: &G, gen: &G) -> bool { + let t = (*gen * self.response - *public_key * self.challenge).into_affine(); + let challenge = Self::compute_challenge::(&t, message); + challenge == self.challenge + } + + pub fn compute_challenge(t: &G, message: &[u8]) -> G::ScalarField { + let mut challenge_bytes = vec![]; + t.serialize_compressed(&mut challenge_bytes).unwrap(); + challenge_bytes.extend_from_slice(&message); + // TODO: This probably is not how the standard implementation of Schnorr signature generates the field element + // from the bytes + field_elem_from_try_and_incr::(&challenge_bytes) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use ark_secp256r1::{Affine, Fr}; + use ark_std::rand::{rngs::StdRng, SeedableRng}; + use sha2::Sha256; + + #[test] + fn sig_verify() { + let mut rng = StdRng::seed_from_u64(0u64); + let message = vec![1, 2, 3, 4]; + let gen = Affine::rand(&mut rng); + let sk = Fr::rand(&mut rng); + let pk = (gen * sk).into_affine(); + let sig = Signature::new::<_, Sha256>(&mut rng, &message, &sk, &gen); + assert!(sig.verify::(&message, &pk, &gen)); + } +}