Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SignatureScheme trait bounds #148

Merged
merged 16 commits into from
Dec 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion plonk/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ derivative = { version = "2", features = ["use_core"] }
displaydoc = { version = "0.2.3", default-features = false }
downcast-rs = { version = "1.2.0", default-features = false }
dyn-clone = "^1.0"
espresso-systems-common = { git = "https://github.com/espressosystems/espresso-systems-common", branch = "main" }
espresso-systems-common = { git = "https://github.com/espressosystems/espresso-systems-common", tag = "0.4.0" }
hashbrown = "0.12.3"
itertools = { version = "0.10.1", default-features = false }
jf-primitives = { path = "../primitives", default-features = false }
Expand Down
2 changes: 1 addition & 1 deletion primitives/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ crypto_box = "0.8.1"
derivative = { version = "2", features = ["use_core"] }
digest = { version = "0.10.1", default-features = false, features = ["alloc"] }
displaydoc = { version = "0.2.3", default-features = false }
espresso-systems-common = { git = "https://github.com/espressosystems/espresso-systems-common", branch = "main" }
espresso-systems-common = { git = "https://github.com/espressosystems/espresso-systems-common", tag = "0.4.0" }
generic-array = { version = "^0.14", default-features = false }
itertools = { version = "0.10.1", default-features = false, features = [ "use_alloc" ] }
jf-relation = { path = "../relation", default-features = false }
Expand Down
7 changes: 7 additions & 0 deletions primitives/src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,10 @@ pub const CS_ID_SCHNORR: &str = "SCHNORR_WITH_RESCUE_HASH_v01";

/// ciphersuite identifier for BLS signature
pub const CS_ID_BLS_SIG_NAIVE: &str = "BLS_SIG_WITH_NAIVE_HtG_v01";

/// Size in bytes of a secret key in our BLS signature scheme.
pub const BLS_SIG_KEY_SIZE: usize = 32;
/// Size in bytes of a signature in our BLS signature scheme.
pub const BLS_SIG_SIGNATURE_SIZE: usize = 96;
/// Size in bytes of a verification key in our BLS signature scheme.
pub const BLS_SIG_VERKEY_SIZE: usize = 192;
178 changes: 171 additions & 7 deletions primitives/src/signatures/bls.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,151 @@
//! BLS Signature Scheme

use super::SignatureScheme;
use crate::{constants::CS_ID_BLS_SIG_NAIVE, errors::PrimitivesError};
use crate::{
constants::{
BLS_SIG_KEY_SIZE, BLS_SIG_SIGNATURE_SIZE, BLS_SIG_VERKEY_SIZE, CS_ID_BLS_SIG_NAIVE,
},
errors::PrimitivesError,
};

use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, SerializationError};
use ark_std::{
convert::TryInto,
format,
rand::{CryptoRng, RngCore},
};

use blst::{min_sig::*, BLST_ERROR};
use blst::BLST_ERROR;
use espresso_systems_common::jellyfish::tag;
use tagged_base64::tagged;

pub use blst::min_sig::{
PublicKey as BLSVerKey, SecretKey as BLSSignKey, Signature as BLSSignature,
};
pub use blst::min_sig::{PublicKey, SecretKey, Signature};
use zeroize::Zeroize;

/// Newtype wrapper for a BLS Signing Key.
#[tagged(tag::BLS_SIGNING_KEY)]
#[derive(Clone, Debug, Zeroize)]
pub struct BLSSignKey(SecretKey);

impl core::ops::Deref for BLSSignKey {
type Target = SecretKey;

fn deref(&self) -> &Self::Target {
&self.0
}
}

impl CanonicalSerialize for BLSSignKey {
fn serialized_size(&self) -> usize {
BLS_SIG_KEY_SIZE
}

fn serialize<W: ark_serialize::Write>(&self, writer: W) -> Result<(), SerializationError> {
let bytes = &self.0.serialize();
CanonicalSerialize::serialize(bytes.as_ref(), writer)
}
}

