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

feat: migrate key generation to blst #20

Merged
merged 16 commits into from
May 21, 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
9 changes: 5 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,17 @@ wasm = ["getrandom"]

[dependencies]
digest = "0.9"
ff = "0.9"
group = "0.9"
ff = "0.11.0"
group = "0.11.0"
hashbrown = "0.11"
heapless = "0.6"
hkdf = "0.10"
rand = { version = "0.8", features = ["getrandom"] }
getrandom = { version = "0.2", optional = true, features = ["js"] }
rand_core = "0.6"
rand_xorshift = { version = "0.3", optional = true }
pairing = "0.19"
pairing = "0.22.0"
blst_lib = { version = "=0.3.7", package = "blst" }
blstrs = { version = "0.4.2"}
serde = { version = "1.0", features = ["derive"] }
sha2 = "0.9"
sha3 = "0.9"
Expand Down
3 changes: 3 additions & 0 deletions rustfmt.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# stable options
max_width = 80
use_field_init_shorthand = true
13 changes: 13 additions & 0 deletions src/common.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/// Minimum ikm size in bytes.
pub const MIN_IKM_LENGTH_BYTES: usize = 32;

/// Errors.
dev0x1 marked this conversation as resolved.
Show resolved Hide resolved
pub mod error;

/// Secret key type.
pub mod secret_key;

/// Public key type.
pub mod public_key;

mod util;
85 changes: 85 additions & 0 deletions src/common/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/// Error enumerates all possible errors occuring in this library.
#[derive(Debug)]
pub enum Error {
/// A conversion between compatible data types failed.
Conversion { cause: String },

/// A generic failure during underlying cryptographic operation.
CryptoOps,

/// IKM data size is not valid.
CryptoInvalidIkmLength,

/// Type encoding is malformed.
CryptoBadEncoding,

/// Point is not on underlying curve.
CryptoPointNotOnCurve,

/// Point is not in underlying group.
CryptoPointNotOnGroup,

/// Scalar is invalid.
CryptoBadScalar,

/// Error during serialization deserialization in Serde.
Serde,
}

impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match *self {
Error::Conversion { .. } => None,
Error::CryptoOps => None,
Error::CryptoInvalidIkmLength => None,
Error::CryptoBadEncoding => None,
Error::CryptoPointNotOnCurve => None,
Error::CryptoPointNotOnGroup => None,
Error::CryptoBadScalar => None,
Error::Serde => None,
}
}
}

impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match *self {
Error::Conversion { ref cause } => {
write!(f, "data conversion failed: cause: {}", cause)
}
Error::CryptoOps => {
write!(f, "unexpected failure in cryptographic operation.")
}
Error::CryptoInvalidIkmLength => {
write!(f, "IKM size is too short.")
}
Error::CryptoBadEncoding => {
write!(f, "bad encoding encountered.")
}
Error::CryptoPointNotOnCurve => {
write!(f, "point is not on underlying curve.")
}
Error::CryptoPointNotOnGroup => {
write!(f, "point is not in underlying group.")
}
Error::CryptoBadScalar => write!(f, "scalar is invalid."),
Error::Serde => write!(f, "error during ser-de operation."),
}
}
}

impl From<blst_lib::BLST_ERROR> for Error {
fn from(err: blst_lib::BLST_ERROR) -> Error {
match err {
blst_lib::BLST_ERROR::BLST_BAD_ENCODING => Error::CryptoBadEncoding,
blst_lib::BLST_ERROR::BLST_POINT_NOT_ON_CURVE => {
Error::CryptoPointNotOnCurve
}
blst_lib::BLST_ERROR::BLST_POINT_NOT_IN_GROUP => {
Error::CryptoPointNotOnGroup
}
blst_lib::BLST_ERROR::BLST_BAD_SCALAR => Error::CryptoBadScalar,
_ => Error::CryptoOps,
}
}
}
File renamed without changes.
91 changes: 91 additions & 0 deletions src/common/public_key.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
use super::error::Error;
use super::secret_key::SecretKey;
use super::util::vec_to_byte_array;
use blstrs::{G2Affine, G2Projective};
use core::ops::{BitOr, Not};
use group::prime::PrimeCurveAffine;
use group::Curve;
use group::Group;
use serde::{Deserialize, Serialize};
use subtle::Choice;

