-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor: migrate key generation to blst (#20)
* 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
Showing
36 changed files
with
617 additions
and
11,168 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# stable options | ||
max_width = 80 | ||
use_field_init_shorthand = true |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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), | ||
}), | ||
} | ||
} |
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.