add support for deriving identifiers from arbitary strings (#418)

This commit is contained in:
Conrado Gouvea 2023-06-30 12:45:46 -03:00 committed by GitHub
parent bed23c3e1d
commit 78b5c44de0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 140 additions and 35 deletions

View File

@ -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_

View File

@ -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,
}
}
}

View File

@ -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)
}
}

View File

@ -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()`]).
///

View File

@ -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);
}

View File

@ -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;

View File

@ -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>();
}

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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

View File

@ -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;

View File

@ -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>();
}

View File

@ -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;