/// Number of bytes needed to represent the public key in compressed form
pub(crate) const G2_COMPRESSED_SIZE: usize = 96;

/// A BBS public key
dev0x1 marked this conversation as resolved.
Show resolved Hide resolved
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct PublicKey(pub(crate) G2Projective);

impl Default for PublicKey {
fn default() -> Self {
Self(G2Projective::identity())
}
}

impl From<&SecretKey> for PublicKey {
fn from(s: &SecretKey) -> Self {
let mut pk = G2Affine::identity();

unsafe {
blst_lib::blst_sk_to_pk2_in_g2(
std::ptr::null_mut(),
pk.as_mut(),
&s.0.into(),
);
}

PublicKey(pk.into())
}
}

impl From<PublicKey> for [u8; PublicKey::SIZE_BYTES] {
fn from(pk: PublicKey) -> Self {
pk.to_bytes()
}
}

impl<'a> From<&'a PublicKey> for [u8; PublicKey::SIZE_BYTES] {
fn from(pk: &'a PublicKey) -> [u8; PublicKey::SIZE_BYTES] {
pk.to_bytes()
}
}

impl PublicKey {
/// Number of bytes needed to represent the public key in compressed form
pub const SIZE_BYTES: usize = G2_COMPRESSED_SIZE;

/// Check if this signature is valid
pub fn is_valid(&self) -> Choice {
self.0.is_identity().not().bitor(self.0.is_on_curve())
}

/// Check if this signature is invalid
pub fn is_invalid(&self) -> Choice {
self.0.is_identity().bitor(self.0.is_on_curve().not())
}

/// Get the byte representation of this key
pub fn to_bytes(&self) -> [u8; Self::SIZE_BYTES] {
self.0.to_affine().to_compressed()
}

/// Convert a vector of bytes of big-endian representation of the public key
pub fn from_vec(bytes: Vec<u8>) -> Result<Self, Error> {
match vec_to_byte_array::<{ Self::SIZE_BYTES }>(bytes) {
Ok(result) => Self::from_bytes(&result),
Err(e) => Err(e),
}
}

/// Convert a big-endian representation of the public key
pub fn from_bytes(bytes: &[u8; Self::SIZE_BYTES]) -> Result<Self, Error> {
let result = G2Affine::from_compressed(bytes)
.map(|p| Self(G2Projective::from(&p)));

if result.is_some().unwrap_u8() == 1u8 {
Ok(result.unwrap())
} else {
Err(Error::CryptoBadEncoding)
}
}
}
137 changes: 137 additions & 0 deletions src/common/secret_key.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
use super::{error::Error, util::vec_to_byte_array};
use crate::common::MIN_IKM_LENGTH_BYTES;
use blstrs::Scalar;
use ff::{Field, PrimeField};
use rand::{CryptoRng, RngCore};
use serde::{Deserialize, Serialize};
use zeroize::DefaultIsZeroes;

/// The secret key is field element 0 < `x` < `r`
/// where `r` is the curve order. See Section 4.3 in
/// <https://eprint.iacr.org/2016/663.pdf>
tplooker marked this conversation as resolved.
Show resolved Hide resolved
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct SecretKey(pub Scalar);

impl Default for SecretKey {
fn default() -> Self {
Self(Scalar::zero())
}
}

impl DefaultIsZeroes for SecretKey {}

impl From<SecretKey> for [u8; SecretKey::SIZE_BYTES] {
fn from(sk: SecretKey) -> [u8; SecretKey::SIZE_BYTES] {
sk.to_bytes()
}
}

impl<'a> From<&'a SecretKey> for [u8; SecretKey::SIZE_BYTES] {
fn from(sk: &'a SecretKey) -> [u8; SecretKey::SIZE_BYTES] {
sk.to_bytes()
}
}