impl CanonicalDeserialize for BLSSignKey {
fn deserialize<R: ark_serialize::Read>(mut reader: R) -> Result<Self, SerializationError> {
let len = <usize as ark_serialize::CanonicalDeserialize>::deserialize(&mut reader)?;
if len != BLS_SIG_KEY_SIZE {
return Err(SerializationError::InvalidData);
}

let mut key = [0u8; BLS_SIG_KEY_SIZE];
reader.read_exact(&mut key)?;
SecretKey::deserialize(&key)
.map(Self)
.map_err(|_| SerializationError::InvalidData)
}
}

impl PartialEq for BLSSignKey {
fn eq(&self, other: &Self) -> bool {
self.0.serialize() == other.0.serialize()
}
}

impl Eq for BLSSignKey {}

/// Newtype wrapper for a BLS Signature.
#[tagged(tag::BLS_SIG)]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct BLSSignature(Signature);

impl core::ops::Deref for BLSSignature {
type Target = Signature;

fn deref(&self) -> &Self::Target {
&self.0
}
}
Comment on lines +77 to +83
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see manual impl of Deref often, especially not for newtype, may I ask why do we need this?

(some say that implementing Deref for newtype is a bad idea)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a convenient way of allowing calls to the underlying value directly, e.g. sig.verify(...) instead of sig.0.verify(...).

When you say you don't see it done manually, do you mean it's mostly done via a derive, like derive-more crate? Or generally not done via Deref?
Here's a some points against using it: https://rust-unofficial.github.io/patterns/anti_patterns/deref.html
Though also in the discussion you linked there are some strong arguments for using Deref (not DerefMut) specifically for the purpose of newtypes.

In this specific case I don't see an immediate danger of implementing Deref though. If you do, I'm not gonna fight for keeping Deref and happy to use another solution.
Let's also hear other opinions?

Copy link
Contributor

@alxiong alxiong Nov 30, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah I also agree there's no intermediate harm, but the anti-pattern does introduce mental load as

Most importantly this is a surprising idiom - future programmers reading this in code will not expect this to happen.

it's abusing the Deref which is supposed to be custom pointer type, not newtype accessing its inner value.
(another testimony of the same problem)

I don't mind too much about sig.0.verify(), it's internal details, won't get exposed to consumer anyway.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've removed Deref as a trial in 7149b1a. We can still revert later :)

Copy link
Contributor

@alxiong alxiong Dec 5, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So as I dig deeper, there are some subtleties involved.

TL;DR: yes, in our case, we can use Deref as you originally proposed. @tessico

this comment rust-lang/api-guidelines#249 (comment) convinced me that using Deref for transparent newtype is not only okay but also practically seen in std lib and some high-profile crates.
So is suggested in the Rust book itself.

What I'm also more sure of is:

  • we should NOT use Deref to simulate OOP-style inheritance (instead, we should wait for delegation to be available in rust, or use ambassador crate to achieve that)
  • we should be careful about the potential violation of intended visibility when implementing DerefMut (to access fn method(&mut self) of the underlying types). see my example here.

Jon Gjengset had updated a good summary here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@alxiong Nice research! I knew I saw it in the Rust book somewhere, I must have missed it last time around.
It's encouraging to see it used in std :)


impl CanonicalSerialize for BLSSignature {
fn serialized_size(&self) -> usize {
BLS_SIG_SIGNATURE_SIZE
}

fn serialize<W: ark_serialize::Write>(&self, writer: W) -> Result<(), SerializationError> {
let bytes = &self.0.serialize();
CanonicalSerialize::serialize(bytes.as_ref(), writer)
}
}

impl CanonicalDeserialize for BLSSignature {
fn deserialize<R: ark_serialize::Read>(mut reader: R) -> Result<Self, SerializationError> {
let len = <usize as ark_serialize::CanonicalDeserialize>::deserialize(&mut reader)?;
if len != BLS_SIG_SIGNATURE_SIZE {
return Err(SerializationError::InvalidData);
}

let mut sig = [0u8; BLS_SIG_SIGNATURE_SIZE];
reader.read_exact(&mut sig)?;
Signature::deserialize(&sig)
.map(Self)
.map_err(|_| SerializationError::InvalidData)
}
}

