From 9af074fdc21d1ade23f14b9cc6ddcb27f4379475 Mon Sep 17 00:00:00 2001 From: Alexey Date: Thu, 3 Oct 2024 08:50:57 +0200 Subject: [PATCH] Implement BBS+ PoK of Signatures (#3) * Split crypto.rs into multiple files * PoK of Signatures for BBS+ --- Cargo.lock | 25 ++ next-gen-signatures/Cargo.toml | 3 + next-gen-signatures/src/common.rs | 9 +- next-gen-signatures/src/crypto.rs | 276 ----------------- next-gen-signatures/src/crypto/bbs.rs | 355 ++++++++++++++++++++++ next-gen-signatures/src/crypto/fips204.rs | 112 +++++++ next-gen-signatures/src/crypto/mod.rs | 11 + next-gen-signatures/src/macros.rs | 19 +- next-gen-signing-service/Cargo.toml | 1 + next-gen-signing-service/src/main.rs | 123 +++++++- 10 files changed, 634 insertions(+), 300 deletions(-) delete mode 100644 next-gen-signatures/src/crypto.rs create mode 100644 next-gen-signatures/src/crypto/bbs.rs create mode 100644 next-gen-signatures/src/crypto/fips204.rs create mode 100644 next-gen-signatures/src/crypto/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 27e2c99..0f2bb6e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -619,6 +619,15 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + [[package]] name = "futures" version = "0.3.30" @@ -1057,14 +1066,17 @@ dependencies = [ "ark-bls12-381", "ark-ec", "ark-serialize", + "ark-std", "base64", "bbs_plus", "blake2", + "dock_crypto_utils", "fips204", "num-bigint", "paste", "rand", "rocket", + "schnorr_pok", ] [[package]] @@ -1075,6 +1087,7 @@ dependencies = [ "paste", "rocket", "rocket-errors", + "serde_urlencoded", ] [[package]] @@ -1690,6 +1703,18 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + [[package]] name = "serde_with" version = "1.14.0" diff --git a/next-gen-signatures/Cargo.toml b/next-gen-signatures/Cargo.toml index 80df602..0bab44c 100644 --- a/next-gen-signatures/Cargo.toml +++ b/next-gen-signatures/Cargo.toml @@ -33,3 +33,6 @@ bbs_plus = { version = "0.22.0", optional = true } blake2 = { version = "0.10.6", optional = true } num-bigint = { version = "0.4.6", optional = true } rand = { version = "0.8.5", optional = true } +ark-std = "0.4.0" +dock_crypto_utils = "0.20.0" +schnorr_pok = "0.20.0" diff --git a/next-gen-signatures/src/common.rs b/next-gen-signatures/src/common.rs index 4a8e1c1..3ea520b 100644 --- a/next-gen-signatures/src/common.rs +++ b/next-gen-signatures/src/common.rs @@ -21,13 +21,8 @@ pub trait CryptoProvider { fn sk_into_bytes(sk: Self::SecretKey) -> Result; fn sk_from_bytes(bytes: ByteArray) -> Result; - fn sign(sk: &Self::SecretKey, msg: ByteArray, params: Self::SignParams) -> Result; - fn verify( - pk: Self::PublicKey, - msg: ByteArray, - sig: ByteArray, - params: Self::VerifyParams, - ) -> Result; + fn sign(sk: &Self::SecretKey, params: Self::SignParams) -> Result; + fn verify(pk: Self::PublicKey, sig: ByteArray, params: Self::VerifyParams) -> Result; } #[derive(Debug)] diff --git a/next-gen-signatures/src/crypto.rs b/next-gen-signatures/src/crypto.rs deleted file mode 100644 index 9668fbb..0000000 --- a/next-gen-signatures/src/crypto.rs +++ /dev/null @@ -1,276 +0,0 @@ -#[cfg(feature = "fips204")] -pub use fips204::*; - -#[cfg(feature = "fips204")] -pub mod fips204 { - use fips204::traits::{SerDes, Signer, Verifier}; - - use crate::common::{CryptoProvider, NoArguments}; - - macro_rules! fips_provider_impl { - ($provider_name:ident, $pkg_name:ident) => { - pub struct $provider_name; - impl CryptoProvider for $provider_name { - type GenParams = NoArguments; - type SignParams = NoArguments; - type VerifyParams = NoArguments; - - type PublicKey = fips204::$pkg_name::PublicKey; - - type SecretKey = fips204::$pkg_name::PrivateKey; - - fn gen_keypair( - _: Self::GenParams, - ) -> anyhow::Result<(Self::PublicKey, Self::SecretKey)> { - fips204::$pkg_name::try_keygen().map_err(|err| anyhow::anyhow!(err)) - } - - fn pk_into_bytes(pk: Self::PublicKey) -> anyhow::Result { - Ok(pk.into_bytes().to_vec()) - } - - fn pk_from_bytes( - bytes: crate::common::ByteArray, - ) -> anyhow::Result { - let bytes_len = bytes.len(); - Self::PublicKey::try_from_bytes(bytes.try_into().map_err(|_| { - anyhow::anyhow!( - "Expected an array of length {}, got {}!", - fips204::$pkg_name::PK_LEN, - bytes_len - ) - })?) - .map_err(|err| anyhow::anyhow!(err)) - } - - fn sk_into_bytes(sk: Self::SecretKey) -> anyhow::Result { - Ok(sk.into_bytes().to_vec()) - } - - fn sk_from_bytes( - bytes: crate::common::ByteArray, - ) -> anyhow::Result { - let bytes_len = bytes.len(); - Self::SecretKey::try_from_bytes(bytes.try_into().map_err(|_| { - anyhow::anyhow!( - "Expected an array of length {}, got {}!", - fips204::$pkg_name::SK_LEN, - bytes_len - ) - })?) - .map_err(|err| anyhow::anyhow!(err)) - } - - fn sign( - sk: &Self::SecretKey, - msg: crate::common::ByteArray, - _: Self::SignParams, - ) -> anyhow::Result { - sk.try_sign(&msg) - .map(|res| res.to_vec()) - .map_err(|err| anyhow::anyhow!(err)) - } - - fn verify( - pk: Self::PublicKey, - msg: crate::common::ByteArray, - sig: crate::common::ByteArray, - _: Self::VerifyParams, - ) -> anyhow::Result { - let sig_len = sig.len(); - let sig = sig.try_into().map_err(|_| { - anyhow::anyhow!( - "Expected a signature length of {}, got {}!", - fips204::$pkg_name::SIG_LEN, - sig_len - ) - })?; - Ok(pk.verify(&msg, &sig)) - } - } - }; - } - - fips_provider_impl!(Fips204MlDsa44Provider, ml_dsa_44); - fips_provider_impl!(Fips204MlDsa65Provider, ml_dsa_65); - fips_provider_impl!(Fips204MlDsa87Provider, ml_dsa_87); - - #[cfg(test)] - mod tests { - use super::*; - use crate::test_provider; - - test_provider!(Fips204MlDsa44Provider); - test_provider!(Fips204MlDsa65Provider); - test_provider!(Fips204MlDsa87Provider); - } -} - -#[cfg(feature = "bbs")] -pub use bbs::*; - -#[cfg(feature = "bbs")] -pub mod bbs { - use std::io::Cursor; - - use crate::common::{ByteArray, CryptoProvider, TestDefault}; - use ark_bls12_381::Bls12_381; - use ark_ec::pairing::Pairing; - use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; - use base64::Engine; - use bbs_plus::prelude::{ - PublicKeyG1, PublicKeyG2, SecretKey, SignatureG1, SignatureG2, SignatureParamsG1, - SignatureParamsG2, - }; - use blake2::Blake2b512; - use num_bigint::BigUint; - use rand::RngCore; - use rocket::FromForm; - - pub type Digest = Blake2b512; - - fn get_rng() -> impl RngCore { - rand::rngs::OsRng - } - - #[derive(FromForm)] - pub struct GenParams { - nonce: String, - message_count: u32, - } - - pub type SignParams = GenParams; - - pub type VerifyParams = SignParams; - - impl TestDefault for GenParams { - fn default_for_test() -> Self { - GenParams { - nonce: crate::BASE64_URL_SAFE_NO_PAD.encode("nonce"), - message_count: 1, - } - } - } - - macro_rules! fix_arguments { - (G1, $pk:expr, $params:expr) => { - ($pk, $params) - }; - (G2, $pk:expr, $params:expr) => { - (&$pk, &$params) - }; - } - - macro_rules! bbs_plus_provider_impl { - ($g1:ident, $g2:ident, $pairing:ident) => { - paste::item! { - pub struct []; - - impl CryptoProvider for [] { - type GenParams = GenParams; - type SignParams = SignParams; - type VerifyParams = VerifyParams; - - type PublicKey = []<$pairing>; - - type SecretKey = SecretKey<<$pairing as Pairing>::ScalarField>; - - fn gen_keypair( - params: Self::GenParams, - ) -> anyhow::Result<(Self::PublicKey, Self::SecretKey)> { - let nonce = $crate::BASE64_URL_SAFE_NO_PAD.decode(¶ms.nonce)?; - let seed = { - let mut rng = get_rng(); - let mut seed = ByteArray::new(); - rng.fill_bytes(&mut seed); - seed - }; - - let params = []::<$pairing>::new::(&nonce, params.message_count); - - let sk = Self::SecretKey::generate_using_seed::(&seed); - let pk = Self::PublicKey::generate_using_secret_key(&sk, ¶ms); - - Ok((pk, sk)) - } - - fn pk_into_bytes(pk: Self::PublicKey) -> anyhow::Result { - let mut bytes = ByteArray::new(); - pk.serialize_compressed(&mut bytes)?; - Ok(bytes) - } - - fn pk_from_bytes(bytes: crate::common::ByteArray) -> anyhow::Result { - Self::PublicKey::deserialize_compressed(Cursor::new(bytes)).map_err(|err| err.into()) - } - - fn sk_into_bytes(sk: Self::SecretKey) -> anyhow::Result { - let mut bytes = ByteArray::new(); - sk.serialize_compressed(&mut bytes)?; - Ok(bytes) - } - - fn sk_from_bytes(bytes: crate::common::ByteArray) -> anyhow::Result { - Self::SecretKey::deserialize_compressed(Cursor::new(bytes)).map_err(|err| err.into()) - } - - fn sign( - sk: &Self::SecretKey, - msg: crate::common::ByteArray, - params: Self::SignParams, - ) -> anyhow::Result { - let nonce = $crate::BASE64_URL_SAFE_NO_PAD.decode(¶ms.nonce)?; - let params = []::<$pairing>::new::(&nonce, params.message_count); - let sig = []::new( - &mut get_rng(), - &[BigUint::from_bytes_le(&msg).into()], - sk, - ¶ms, - ) - .map_err(|err| anyhow::anyhow!("Signature error: {:?}", err))?; - let mut bytes = ByteArray::new(); - sig.serialize_compressed(&mut bytes)?; - Ok(bytes) - } - - fn verify( - pk: Self::PublicKey, - msg: crate::common::ByteArray, - sig: crate::common::ByteArray, - params: Self::VerifyParams, - ) -> anyhow::Result { - let nonce = $crate::BASE64_URL_SAFE_NO_PAD.decode(¶ms.nonce)?; - let params = []::<$pairing>::new::(&nonce, params.message_count); - let sig = []::<$pairing>::deserialize_compressed(Cursor::new(sig))?; - - let (pk, params) = fix_arguments!($g1, pk, params); - - if !pk.is_valid() || !params.is_valid() { - anyhow::bail!("Invalid params!"); - } - - Ok(sig - .verify( - &[BigUint::from_bytes_le(&msg).into()], - pk, - params - ) - .is_ok()) - } - } - } - }; - } - - bbs_plus_provider_impl!(G1, G2, Bls12_381); - bbs_plus_provider_impl!(G2, G1, Bls12_381); - - #[cfg(test)] - pub mod tests { - use super::*; - use crate::test_provider; - - test_provider!(BbsPlusG1Provider); - test_provider!(BbsPlusG2Provider); - } -} diff --git a/next-gen-signatures/src/crypto/bbs.rs b/next-gen-signatures/src/crypto/bbs.rs new file mode 100644 index 0000000..25f0d8f --- /dev/null +++ b/next-gen-signatures/src/crypto/bbs.rs @@ -0,0 +1,355 @@ +use std::{ + collections::{BTreeMap, BTreeSet}, + io::Cursor, +}; + +use crate::common::{ByteArray, CryptoProvider, TestDefault}; +use ark_bls12_381::Bls12_381; +use ark_ec::pairing::Pairing; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; +use base64::Engine; +use bbs_plus::prelude::{ + PoKOfSignatureG1Proof, PoKOfSignatureG1Protocol, PublicKeyG1, PublicKeyG2, SecretKey, + SignatureG1, SignatureG2, SignatureParamsG1, SignatureParamsG2, +}; +use blake2::Blake2b512; +use dock_crypto_utils::signature::MessageOrBlinding; +use num_bigint::BigUint; +use rand::RngCore; +use rocket::FromForm; +use schnorr_pok::compute_random_oracle_challenge; + +pub type Digest = Blake2b512; + +fn get_rng() -> impl RngCore { + rand::rngs::OsRng +} + +#[derive(FromForm)] +pub struct GenParams { + nonce: String, + message_count: u32, +} + +#[derive(FromForm)] +pub struct SignParams { + nonce: String, + messages: Vec, +} + +pub type VerifyParams = SignParams; + +impl TestDefault for GenParams { + fn default_for_test() -> Self { + GenParams { + nonce: crate::BASE64_URL_SAFE_NO_PAD.encode("nonce"), + message_count: 4, + } + } +} + +impl TestDefault for SignParams { + fn default_for_test() -> Self { + Self { + nonce: crate::BASE64_URL_SAFE_NO_PAD.encode("nonce"), + messages: vec![ + crate::BASE64_URL_SAFE_NO_PAD.encode(b"message 1"), + crate::BASE64_URL_SAFE_NO_PAD.encode(b"message 2"), + crate::BASE64_URL_SAFE_NO_PAD.encode(b"message 3"), + crate::BASE64_URL_SAFE_NO_PAD.encode(b"message 4"), + ], + } + } +} + +macro_rules! fix_arguments { + (G1, $pk:expr, $params:expr) => { + ($pk, $params) + }; + (G2, $pk:expr, $params:expr) => { + (&$pk, &$params) + }; +} + +macro_rules! bbs_plus_crypto_provider_impl { + ($g1:ident, $g2:ident, $pairing:ident) => { + paste::item! { + pub struct []; + + impl CryptoProvider for [] { + type GenParams = GenParams; + type SignParams = SignParams; + type VerifyParams = VerifyParams; + + type PublicKey = []<$pairing>; + + type SecretKey = SecretKey<<$pairing as Pairing>::ScalarField>; + + fn gen_keypair( + params: Self::GenParams, + ) -> anyhow::Result<(Self::PublicKey, Self::SecretKey)> { + let nonce = $crate::BASE64_URL_SAFE_NO_PAD.decode(¶ms.nonce)?; + let seed = { + let mut rng = get_rng(); + let mut seed = ByteArray::new(); + rng.fill_bytes(&mut seed); + seed + }; + + let params = []::<$pairing>::new::(&nonce, params.message_count); + + let sk = Self::SecretKey::generate_using_seed::(&seed); + let pk = Self::PublicKey::generate_using_secret_key(&sk, ¶ms); + + Ok((pk, sk)) + } + + fn pk_into_bytes(pk: Self::PublicKey) -> anyhow::Result { + let mut bytes = ByteArray::new(); + pk.serialize_compressed(&mut bytes)?; + Ok(bytes) + } + + fn pk_from_bytes(bytes: ByteArray) -> anyhow::Result { + Self::PublicKey::deserialize_compressed(Cursor::new(bytes)).map_err(|err| err.into()) + } + + fn sk_into_bytes(sk: Self::SecretKey) -> anyhow::Result { + let mut bytes = ByteArray::new(); + sk.serialize_compressed(&mut bytes)?; + Ok(bytes) + } + + fn sk_from_bytes(bytes: ByteArray) -> anyhow::Result { + Self::SecretKey::deserialize_compressed(Cursor::new(bytes)).map_err(|err| err.into()) + } + + fn sign( + sk: &Self::SecretKey, + params: Self::SignParams, + ) -> anyhow::Result { + let nonce = $crate::BASE64_URL_SAFE_NO_PAD.decode(¶ms.nonce)?; + let messages: Vec<_> = params.messages.iter() + .map(|msg| $crate::BASE64_URL_SAFE_NO_PAD.decode(&msg)) + .collect::, _>>()? + .iter() + .map(|msg| BigUint::from_bytes_le(msg).into()).collect(); + let params = []::<$pairing>::new::(&nonce, params.messages.len() as u32); + let sig = []::new( + &mut get_rng(), + &messages, + sk, + ¶ms, + ) + .map_err(|err| anyhow::anyhow!("Signature error: {:?}", err))?; + let mut bytes = ByteArray::new(); + sig.serialize_compressed(&mut bytes)?; + Ok(bytes) + } + + fn verify( + pk: Self::PublicKey, + sig: ByteArray, + params: Self::VerifyParams, + ) -> anyhow::Result { + let nonce = $crate::BASE64_URL_SAFE_NO_PAD.decode(¶ms.nonce)?; + let messages: Vec<_> = params.messages.iter() + .map(|msg| $crate::BASE64_URL_SAFE_NO_PAD.decode(&msg)) + .collect::, _>>()? + .iter() + .map(|msg| BigUint::from_bytes_le(msg).into()).collect(); + let params = []::<$pairing>::new::(&nonce, params.messages.len() as u32); + let sig = []::<$pairing>::deserialize_compressed(Cursor::new(sig))?; + + let (pk, params) = fix_arguments!($g1, pk, params); + + if !pk.is_valid() || !params.is_valid() { + anyhow::bail!("Invalid params!"); + } + + Ok(sig + .verify( + &messages, + pk, + params + ) + .is_ok()) + } + } + } + }; + } + +bbs_plus_crypto_provider_impl!(G1, G2, Bls12_381); +bbs_plus_crypto_provider_impl!(G2, G1, Bls12_381); + +macro_rules! bbs_plus_provider_impl { + ($g:ident) => { + paste::item! { + impl [] { + pub fn create_pok_of_sig( + sig: ByteArray, + msgs: Vec, + nonce: String, + revealed_idxs: BTreeSet, + ) -> anyhow::Result { + type Fr = ::ScalarField; + + let nonce = crate::BASE64_URL_SAFE_NO_PAD.decode(nonce)?; + + let params = []::::new::(&nonce, msgs.len() as u32); + let sig = []::deserialize_compressed(Cursor::new(sig))?; + let msgs: Vec = msgs + .into_iter() + .map(|m| crate::BASE64_URL_SAFE_NO_PAD.decode(m)) + .collect::, _>>()? + .into_iter() + .map(|m| BigUint::from_bytes_le(&m).into()) + .collect(); + + let mbi = (0..msgs.len()) + .into_iter() + .map(|i| { + if revealed_idxs.contains(&i) { + MessageOrBlinding::RevealMessage(&msgs[i]) + } else { + MessageOrBlinding::BlindMessageRandomly(&msgs[i]) + } + }) + .collect::>(); + + let revealed_msgs = revealed_idxs + .into_iter() + .map(|i| (i, msgs[i])) + .collect::>(); + + let pok = []::init(&mut get_rng(), &sig, ¶ms, mbi) + .map_err(|err| anyhow::anyhow!("PoKOfSig Error: {:?}", err))?; + + let mut chal_bytes_prover = vec![]; + pok.challenge_contribution(&revealed_msgs, ¶ms, &mut chal_bytes_prover) + .map_err(|err| anyhow::anyhow!("Challenge Contribution error: {err:?}"))?; + let challenge_prover = + compute_random_oracle_challenge::(&chal_bytes_prover); + + let proof = pok + .gen_proof(&challenge_prover) + .map_err(|err| anyhow::anyhow!("Gen Proof error: {err:?}"))?; + + let mut bytes = ByteArray::new(); + proof.serialize_compressed(&mut bytes)?; + + Ok(bytes) + } + + pub fn verify_pok_of_sig( + proof: ByteArray, + revealed_msgs: BTreeMap, + public_key: ::PublicKey, + nonce: String, + message_count: u32, + ) -> anyhow::Result { + let nonce = crate::BASE64_URL_SAFE_NO_PAD.decode(nonce)?; + let params = []::::new::(&nonce, message_count); + + let proof = []::::deserialize_compressed(Cursor::new(proof))?; + + let revealed_msgs = revealed_msgs + .into_iter() + .map(|(k, v)| { + crate::BASE64_URL_SAFE_NO_PAD + .decode(v) + .and_then(|v| Ok((k, v))) + }) + .collect::, _>>()? + .into_iter() + .map(|(k, v)| (k, BigUint::from_bytes_le(&v).into())) + .collect(); + + let mut chal_bytes_verifier = vec![]; + proof + .challenge_contribution(&revealed_msgs, ¶ms, &mut chal_bytes_verifier) + .unwrap(); + let challenge_verifier = compute_random_oracle_challenge::< + ::ScalarField, + Blake2b512, + >(&chal_bytes_verifier); + + proof + .verify(&revealed_msgs, &challenge_verifier, public_key, params) + .map_err(|err| anyhow::anyhow!("Failed to verify: {err:?}"))?; + + Ok(true) + } + } + } + }; +} + +// NOTE (pok-notes): Currently there is only a 'PoKOfSignatureG1Protocol' +// without a 'PoKOfSignatureG2Protocol' equivalent. Why that is, I honestly +// don't know. +bbs_plus_provider_impl!(G1); + +#[cfg(test)] +pub mod tests { + use super::*; + use crate::test_provider; + + test_provider!(BbsPlusG1Provider); + test_provider!(BbsPlusG2Provider); + + macro_rules! test_pok_of_sig { + ($g:ident) => { + paste::item! { + #[test] + #[allow(non_snake_case)] + fn []() -> anyhow::Result<()> { + type Provider = []; + let nonce = crate::BASE64_URL_SAFE_NO_PAD.encode(b"my-nonce"); + + let messages: Vec = vec![b"msg1", b"msg2", b"msg3", b"msg4"] + .iter() + .map(|msg| crate::BASE64_URL_SAFE_NO_PAD.encode(msg)) + .collect(); + + let params = GenParams { + nonce: nonce.clone(), + message_count: messages.len() as u32, + }; + + let (pk, sk) = Provider::gen_keypair(params)?; + + let params = SignParams { + nonce: nonce.clone(), + messages: messages.clone(), + }; + + let sig = Provider::sign(&sk, params)?; + + let proof = Provider::create_pok_of_sig( + sig, + messages.clone(), + nonce.clone(), + BTreeSet::from([0, 3]), + )?; + + let success = Provider::verify_pok_of_sig( + proof, + BTreeMap::from([(0, messages[0].clone()), (3, messages[3].clone())]), + pk, + nonce.clone(), + messages.len() as u32, + )?; + + assert!(success); + + Ok(()) + } + } + }; + } + + // NOTE: Why only G1 see above (pok-notes) + test_pok_of_sig!(G1); +} diff --git a/next-gen-signatures/src/crypto/fips204.rs b/next-gen-signatures/src/crypto/fips204.rs new file mode 100644 index 0000000..32d310d --- /dev/null +++ b/next-gen-signatures/src/crypto/fips204.rs @@ -0,0 +1,112 @@ +use base64::Engine; +use fips204::traits::{SerDes, Signer, Verifier}; +use rocket::FromForm; + +use crate::common::{CryptoProvider, NoArguments, TestDefault}; + +#[derive(FromForm)] +pub struct SignParams { + message: String, +} + +impl TestDefault for SignParams { + fn default_for_test() -> Self { + Self { + message: crate::BASE64_URL_SAFE_NO_PAD.encode(b"message"), + } + } +} + +macro_rules! fips_provider_impl { + ($provider_name:ident, $pkg_name:ident) => { + pub struct $provider_name; + impl CryptoProvider for $provider_name { + type GenParams = NoArguments; + type SignParams = SignParams; + type VerifyParams = SignParams; + + type PublicKey = fips204::$pkg_name::PublicKey; + + type SecretKey = fips204::$pkg_name::PrivateKey; + + fn gen_keypair( + _: Self::GenParams, + ) -> anyhow::Result<(Self::PublicKey, Self::SecretKey)> { + fips204::$pkg_name::try_keygen().map_err(|err| anyhow::anyhow!(err)) + } + + fn pk_into_bytes(pk: Self::PublicKey) -> anyhow::Result { + Ok(pk.into_bytes().to_vec()) + } + + fn pk_from_bytes(bytes: crate::common::ByteArray) -> anyhow::Result { + let bytes_len = bytes.len(); + Self::PublicKey::try_from_bytes(bytes.try_into().map_err(|_| { + anyhow::anyhow!( + "Expected an array of length {}, got {}!", + fips204::$pkg_name::PK_LEN, + bytes_len + ) + })?) + .map_err(|err| anyhow::anyhow!(err)) + } + + fn sk_into_bytes(sk: Self::SecretKey) -> anyhow::Result { + Ok(sk.into_bytes().to_vec()) + } + + fn sk_from_bytes(bytes: crate::common::ByteArray) -> anyhow::Result { + let bytes_len = bytes.len(); + Self::SecretKey::try_from_bytes(bytes.try_into().map_err(|_| { + anyhow::anyhow!( + "Expected an array of length {}, got {}!", + fips204::$pkg_name::SK_LEN, + bytes_len + ) + })?) + .map_err(|err| anyhow::anyhow!(err)) + } + + fn sign( + sk: &Self::SecretKey, + params: Self::SignParams, + ) -> anyhow::Result { + let msg = $crate::BASE64_URL_SAFE_NO_PAD.decode(¶ms.message)?; + sk.try_sign(&msg) + .map(|res| res.to_vec()) + .map_err(|err| anyhow::anyhow!(err)) + } + + fn verify( + pk: Self::PublicKey, + sig: crate::common::ByteArray, + params: Self::VerifyParams, + ) -> anyhow::Result { + let msg = $crate::BASE64_URL_SAFE_NO_PAD.decode(¶ms.message)?; + let sig_len = sig.len(); + let sig = sig.try_into().map_err(|_| { + anyhow::anyhow!( + "Expected a signature length of {}, got {}!", + fips204::$pkg_name::SIG_LEN, + sig_len + ) + })?; + Ok(pk.verify(&msg, &sig)) + } + } + }; +} + +fips_provider_impl!(Fips204MlDsa44Provider, ml_dsa_44); +fips_provider_impl!(Fips204MlDsa65Provider, ml_dsa_65); +fips_provider_impl!(Fips204MlDsa87Provider, ml_dsa_87); + +#[cfg(test)] +mod tests { + use super::*; + use crate::test_provider; + + test_provider!(Fips204MlDsa44Provider); + test_provider!(Fips204MlDsa65Provider); + test_provider!(Fips204MlDsa87Provider); +} diff --git a/next-gen-signatures/src/crypto/mod.rs b/next-gen-signatures/src/crypto/mod.rs new file mode 100644 index 0000000..834c7ad --- /dev/null +++ b/next-gen-signatures/src/crypto/mod.rs @@ -0,0 +1,11 @@ +#[cfg(feature = "fips204")] +pub use fips204::{Fips204MlDsa44Provider, Fips204MlDsa65Provider, Fips204MlDsa87Provider}; + +#[cfg(feature = "fips204")] +pub mod fips204; + +#[cfg(feature = "bbs")] +pub use bbs::{BbsPlusG1Provider, BbsPlusG2Provider}; + +#[cfg(feature = "bbs")] +pub mod bbs; diff --git a/next-gen-signatures/src/macros.rs b/next-gen-signatures/src/macros.rs index 1cb8ca8..3a3d318 100644 --- a/next-gen-signatures/src/macros.rs +++ b/next-gen-signatures/src/macros.rs @@ -38,15 +38,12 @@ macro_rules! test_provider { fn test_sign_verify_roundtrip() -> anyhow::Result<()> { let (pk, sk) = $provider::gen_keypair(<$provider as CryptoProvider>::GenParams::default_for_test())?; - let msg = b"Hello, World".to_vec(); let sig = $provider::sign( &sk, - msg.clone(), <$provider as CryptoProvider>::SignParams::default_for_test() )?; let valid = $provider::verify( pk, - msg, sig, <$provider as CryptoProvider>::VerifyParams::default_for_test() )?; @@ -80,39 +77,35 @@ macro_rules! generate_crypto_routes { Ok(Json(KeyPair { public_key: pk, secret_key: sk })) } - #[get("/sign?&&")] + #[get("/sign?&")] #[allow(non_snake_case)] pub(crate) fn [<$provider _sign>]( secret_key: &str, - message: &str, - params: <$crate::crypto::$provider as $crate::common::CryptoProvider>::GenParams + params: <$crate::crypto::$provider as $crate::common::CryptoProvider>::SignParams ) -> anyhow::Result> { use next_gen_signatures::common::CryptoProvider; let sk = BASE64_URL_SAFE_NO_PAD.decode(secret_key).unwrap(); let sk = $crate::crypto::$provider::sk_from_bytes(sk).unwrap(); - let msg = BASE64_URL_SAFE_NO_PAD.decode(message).unwrap(); - let sig = $crate::crypto::$provider::sign(&sk, msg, params).unwrap(); + let sig = $crate::crypto::$provider::sign(&sk, params).unwrap(); Ok(Json(BASE64_URL_SAFE_NO_PAD.encode(sig))) } - #[get("/verify?&&&")] + #[get("/verify?&&")] #[allow(non_snake_case)] pub(crate) fn [<$provider _verify>]( public_key: &str, - message: &str, signature: &str, - params: <$crate::crypto::$provider as $crate::common::CryptoProvider>::GenParams + params: <$crate::crypto::$provider as $crate::common::CryptoProvider>::VerifyParams ) -> Json { use next_gen_signatures::common::CryptoProvider; let pk = BASE64_URL_SAFE_NO_PAD.decode(public_key).unwrap(); let pk = $crate::crypto::$provider::pk_from_bytes(pk).unwrap(); - let msg = BASE64_URL_SAFE_NO_PAD.decode(message).unwrap(); let sig = BASE64_URL_SAFE_NO_PAD.decode(signature).unwrap(); - let valid = $crate::crypto::$provider::verify(pk, msg, sig, params).unwrap(); + let valid = $crate::crypto::$provider::verify(pk, sig, params).unwrap(); Json(valid) } diff --git a/next-gen-signing-service/Cargo.toml b/next-gen-signing-service/Cargo.toml index 46d5706..bfa7c2d 100644 --- a/next-gen-signing-service/Cargo.toml +++ b/next-gen-signing-service/Cargo.toml @@ -10,3 +10,4 @@ next-gen-signatures = { path = "../next-gen-signatures", features = [ paste = "1.0.15" rocket = { version = "0.5.1", features = ["json"] } rocket-errors = "0.1.0" +serde_urlencoded = "0.7.1" diff --git a/next-gen-signing-service/src/main.rs b/next-gen-signing-service/src/main.rs index a8d0f2a..a5983db 100644 --- a/next-gen-signing-service/src/main.rs +++ b/next-gen-signing-service/src/main.rs @@ -22,11 +22,56 @@ mod fips204_routes { } mod bbs_plus_routes { + use std::collections::BTreeMap; + use super::*; - use next_gen_signatures::generate_crypto_routes; + use next_gen_signatures::{common::CryptoProvider, generate_crypto_routes}; generate_crypto_routes!(BbsPlusG1Provider); generate_crypto_routes!(BbsPlusG2Provider); + + #[get("/pok/create?&&&")] + #[allow(non_snake_case)] + pub fn BbsPlusG1Provider_create_pok_of_sig( + signature: String, + messages: Vec, + nonce: String, + revealed_indexes: Vec, + ) -> Json { + use next_gen_signatures::{crypto::BbsPlusG1Provider as Provider, BASE64_URL_SAFE_NO_PAD}; + + let signature = BASE64_URL_SAFE_NO_PAD.decode(signature).unwrap(); + let revealed_indexes = revealed_indexes.into_iter().collect(); + let proof = + Provider::create_pok_of_sig(signature, messages, nonce, revealed_indexes).unwrap(); + + Json(BASE64_URL_SAFE_NO_PAD.encode(proof)) + } + + #[get("/pok/verify?&&&&")] + #[allow(non_snake_case)] + pub fn BbsPlusG1Provider_verify_pok_of_sig( + proof: String, + revealed_messages: BTreeMap, + public_key: String, + nonce: String, + message_count: u32, + ) -> Json { + use next_gen_signatures::{crypto::BbsPlusG1Provider as Provider, BASE64_URL_SAFE_NO_PAD}; + + let proof = BASE64_URL_SAFE_NO_PAD.decode(proof).unwrap(); + + let public_key = BASE64_URL_SAFE_NO_PAD.decode(public_key).unwrap(); + let public_key = Provider::pk_from_bytes(public_key).unwrap(); + + let revealed_messages = revealed_messages.into_iter().collect(); + + let success = + Provider::verify_pok_of_sig(proof, revealed_messages, public_key, nonce, message_count) + .unwrap(); + + Json(success) + } } #[get("/")] @@ -68,6 +113,8 @@ fn rocket() -> _ { bbs_plus_routes::BbsPlusG1Provider_gen_keypair, bbs_plus_routes::BbsPlusG1Provider_sign, bbs_plus_routes::BbsPlusG1Provider_verify, + bbs_plus_routes::BbsPlusG1Provider_create_pok_of_sig, + bbs_plus_routes::BbsPlusG1Provider_verify_pok_of_sig, ], ) .mount( @@ -84,7 +131,7 @@ fn rocket() -> _ { mod test { use crate::VERSION; - use super::rocket; + use super::*; use rocket::local::blocking::Client; use rocket::{http::Status, uri}; @@ -166,7 +213,7 @@ mod test { let response = client .get(format!( - "/bbs+/{g}/sign?secret_key={sk}&message={msg}&nonce={n}&message_count={c}", + "/bbs+/{g}/sign?secret_key={sk}&messages.0={msg}&nonce={n}&message_count={c}", g = stringify!($g), sk = keypair.secret_key, msg = message, @@ -179,7 +226,7 @@ mod test { let signature = response.into_json::().unwrap(); let response = client .get(format!( - "/bbs+/{g}/verify?public_key={pk}&signature={sig}&message={msg}&nonce={n}&message_count={c}", + "/bbs+/{g}/verify?public_key={pk}&signature={sig}&messages.0={msg}&nonce={n}&message_count={c}", g = stringify!($g), pk = keypair.public_key, sig = signature, @@ -213,4 +260,72 @@ mod test { test_roundtrip_bbs_plus!(g1); test_roundtrip_bbs_plus!(g2); + + #[test] + fn test_roundtrip_bbs_plus_g1_pok() { + let nonce = BASE64_URL_SAFE_NO_PAD.encode("nonce"); + let messages = [ + b"message 1", + b"message 2", + b"message 3", + b"message 4", + b"message 5", + ] + .iter() + .map(|m| BASE64_URL_SAFE_NO_PAD.encode(m)) + .collect::>(); + + let client = Client::tracked(rocket()).unwrap(); + let response = client + .get(format!( + "/bbs+/g1/keypair?nonce={n}&message_count={c}", + n = nonce, + c = messages.len() + )) + .dispatch(); + assert_eq!(response.status(), Status::Ok); + + let keypair = response.into_json::().unwrap(); + + let response = client + .get(format!( + "/bbs+/g1/sign?secret_key={sk}&nonce={n}&message_count={c}&messages={msgs}", + sk = keypair.secret_key, + n = nonce, + c = messages.len(), + msgs = messages.join("&messages=") + )) + .dispatch(); + assert_eq!(response.status(), Status::Ok); + + let signature = response.into_json::().unwrap(); + + let response = client + .get(format!( + "/bbs+/g1/pok/create?signature={signature}&nonce={nonce}&messages={msgs}&revealed_indexes={ri}", + msgs = messages.join("&messages="), + ri = ["0", "2", "4"].join("&revealed_indexes=") + )) + .dispatch(); + assert_eq!(response.status(), Status::Ok); + + let proof = response.into_json::().unwrap(); + + let response = client + .get(format!( + "/bbs+/g1/pok/verify?proof={proof}{msgs}&public_key={pk}&nonce={nonce}&message_count={count}", + msgs = [0usize, 2, 4] + .into_iter() + .map(|i| format!("&revealed_messages[{i}]={msg}", msg = &messages[i])) + .collect::>().join(""), + pk = &keypair.public_key, + count = messages.len() + )) + .dispatch(); + assert_eq!(response.status(), Status::Ok); + + let success = response.into_json::().unwrap(); + + assert!(success); + } }