#![forbid(unsafe_code)]
use curve25519_dalek::{
constants::ED25519_BASEPOINT_POINT, edwards::CompressedEdwardsY, traits::IsIdentity,
EdwardsPoint, Scalar,
};
use oxicrypto_core::{CryptoError, Vec};
use sha2::{Digest, Sha512};
pub mod aggregate;
pub mod keygen;
pub mod round1;
pub mod round2;
#[cfg(test)]
mod tests;
pub use aggregate::{aggregate, verify_signature, Signature};
pub use keygen::{
trusted_dealer_keygen, trusted_dealer_keygen_with_coefficients, KeyPackage, PublicKeyPackage,
SecretShare,
};
pub use round1::{commit, SigningCommitments, SigningNonces};
pub use round2::{sign, verify_signature_share, SignatureShare};
pub const CONTEXT_STRING: &[u8] = b"FROST-ED25519-SHA512-v1";
pub const SCALAR_LEN: usize = 32;
pub const ELEMENT_LEN: usize = 32;
pub const SIGNATURE_LEN: usize = ELEMENT_LEN + SCALAR_LEN;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Identifier(Scalar);
impl Identifier {
#[must_use = "result must be checked"]
pub fn new(index: u16) -> Result<Self, CryptoError> {
if index == 0 {
return Err(CryptoError::BadInput);
}
Ok(Self(Scalar::from(u64::from(index))))
}
#[must_use = "result must be checked"]
pub fn from_scalar(scalar: Scalar) -> Result<Self, CryptoError> {
if scalar == Scalar::ZERO {
return Err(CryptoError::BadInput);
}
Ok(Self(scalar))
}
#[must_use = "result must be checked"]
pub fn from_bytes(bytes: &[u8]) -> Result<Self, CryptoError> {
let scalar = deserialize_scalar(bytes)?;
Self::from_scalar(scalar)
}
#[must_use]
pub fn as_scalar(&self) -> Scalar {
self.0
}
#[must_use]
pub fn to_bytes(&self) -> [u8; SCALAR_LEN] {
self.0.to_bytes()
}
}
#[must_use]
pub(crate) fn scalar_base_mult(s: &Scalar) -> EdwardsPoint {
s * ED25519_BASEPOINT_POINT
}
#[must_use = "result must be checked"]
pub(crate) fn serialize_element(point: &EdwardsPoint) -> Result<[u8; ELEMENT_LEN], CryptoError> {
if point.is_identity() {
return Err(CryptoError::InvalidKey);
}
Ok(point.compress().to_bytes())
}
#[must_use = "result must be checked"]
pub(crate) fn deserialize_element(buf: &[u8]) -> Result<EdwardsPoint, CryptoError> {
if buf.len() != ELEMENT_LEN {
return Err(CryptoError::InvalidKey);
}
let mut bytes = [0u8; ELEMENT_LEN];
bytes.copy_from_slice(buf);
let point = CompressedEdwardsY(bytes)
.decompress()
.ok_or(CryptoError::InvalidKey)?;
if point.is_identity() || !point.is_torsion_free() {
return Err(CryptoError::InvalidKey);
}
Ok(point)
}
#[must_use]
pub(crate) fn serialize_scalar(s: &Scalar) -> [u8; SCALAR_LEN] {
s.to_bytes()
}
#[must_use = "result must be checked"]
pub(crate) fn deserialize_scalar(buf: &[u8]) -> Result<Scalar, CryptoError> {
if buf.len() != SCALAR_LEN {
return Err(CryptoError::InvalidKey);
}
let mut bytes = [0u8; SCALAR_LEN];
bytes.copy_from_slice(buf);
Option::<Scalar>::from(Scalar::from_canonical_bytes(bytes)).ok_or(CryptoError::InvalidKey)
}
#[must_use]
fn reduce_wide(digest: [u8; 64]) -> Scalar {
Scalar::from_bytes_mod_order_wide(&digest)
}
#[must_use]
fn sha512_concat(parts: &[&[u8]]) -> [u8; 64] {
let mut hasher = Sha512::new();
for part in parts {
hasher.update(part);
}
let digest = hasher.finalize();
let mut out = [0u8; 64];
out.copy_from_slice(&digest);
out
}
#[must_use]
pub(crate) fn h1(m: &[u8]) -> Scalar {
reduce_wide(sha512_concat(&[CONTEXT_STRING, b"rho", m]))
}
#[must_use]
pub(crate) fn h2(m: &[u8]) -> Scalar {
reduce_wide(sha512_concat(&[m]))
}
#[must_use]
pub(crate) fn h3(m: &[u8]) -> Scalar {
reduce_wide(sha512_concat(&[CONTEXT_STRING, b"nonce", m]))
}
#[must_use]
pub(crate) fn h4(m: &[u8]) -> [u8; 64] {
sha512_concat(&[CONTEXT_STRING, b"msg", m])
}
#[must_use]
pub(crate) fn h5(m: &[u8]) -> [u8; 64] {
sha512_concat(&[CONTEXT_STRING, b"com", m])
}
#[must_use = "result must be checked"]
pub(crate) fn encode_group_commitment_list(
commitment_list: &[SigningCommitments],
) -> Result<Vec<u8>, CryptoError> {
let mut encoded = Vec::with_capacity(commitment_list.len() * (SCALAR_LEN + 2 * ELEMENT_LEN));
for commitment in commitment_list {
encoded.extend_from_slice(&commitment.identifier().to_bytes());
encoded.extend_from_slice(&serialize_element(&commitment.hiding())?);
encoded.extend_from_slice(&serialize_element(&commitment.binding())?);
}
Ok(encoded)
}
#[derive(Clone, Copy, Debug)]
pub(crate) struct BindingFactor {
pub(crate) identifier: Identifier,
pub(crate) factor: Scalar,
}
#[must_use = "result must be checked"]
pub(crate) fn compute_binding_factors(
group_public_key: &EdwardsPoint,
commitment_list: &[SigningCommitments],
msg: &[u8],
) -> Result<Vec<BindingFactor>, CryptoError> {
let group_public_key_enc = serialize_element(group_public_key)?;
let msg_hash = h4(msg);
let encoded_commitment_hash = h5(&encode_group_commitment_list(commitment_list)?);
let mut rho_input_prefix =
Vec::with_capacity(ELEMENT_LEN + msg_hash.len() + encoded_commitment_hash.len());
rho_input_prefix.extend_from_slice(&group_public_key_enc);
rho_input_prefix.extend_from_slice(&msg_hash);
rho_input_prefix.extend_from_slice(&encoded_commitment_hash);
let mut binding_factor_list = Vec::with_capacity(commitment_list.len());
for commitment in commitment_list {
let mut rho_input = rho_input_prefix.clone();
rho_input.extend_from_slice(&commitment.identifier().to_bytes());
binding_factor_list.push(BindingFactor {
identifier: commitment.identifier(),
factor: h1(&rho_input),
});
}
Ok(binding_factor_list)
}
#[must_use = "result must be checked"]
pub(crate) fn binding_factor_for_participant(
binding_factor_list: &[BindingFactor],
identifier: Identifier,
) -> Result<Scalar, CryptoError> {
binding_factor_list
.iter()
.find(|bf| bf.identifier == identifier)
.map(|bf| bf.factor)
.ok_or(CryptoError::BadInput)
}
#[must_use = "result must be checked"]
pub(crate) fn compute_group_commitment(
commitment_list: &[SigningCommitments],
binding_factor_list: &[BindingFactor],
) -> Result<EdwardsPoint, CryptoError> {
let mut group_commitment = EdwardsPoint::default();
for commitment in commitment_list {
let binding_factor =
binding_factor_for_participant(binding_factor_list, commitment.identifier())?;
let binding = commitment.binding() * binding_factor;
group_commitment += commitment.hiding() + binding;
}
Ok(group_commitment)
}
#[must_use = "result must be checked"]
pub(crate) fn compute_challenge(
group_commitment: &EdwardsPoint,
group_public_key: &EdwardsPoint,
msg: &[u8],
) -> Result<Scalar, CryptoError> {
let group_comm_enc = serialize_element(group_commitment)?;
let group_public_key_enc = serialize_element(group_public_key)?;
let mut challenge_input =
Vec::with_capacity(group_comm_enc.len() + group_public_key_enc.len() + msg.len());
challenge_input.extend_from_slice(&group_comm_enc);
challenge_input.extend_from_slice(&group_public_key_enc);
challenge_input.extend_from_slice(msg);
Ok(h2(&challenge_input))
}
#[must_use = "result must be checked"]
pub(crate) fn derive_interpolating_value(
participants: &[Identifier],
x_i: Identifier,
) -> Result<Scalar, CryptoError> {
if !participants.contains(&x_i) {
return Err(CryptoError::BadInput);
}
for (idx, p) in participants.iter().enumerate() {
if participants[idx + 1..].contains(p) {
return Err(CryptoError::BadInput);
}
}
let mut numerator = Scalar::ONE;
let mut denominator = Scalar::ONE;
let xi_scalar = x_i.as_scalar();
for x_j in participants {
if *x_j == x_i {
continue;
}
let xj_scalar = x_j.as_scalar();
numerator *= xj_scalar;
denominator *= xj_scalar - xi_scalar;
}
if denominator == Scalar::ZERO {
return Err(CryptoError::BadInput);
}
Ok(numerator * denominator.invert())
}
#[must_use = "result must be checked"]
pub fn sort_commitments(
commitments: &[SigningCommitments],
) -> Result<Vec<SigningCommitments>, CryptoError> {
let mut sorted = commitments.to_vec();
sorted.sort_by_key(|c| c.identifier().to_bytes());
for window in sorted.windows(2) {
if window[0].identifier() == window[1].identifier() {
return Err(CryptoError::BadInput);
}
}
Ok(sorted)
}