Skip to content

Commit

Permalink
refactor: migrate key generation to blst (#20)
Browse files Browse the repository at this point in the history
* chore: add blst and blstrs packages

bumping dependency version needed to resolve
ferrilab/funty#3

* chore: remove bls12-381 curve implementation

plan is to migrate to `blst` crate

* refactor: move common data structures

* chore: add rustfmt config

* feat: add public facing error type

* feat: migrate secret key generation using blst

* chore: remove hkdf package

* fix: accept review comment

Co-authored-by: Tobias Looker <[email protected]>

* fix: accept review comment

Co-authored-by: Tobias Looker <[email protected]>

* fix: accept review comment

Co-authored-by: Tobias Looker <[email protected]>

* fix: accept review comment

Co-authored-by: Tobias Looker <[email protected]>

* feat: base SecretKey upon blstrs::Scalar

- using blstrs::Scalar gives a bit abstraction over blst_scalar
- we can switch or additionally support any `ff` and `group` based impl

* feat: migrating public key generation to blst

Fixes #16

* chore: cargo fmt

* chore: apply suggestions from code review

Co-authored-by: Tobias Looker <[email protected]>

* chore: fix review comments

Co-authored-by: Tobias Looker <[email protected]>
  • Loading branch information
dev0x1 and tplooker authored May 21, 2022
1 parent 3f55b04 commit c2859cc
Show file tree
Hide file tree
Showing 36 changed files with 617 additions and 11,168 deletions.
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.
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
#[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>
#[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

0 comments on commit c2859cc

Please sign in to comment.