use super::{Ciphersuite, Scalar, encode_scalar, scalar_from_be_mod_l};
use std::collections::HashMap;
#[derive(Clone)]
pub struct NonceCommitment<C: Ciphersuite> {
pub identifier: Vec<u8>,
pub hiding: C::Point,
pub binding: C::Point,
}
fn cmp_be(a: &[u8], b: &[u8]) -> std::cmp::Ordering {
let a = strip(a);
let b = strip(b);
a.len().cmp(&b.len()).then_with(|| a.cmp(b))
}
fn strip(b: &[u8]) -> &[u8] {
let start = b.iter().position(|&x| x != 0).unwrap_or(b.len());
&b[start..]
}
fn id_key(id: &[u8]) -> Vec<u8> {
strip(id).to_vec()
}
pub fn encode_commitment_list<C: Ciphersuite>(commitments: &[NonceCommitment<C>]) -> Vec<u8> {
let mut sorted: Vec<&NonceCommitment<C>> = commitments.iter().collect();
sorted.sort_by(|a, b| cmp_be(&a.identifier, &b.identifier));
let mut buf = Vec::new();
for c in sorted {
buf.extend_from_slice(&encode_scalar(&scalar_from_be_mod_l(&c.identifier)));
buf.extend_from_slice(&C::encode_point(&c.hiding));
buf.extend_from_slice(&C::encode_point(&c.binding));
}
buf
}
pub fn compute_binding_factors<C: Ciphersuite>(
msg: &[u8],
commitments: &[NonceCommitment<C>],
) -> HashMap<Vec<u8>, Scalar> {
let encoded_msg = C::h4(msg);
let encoded_commitments = encode_commitment_list(commitments);
let encoded_commitment_hash = C::h5(&encoded_commitments);
let mut prefix = Vec::with_capacity(encoded_msg.len() + encoded_commitment_hash.len());
prefix.extend_from_slice(&encoded_msg);
prefix.extend_from_slice(&encoded_commitment_hash);
let mut out = HashMap::with_capacity(commitments.len());
for c in commitments {
let mut input = prefix.clone();
input.extend_from_slice(&encode_scalar(&scalar_from_be_mod_l(&c.identifier)));
out.insert(id_key(&c.identifier), C::h1(&input));
}
out
}
pub fn compute_group_commitment<C: Ciphersuite>(
commitments: &[NonceCommitment<C>],
binding_factors: &HashMap<Vec<u8>, Scalar>,
) -> Option<C::Point> {
let mut r: Option<C::Point> = None;
for c in commitments {
let rho = binding_factors.get(&id_key(&c.identifier))?;
let term = C::add(&c.hiding, &C::scalar_mul(&c.binding, rho));
r = Some(match r {
None => term,
Some(acc) => C::add(&acc, &term),
});
}
r
}
pub fn compute_group_challenge<C: Ciphersuite>(
r: &C::Point,
group_public_key: &C::Point,
msg: &[u8],
) -> Scalar {
let mut input = Vec::new();
input.extend_from_slice(&C::encode_point(r));
input.extend_from_slice(&C::encode_point(group_public_key));
input.extend_from_slice(msg);
C::h2(&input)
}
pub fn lagrange_coefficient<C: Ciphersuite>(id: &[u8], signers: &[Vec<u8>]) -> Option<Scalar> {
let id_s = scalar_from_be_mod_l(id);
let mut lambda = Scalar::ONE;
for xj in signers {
let xj_s = scalar_from_be_mod_l(xj);
if bool::from(xj_s.ct_eq(&id_s)) {
continue;
}
let den = xj_s.sub(&id_s);
let den_inv = den.invert();
if bool::from(den_inv.ct_eq(&Scalar::ZERO)) {
return None;
}
lambda = lambda.mul(&xj_s.mul(&den_inv));
}
Some(lambda)
}
pub fn nonce_generate_labeled<C: Ciphersuite>(
random_bytes: &[u8; 32],
secret: &Scalar,
label: &[u8],
) -> Scalar {
let mut input = Vec::with_capacity(64 + label.len());
input.extend_from_slice(random_bytes);
input.extend_from_slice(&encode_scalar(secret));
input.extend_from_slice(label);
C::h3(&input)
}
pub fn nonce_generate<C: Ciphersuite>(random_bytes: &[u8; 32], secret: &Scalar) -> Scalar {
let mut input = Vec::with_capacity(64);
input.extend_from_slice(random_bytes);
input.extend_from_slice(&encode_scalar(secret));
C::h3(&input)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::frost::Ed25519;
fn scalar_small(n: u8) -> Scalar {
let mut b = [0u8; 32];
b[0] = n;
Scalar::from_bytes_canonical(&b).unwrap()
}
#[test]
fn lagrange_single_signer_is_one() {
let id = vec![7u8];
let lam = lagrange_coefficient::<Ed25519>(&id, std::slice::from_ref(&id)).unwrap();
assert!(bool::from(lam.ct_eq(&Scalar::ONE)));
}
#[test]
fn lagrange_two_signers_matches_formula() {
let signers = vec![vec![3u8], vec![5u8]];
let lam3 = lagrange_coefficient::<Ed25519>(&[3], &signers).unwrap();
let want3 = scalar_small(5).mul(&scalar_small(2).invert());
assert!(bool::from(lam3.ct_eq(&want3)));
let lam5 = lagrange_coefficient::<Ed25519>(&[5], &signers).unwrap();
let want5 = scalar_small(3).mul(&scalar_small(2).negate().invert());
assert!(bool::from(lam5.ct_eq(&want5)));
}
#[test]
fn lagrange_reconstructs_constant_term() {
let a0 = scalar_small(11);
let a1 = scalar_small(4);
let f = |x: u8| a0.add(&a1.mul(&scalar_small(x)));
let signers = vec![vec![3u8], vec![5u8]];
let l3 = lagrange_coefficient::<Ed25519>(&[3], &signers).unwrap();
let l5 = lagrange_coefficient::<Ed25519>(&[5], &signers).unwrap();
let recon = l3.mul(&f(3)).add(&l5.mul(&f(5)));
assert!(bool::from(recon.ct_eq(&a0)));
}
#[test]
fn binding_factors_are_deterministic_and_per_signer() {
let commitments = vec![
NonceCommitment::<Ed25519> {
identifier: vec![1],
hiding: Ed25519::generator(),
binding: Ed25519::generator(),
},
NonceCommitment::<Ed25519> {
identifier: vec![2],
hiding: Ed25519::generator(),
binding: Ed25519::generator(),
},
];
let msg = b"hello";
let bf1 = compute_binding_factors::<Ed25519>(msg, &commitments);
let bf2 = compute_binding_factors::<Ed25519>(msg, &commitments);
assert!(bool::from(bf1[&vec![1u8]].ct_eq(&bf2[&vec![1u8]])));
assert!(!bool::from(bf1[&vec![1u8]].ct_eq(&bf1[&vec![2u8]])));
}
#[test]
fn group_commitment_sums_terms() {
let commitments = vec![NonceCommitment::<Ed25519> {
identifier: vec![1],
hiding: Ed25519::generator(),
binding: Ed25519::generator(),
}];
let bf = compute_binding_factors::<Ed25519>(b"m", &commitments);
let r = compute_group_commitment::<Ed25519>(&commitments, &bf).unwrap();
let rho = &bf[&vec![1u8]];
let want = Ed25519::mul_base(&Scalar::ONE.add(rho));
assert!(Ed25519::eq(&r, &want));
}
}