add support for deriving identifiers from arbitary strings (#418)
This commit is contained in:
parent
bed23c3e1d
commit
78b5c44de0
|
@ -23,8 +23,12 @@ ciphersuite) which identifies a specific party. There are no restrictions to
|
|||
them other than being unique for each participant and being in the valid range.
|
||||
|
||||
In the ZF FROST library, they are either automatically generated incrementally
|
||||
during key generation or can be instantiated from a byte array using
|
||||
[`Identifier::deserialize()`](https://docs.rs/frost-core/latest/frost_core/frost/struct.Identifier.html#method.deserialize).
|
||||
during key generation or converted from a `u16` using a
|
||||
[`TryFrom<u16>`](https://docs.rs/frost-core/latest/frost_core/frost/struct.Identifier.html#impl-TryFrom%3Cu16%3E-for-Identifier%3CC%3E).
|
||||
|
||||
ZF FROST also allows deriving identifiers from arbitrary byte strings with
|
||||
[`Identifier::derive()`](https://docs.rs/frost-core/latest/frost_core/frost/struct.Identifier.html#method.derive).
|
||||
This allows deriving identifiers from usernames or emails, for example.
|
||||
|
||||
### _Peer to peer channel_
|
||||
|
||||
|
|
|
@ -80,6 +80,9 @@ pub enum Error<C: Ciphersuite> {
|
|||
/// Error in coefficient commitment deserialization.
|
||||
#[error("Invalid coefficient")]
|
||||
InvalidCoefficient,
|
||||
/// The ciphersuite does not support deriving identifiers from strings.
|
||||
#[error("The ciphersuite does not support deriving identifiers from strings.")]
|
||||
IdentifierDerivationNotSupported,
|
||||
}
|
||||
|
||||
impl<C> Error<C>
|
||||
|
@ -119,7 +122,8 @@ where
|
|||
| Error::DKGNotSupported
|
||||
| Error::FieldError(_)
|
||||
| Error::GroupError(_)
|
||||
| Error::InvalidCoefficient => None,
|
||||
| Error::InvalidCoefficient
|
||||
| Error::IdentifierDerivationNotSupported => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,6 +25,29 @@ impl<C> Identifier<C>
|
|||
where
|
||||
C: Ciphersuite,
|
||||
{
|
||||
/// Create a new Identifier from a scalar. For internal use only.
|
||||
fn new(scalar: Scalar<C>) -> Result<Self, Error<C>> {
|
||||
if scalar == <<C::Group as Group>::Field>::zero() {
|
||||
Err(FieldError::InvalidZeroScalar.into())
|
||||
} else {
|
||||
Ok(Self(scalar))
|
||||
}
|
||||
}
|
||||
|
||||
/// Derive an Identifier from an arbitrary byte string.
|
||||
///
|
||||
/// This feature is not part of the specification and is just a convenient
|
||||
/// way of creating identifiers.
|
||||
///
|
||||
/// Each possible byte string will map to an uniformly random identifier.
|
||||
/// Returns an error if the ciphersuite does not support identifier derivation,
|
||||
/// or if the mapped identifier is zero (which is unpredictable, but should happen
|
||||
/// with negligible probability).
|
||||
pub fn derive(s: &[u8]) -> Result<Self, Error<C>> {
|
||||
let scalar = C::HID(s).ok_or(Error::IdentifierDerivationNotSupported)?;
|
||||
Self::new(scalar)
|
||||
}
|
||||
|
||||
/// Serialize the identifier using the ciphersuite encoding.
|
||||
pub fn serialize(&self) -> <<C::Group as Group>::Field as Field>::Serialization {
|
||||
<<C::Group as Group>::Field>::serialize(&self.0)
|
||||
|
@ -36,11 +59,7 @@ where
|
|||
buf: &<<C::Group as Group>::Field as Field>::Serialization,
|
||||
) -> Result<Self, Error<C>> {
|
||||
let scalar = <<C::Group as Group>::Field>::deserialize(buf)?;
|
||||
if scalar == <<C::Group as Group>::Field>::zero() {
|
||||
Err(FieldError::InvalidZeroScalar.into())
|
||||
} else {
|
||||
Ok(Self(scalar))
|
||||
}
|
||||
Self::new(scalar)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -292,6 +292,17 @@ pub trait Ciphersuite: Copy + Clone + PartialEq + Debug {
|
|||
None
|
||||
}
|
||||
|
||||
/// Hash function for a FROST ciphersuite, used for deriving identifiers from strings.
|
||||
///
|
||||
/// This feature is not part of the specification and is just a convenient
|
||||
/// way of creating identifiers. Therefore it can return None if this is not supported by the
|
||||
/// Ciphersuite. This is the default implementation.
|
||||
///
|
||||
/// Maps arbitrary inputs to non-zero `Self::Scalar` elements of the prime-order group scalar field.
|
||||
fn HID(_m: &[u8]) -> Option<<<Self::Group as Group>::Field as Field>::Scalar> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Verify a signature for this ciphersuite. The default implementation uses the "cofactored"
|
||||
/// equation (it multiplies by the cofactor returned by [`Group::cofactor()`]).
|
||||
///
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
use std::{collections::HashMap, convert::TryFrom};
|
||||
|
||||
use crate::{
|
||||
frost::{self},
|
||||
frost::{self, Identifier},
|
||||
Error, Field, Group, Signature, VerifyingKey,
|
||||
};
|
||||
use debugless_unwrap::DebuglessUnwrapErr;
|
||||
|
@ -380,3 +380,13 @@ pub fn check_error_culprit<C: Ciphersuite>() {
|
|||
let e: Error<C> = Error::InvalidSignature;
|
||||
assert_eq!(e.culprit(), None);
|
||||
}
|
||||
|
||||
/// Test identifier derivation with a Ciphersuite
|
||||
pub fn check_identifier_derivation<C: Ciphersuite>() {
|
||||
let id1a = Identifier::<C>::derive("username1".as_bytes()).unwrap();
|
||||
let id1b = Identifier::<C>::derive("username1".as_bytes()).unwrap();
|
||||
let id2 = Identifier::<C>::derive("username2".as_bytes()).unwrap();
|
||||
|
||||
assert!(id1a == id1b);
|
||||
assert!(id1a != id2);
|
||||
}
|
||||
|
|
|
@ -202,6 +202,11 @@ impl Ciphersuite for Ed25519Sha512 {
|
|||
fn HDKG(m: &[u8]) -> Option<<<Self::Group as Group>::Field as Field>::Scalar> {
|
||||
Some(hash_to_scalar(&[CONTEXT_STRING.as_bytes(), b"dkg", m]))
|
||||
}
|
||||
|
||||
/// HID for FROST(Ed25519, SHA-512)
|
||||
fn HID(m: &[u8]) -> Option<<<Self::Group as Group>::Field as Field>::Scalar> {
|
||||
Some(hash_to_scalar(&[CONTEXT_STRING.as_bytes(), b"id", m]))
|
||||
}
|
||||
}
|
||||
|
||||
type E = Ed25519Sha512;
|
||||
|
|
|
@ -1,26 +1,13 @@
|
|||
use crate::Ed25519Sha512;
|
||||
use frost_ed25519::*;
|
||||
use lazy_static::lazy_static;
|
||||
use rand::thread_rng;
|
||||
use serde_json::Value;
|
||||
|
||||
mod helpers;
|
||||
|
||||
#[test]
|
||||
fn check_sign_with_dkg() {
|
||||
let rng = thread_rng();
|
||||
|
||||
// Test with multiple keys/signatures to better exercise the key generation
|
||||
// and the interoperability check. A smaller number of iterations is used
|
||||
// because DKG takes longer and otherwise the test would be too slow.
|
||||
for _ in 0..32 {
|
||||
let (msg, group_signature, group_pubkey) =
|
||||
frost_core::tests::ciphersuite_generic::check_sign_with_dkg::<Ed25519Sha512, _>(
|
||||
rng.clone(),
|
||||
);
|
||||
|
||||
helpers::verify_signature(&msg, group_signature, group_pubkey);
|
||||
}
|
||||
frost_core::tests::ciphersuite_generic::check_sign_with_dkg::<Ed25519Sha512, _>(rng);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -34,18 +21,7 @@ fn check_rts() {
|
|||
fn check_sign_with_dealer() {
|
||||
let rng = thread_rng();
|
||||
|
||||
// Test with multiple keys/signatures to better exercise the key generation
|
||||
// and the interoperability check.
|
||||
for _ in 0..256 {
|
||||
let (msg, group_signature, group_pubkey) =
|
||||
frost_core::tests::ciphersuite_generic::check_sign_with_dealer::<Ed25519Sha512, _>(
|
||||
rng.clone(),
|
||||
);
|
||||
|
||||
// Check that the threshold signature can be verified by the `ed25519_dalek` crate
|
||||
// public key (interoperability test)
|
||||
helpers::verify_signature(&msg, group_signature, group_pubkey);
|
||||
}
|
||||
frost_core::tests::ciphersuite_generic::check_sign_with_dealer::<Ed25519Sha512, _>(rng);
|
||||
}
|
||||
|
||||
/// This is testing that Shamir's secret sharing to compute and arbitrary
|
||||
|
@ -72,3 +48,8 @@ fn check_sign_with_test_vectors() {
|
|||
&VECTORS_BIG_IDENTIFIER,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_identifier_derivation() {
|
||||
frost_core::tests::ciphersuite_generic::check_identifier_derivation::<Ed25519Sha512>();
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
use crate::Ed25519Sha512;
|
||||
use frost_ed25519::*;
|
||||
use rand::thread_rng;
|
||||
|
||||
mod helpers;
|
||||
|
||||
#[test]
|
||||
fn check_interoperability_in_sign_with_dkg() {
|
||||
let rng = thread_rng();
|
||||
|
||||
// Test with multiple keys/signatures to better exercise the key generation
|
||||
// and the interoperability check. A smaller number of iterations is used
|
||||
// because DKG takes longer and otherwise the test would be too slow.
|
||||
for _ in 0..32 {
|
||||
let (msg, group_signature, group_pubkey) =
|
||||
frost_core::tests::ciphersuite_generic::check_sign_with_dkg::<Ed25519Sha512, _>(
|
||||
rng.clone(),
|
||||
);
|
||||
|
||||
helpers::verify_signature(&msg, group_signature, group_pubkey);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_interoperability_in_sign_with_dealer() {
|
||||
let rng = thread_rng();
|
||||
|
||||
// Test with multiple keys/signatures to better exercise the key generation
|
||||
// and the interoperability check.
|
||||
for _ in 0..256 {
|
||||
let (msg, group_signature, group_pubkey) =
|
||||
frost_core::tests::ciphersuite_generic::check_sign_with_dealer::<Ed25519Sha512, _>(
|
||||
rng.clone(),
|
||||
);
|
||||
|
||||
// Check that the threshold signature can be verified by the `ed25519_dalek` crate
|
||||
// public key (interoperability test)
|
||||
helpers::verify_signature(&msg, group_signature, group_pubkey);
|
||||
}
|
||||
}
|
|
@ -197,6 +197,11 @@ impl Ciphersuite for Ed448Shake256 {
|
|||
fn HDKG(m: &[u8]) -> Option<<<Self::Group as Group>::Field as Field>::Scalar> {
|
||||
Some(hash_to_scalar(&[CONTEXT_STRING.as_bytes(), b"dkg", m]))
|
||||
}
|
||||
|
||||
/// HID for FROST(Ed448, SHAKE256)
|
||||
fn HID(m: &[u8]) -> Option<<<Self::Group as Group>::Field as Field>::Scalar> {
|
||||
Some(hash_to_scalar(&[CONTEXT_STRING.as_bytes(), b"id", m]))
|
||||
}
|
||||
}
|
||||
|
||||
type E = Ed448Shake256;
|
||||
|
|
|
@ -225,6 +225,14 @@ impl Ciphersuite for P256Sha256 {
|
|||
m,
|
||||
))
|
||||
}
|
||||
|
||||
/// HID for FROST(P-256, SHA-256)
|
||||
fn HID(m: &[u8]) -> Option<<<Self::Group as Group>::Field as Field>::Scalar> {
|
||||
Some(hash_to_scalar(
|
||||
(CONTEXT_STRING.to_owned() + "id").as_bytes(),
|
||||
m,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
// Shorthand alias for the ciphersuite
|
||||
|
|
|
@ -191,6 +191,11 @@ impl Ciphersuite for Ristretto255Sha512 {
|
|||
fn HDKG(m: &[u8]) -> Option<<<Self::Group as Group>::Field as Field>::Scalar> {
|
||||
Some(hash_to_scalar(&[CONTEXT_STRING.as_bytes(), b"dkg", m]))
|
||||
}
|
||||
|
||||
/// HID for FROST(ristretto255, SHA-512)
|
||||
fn HID(m: &[u8]) -> Option<<<Self::Group as Group>::Field as Field>::Scalar> {
|
||||
Some(hash_to_scalar(&[CONTEXT_STRING.as_bytes(), b"id", m]))
|
||||
}
|
||||
}
|
||||
|
||||
type R = Ristretto255Sha512;
|
||||
|
|
|
@ -53,3 +53,8 @@ fn check_sign_with_test_vectors() {
|
|||
fn check_error_culprit() {
|
||||
frost_core::tests::ciphersuite_generic::check_error_culprit::<Ristretto255Sha512>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_identifier_derivation() {
|
||||
frost_core::tests::ciphersuite_generic::check_identifier_derivation::<Ristretto255Sha512>();
|
||||
}
|
||||
|
|
|
@ -226,6 +226,14 @@ impl Ciphersuite for Secp256K1Sha256 {
|
|||
m,
|
||||
))
|
||||
}
|
||||
|
||||
/// HID for FROST(secp256k1, SHA-256)
|
||||
fn HID(m: &[u8]) -> Option<<<Self::Group as Group>::Field as Field>::Scalar> {
|
||||
Some(hash_to_scalar(
|
||||
(CONTEXT_STRING.to_owned() + "id").as_bytes(),
|
||||
m,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
type S = Secp256K1Sha256;
|
||||
|
|
Loading…
Reference in New Issue