/// Newtype wrapper for a BLS Verification Key.
#[tagged(tag::BLS_VER_KEY)]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct BLSVerKey(PublicKey);

impl core::ops::Deref for BLSVerKey {
type Target = PublicKey;

fn deref(&self) -> &Self::Target {
&self.0
}
}

impl CanonicalSerialize for BLSVerKey {
fn serialized_size(&self) -> usize {
BLS_SIG_VERKEY_SIZE
}

fn serialize<W: ark_serialize::Write>(&self, writer: W) -> Result<(), SerializationError> {
let bytes = &self.0.serialize();
CanonicalSerialize::serialize(bytes.as_ref(), writer)
}
}

impl CanonicalDeserialize for BLSVerKey {
fn deserialize<R: ark_serialize::Read>(mut reader: R) -> Result<Self, SerializationError> {
let len = <usize as ark_serialize::CanonicalDeserialize>::deserialize(&mut reader)?;
if len != BLS_SIG_VERKEY_SIZE {
return Err(SerializationError::InvalidData);
}

let mut key = [0u8; BLS_SIG_VERKEY_SIZE];
reader.read_exact(&mut key)?;
PublicKey::deserialize(&key)
.map(Self)
.map_err(|_| SerializationError::InvalidData)
}
}

/// BLS signature scheme. Imports blst library.
pub struct BLSSignatureScheme;
Expand Down Expand Up @@ -55,7 +188,7 @@ impl SignatureScheme for BLSSignatureScheme {
Err(e) => return Err(PrimitivesError::InternalError(format!("{:?}", e))),
};
let vk = sk.sk_to_pk();
Ok((sk, vk))
Ok((BLSSignKey(sk), BLSVerKey(vk)))
}

/// Sign a message
Expand All @@ -65,7 +198,11 @@ impl SignatureScheme for BLSSignatureScheme {
msg: M,
_prng: &mut R,
) -> Result<Self::Signature, PrimitivesError> {
Ok(sk.sign(msg.as_ref(), Self::CS_ID.as_bytes(), &[]))
Ok(BLSSignature(sk.sign(
msg.as_ref(),
Self::CS_ID.as_bytes(),
&[],
)))
}