impl SecretKey {
/// Number of bytes needed to represent the secret key
pub const SIZE_BYTES: usize = (Scalar::NUM_BITS as usize + 8 - 1) / 8;

/// Computes a secret key from an IKM, as defined by
/// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-bls-signature-04#section-2.3
/// Note this procedure does not follow
/// https://identity.foundation/bbs-signature/draft-bbs-signatures.html#name-keygen
pub fn new<T1, T2>(ikm: T1, key_info: T2) -> Option<Self>
where
T1: AsRef<[u8]>,
T2: AsRef<[u8]>,
{
generate_secret_key(ikm, key_info).ok()
}

/// Compute a secret key from a CS-PRNG
pub fn random<R>(rng: &mut R) -> Option<Self>
where
R: RngCore + CryptoRng,
{
let mut ikm = [0u8; Self::SIZE_BYTES];
rng.try_fill_bytes(&mut ikm)
.expect("failed to draw bytes from random number generator.");

let key_info = [];

Self::new(ikm, key_info)
}

/// Convert a vector of bytes of big-endian representation of the secret key
pub fn from_vec(bytes: Vec<u8>) -> Result<Self, Error> {
match vec_to_byte_array::<{ Self::SIZE_BYTES }>(bytes) {
Ok(result) => Self::from_bytes(&result),
Err(e) => Err(e),
}
}

/// Convert the secret key to a big-endian representation
pub fn to_bytes(&self) -> [u8; Self::SIZE_BYTES] {
self.0.to_bytes_be()
}

/// Convert a big-endian representation of the secret key
pub fn from_bytes(bytes: &[u8; Self::SIZE_BYTES]) -> Result<Self, Error> {
let result = Scalar::from_bytes_be(bytes).map(SecretKey);

if result.is_some().unwrap_u8() == 1u8 {
Ok(result.unwrap())
} else {
Err(Error::CryptoBadEncoding)
}
}
}

/// Computes a secret key from an IKM, as defined by
/// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-bls-signature-04#section-2.3
/// Note this procedure does not follow
/// https://identity.foundation/bbs-signature/draft-bbs-signatures.html#name-keygen
fn generate_secret_key<T1, T2>(
ikm: T1,
key_info: T2,
) -> Result<SecretKey, Error>
where
T1: AsRef<[u8]>,
T2: AsRef<[u8]>,
{
use core::convert::TryInto;
let ikm = ikm.as_ref();
if ikm.len() < MIN_IKM_LENGTH_BYTES {
return Err(Error::CryptoInvalidIkmLength);
}

let key_info = key_info.as_ref();
let mut out = blst_lib::blst_scalar::default();
unsafe {
blst_lib::blst_keygen(
&mut out,
ikm.as_ptr(),
ikm.len(),
key_info.as_ptr(),
key_info.len(),
)
};

let out: Scalar = out.try_into().expect("error during key generation");

Ok(SecretKey(out))
}

#[test]
fn test_from_seed() {
let seed = [0u8; MIN_IKM_LENGTH_BYTES];
let key_info = [];

let sk = SecretKey::new(seed, key_info);
let expected = [
77, 18, 154, 25, 223, 134, 160, 245, 52, 91, 173, 76, 198, 242, 73,
236, 42, 129, 156, 204, 51, 134, 137, 91, 235, 79, 125, 152, 179, 219,
98, 53,
];
assert_eq!(sk.unwrap().to_bytes(), expected);
}
14 changes: 14 additions & 0 deletions src/common/util.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
use crate::common::error::Error;

pub fn vec_to_byte_array<const N: usize>(
vec: Vec<u8>,
) -> Result<[u8; N], Error> {
use core::convert::TryFrom;
let data_len = vec.len();
match <[u8; N]>::try_from(vec) {
Ok(result) => Ok(result),
Err(_) => Err(Error::Conversion {
cause: format!("source vector size {}, expected destination byte array size {}", data_len, N),
}),
}
}
1 change: 0 additions & 1 deletion src/curves.rs

This file was deleted.

Loading