/// Verify a signature.
Expand All @@ -84,6 +221,8 @@ impl SignatureScheme for BLSSignatureScheme {

#[cfg(test)]
mod test {
use ark_std::{test_rng, vec::Vec};

use super::*;
use crate::signatures::tests::{failed_verification, sign_and_verify};

Expand All @@ -94,4 +233,29 @@ mod test {
sign_and_verify::<BLSSignatureScheme>(message.as_ref());
failed_verification::<BLSSignatureScheme>(message.as_ref(), message_bad.as_ref());
}

#[test]
fn test_bls_sig_serde() {
let rng = &mut test_rng();
let parameters = BLSSignatureScheme::param_gen(Some(rng)).unwrap();
let (sk, vk) = BLSSignatureScheme::key_gen(&parameters, rng).unwrap();

// serde for Verification Key
let mut keypair_bytes = Vec::new();
vk.serialize(&mut keypair_bytes).unwrap();
let keypair_de = BLSVerKey::deserialize(&keypair_bytes[..]).unwrap();
assert_eq!(vk, keypair_de);
// wrong byte length
assert!(BLSVerKey::deserialize(&keypair_bytes[1..]).is_err());

// serde for Signature
let message = "this is a test message";
let sig = BLSSignatureScheme::sign(&parameters, &sk, message.as_bytes(), rng).unwrap();
let mut sig_bytes = Vec::new();
sig.serialize(&mut sig_bytes).unwrap();
let sig_de = BLSSignature::deserialize(&sig_bytes[..]).unwrap();
assert_eq!(sig, sig_de);
// wrong byte length
assert!(BLSSignature::deserialize(&sig_bytes[1..]).is_err());
}
}
34 changes: 29 additions & 5 deletions primitives/src/signatures/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ use ark_std::rand::{CryptoRng, RngCore};
pub mod bls;
pub mod schnorr;
pub use bls::BLSSignatureScheme;
use core::fmt::Debug;
pub use schnorr::SchnorrSignatureScheme;

use serde::{Deserialize, Serialize};
use zeroize::Zeroize;
/// Trait definition for a signature scheme.
// A signature scheme is associated with a hash function H that is
// to be used for challenge generation.
Expand All @@ -17,19 +19,41 @@ pub trait SignatureScheme {
const CS_ID: &'static str;

/// Signing key.
type SigningKey;
type SigningKey: Debug
+ Clone
+ Send
+ Sync
+ Zeroize
+ for<'a> Deserialize<'a>
+ Serialize
+ PartialEq
+ Eq;

/// Verification key
type VerificationKey;
type VerificationKey: Debug
+ Clone
+ Send
+ Sync
+ for<'a> Deserialize<'a>
+ Serialize
+ PartialEq
+ Eq;

/// Public Parameter
type PublicParameter;

/// Signature
type Signature;
type Signature: Debug
+ Clone
+ Send
+ Sync
+ for<'a> Deserialize<'a>
+ Serialize
+ PartialEq
+ Eq;

/// A message is &\[MessageUnit\]
type MessageUnit;
type MessageUnit: Debug + Clone + Send + Sync;

/// generate public parameters from RNG.
/// If the RNG is not presented, use the default group generator.
Expand Down
7 changes: 4 additions & 3 deletions primitives/src/signatures/schnorr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ where
// =====================================================
// Signing key
// =====================================================
#[tagged(tag::SCHNORR_SIGNING_KEY)]
#[derive(
Clone, Hash, Default, Zeroize, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize, Debug,
)]
Expand All @@ -127,7 +128,7 @@ impl<F: PrimeField> SignKey<F> {

/// Signature public verification key
// derive zeroize here so that keypair can be zeroized
#[tagged(tag::SCHNORRVERKEY)]
#[tagged(tag::SCHNORR_VER_KEY)]
#[derive(CanonicalSerialize, CanonicalDeserialize, Derivative)]
#[derivative(
Debug(bound = "P: Parameters"),
Expand Down Expand Up @@ -196,7 +197,7 @@ impl<P: Parameters> VerKey<P> {

/// Signature secret key pair used to sign messages
// make sure sk can be zeroized
#[tagged(tag::SIGNKEYPAIR)]
#[tagged(tag::SCHNORR_KEY_PAIR)]
#[derive(CanonicalSerialize, CanonicalDeserialize, Derivative)]
#[derivative(
Debug(bound = "P: Parameters"),
Expand All @@ -217,7 +218,7 @@ where
// =====================================================

/// The signature of Schnorr signature scheme
#[tagged(tag::SIG)]
#[tagged(tag::SCHNORR_SIG)]
#[derive(CanonicalSerialize, CanonicalDeserialize, Derivative)]
#[derivative(
Debug(bound = "P: Parameters"),
Expand Down
6 changes: 3 additions & 3 deletions utilities/src/serialize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@
use ark_std::vec::Vec;
use serde::{Deserialize, Serialize};

/// A helper for converting CanonicalSerde bytes to standard Serde bytes.
/// use this struct as intermediate target instead of directly deriving
/// serde::Serialize/Deserialize to avoid implementation of Visitors.
/// A helper for converting ark_serialize::CanonicalSerialize bytes to standard
/// Serde bytes. Use this struct as intermediate target instead of directly
/// deriving serde::Serialize/Deserialize to avoid implementation of Visitors.
#[derive(Serialize, Deserialize)]
pub struct CanonicalBytes(pub Vec<u8>);

Expand Down