use crypto_bigint::{
CtEq,
U256,
};
use lib_q_prf::{
GoldKey256,
GoldPrfParams256,
LegendreKey256,
LegendrePrfParams256,
PrfError,
gold_prf_u256,
legendre_prf_u256,
u256_from_le_bytes,
};
use lib_q_sha3::{
ExtendableOutput,
Update,
XofReader,
};
use rand_core::{
CryptoRng,
Rng,
};
use subtle::Choice;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum DualringPrfError {
Prf(PrfError),
InvalidInput,
Rejected,
}
impl From<PrfError> for DualringPrfError {
fn from(e: PrfError) -> Self {
DualringPrfError::Prf(e)
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct DualringPrfMemberSecrets256 {
pub legendre_key_le: [u8; 32],
pub gold_key_le: [u8; 32],
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct DualringPrfSignature256 {
pub commitment: [u8; 32],
pub challenge: [u8; 32],
pub legendre_out: i8,
pub gold_out: [u8; 32],
}
pub type DualringPrfBatchItem256 = (Vec<u8>, usize, DualringPrfSignature256);
const FS_LABEL: &[u8] = b"lib-q-ring-sig/dualring-prf-v1";
const RING_LABEL: &[u8] = b"lib-q-ring-sig/dualring-prf-ring-v1";
#[must_use]
pub fn dualring_prf_ring_digest(members: &[DualringPrfMemberSecrets256]) -> [u8; 32] {
let mut h = lib_q_sha3::Shake256::default();
h.update(RING_LABEL);
h.update(&(members.len() as u64).to_le_bytes());
for m in members {
h.update(&m.legendre_key_le);
h.update(&m.gold_key_le);
}
let mut out = [0u8; 32];
let mut r = h.finalize_xof();
XofReader::read(&mut r, &mut out);
out
}
fn fs_challenge(ring_digest: &[u8; 32], message: &[u8], commitment: &[u8; 32]) -> [u8; 32] {
let mut h = lib_q_sha3::Shake256::default();
h.update(FS_LABEL);
h.update(ring_digest);
h.update(&(message.len() as u64).to_le_bytes());
h.update(message);
h.update(commitment);
let mut out = [0u8; 32];
let mut r = h.finalize_xof();
XofReader::read(&mut r, &mut out);
out
}
fn challenge_to_field_x(challenge: &[u8; 32], leg_params: &LegendrePrfParams256) -> U256 {
let x = U256::from_le_slice(challenge.as_slice());
x.rem_vartime(&leg_params.p)
}
#[must_use = "signature may be invalid if the Result is not checked"]
#[allow(clippy::too_many_lines)]
pub fn dualring_prf_sign_u256<R: Rng + CryptoRng>(
rng: &mut R,
ring: &[DualringPrfMemberSecrets256],
signer_index: usize,
leg_key: &LegendreKey256,
gold_key: &GoldKey256,
message: &[u8],
) -> Result<DualringPrfSignature256, DualringPrfError> {
let member = ring
.get(signer_index)
.ok_or(DualringPrfError::InvalidInput)?;
let leg_params = LegendrePrfParams256::pilot();
let gold_params = GoldPrfParams256::pilot();
let pk_leg =
LegendreKey256::from_uint(u256_from_le_bytes(&member.legendre_key_le), &leg_params)
.map_err(|_| DualringPrfError::InvalidInput)?;
let pk_gold = GoldKey256::from_uint(u256_from_le_bytes(&member.gold_key_le), &gold_params)
.map_err(|_| DualringPrfError::InvalidInput)?;
if !bool::from(leg_key.as_uint().ct_eq(pk_leg.as_uint())) ||
!bool::from(gold_key.as_uint().ct_eq(pk_gold.as_uint()))
{
return Err(DualringPrfError::InvalidInput);
}
let mut commitment = [0u8; 32];
rng.fill_bytes(&mut commitment);
let ring_digest = dualring_prf_ring_digest(ring);
let challenge = fs_challenge(&ring_digest, message, &commitment);
let x = challenge_to_field_x(&challenge, &leg_params);
let legendre_out = legendre_prf_u256(leg_key, &x, &leg_params)?;
let gold_out = gold_prf_u256(gold_key, &x, &gold_params)?;
Ok(DualringPrfSignature256 {
commitment,
challenge,
legendre_out,
gold_out,
})
}
#[must_use = "verification outcome must be checked"]
pub fn dualring_prf_verify_u256(
ring: &[DualringPrfMemberSecrets256],
signer_index: usize,
message: &[u8],
sig: &DualringPrfSignature256,
) -> Result<(), DualringPrfError> {
let member = ring
.get(signer_index)
.ok_or(DualringPrfError::InvalidInput)?;
let leg_params = LegendrePrfParams256::pilot();
let gold_params = GoldPrfParams256::pilot();
let leg_k = LegendreKey256::from_uint(u256_from_le_bytes(&member.legendre_key_le), &leg_params)
.map_err(|_| DualringPrfError::InvalidInput)?;
let gold_k = GoldKey256::from_uint(u256_from_le_bytes(&member.gold_key_le), &gold_params)
.map_err(|_| DualringPrfError::InvalidInput)?;
let ring_digest = dualring_prf_ring_digest(ring);
let expected_chal = fs_challenge(&ring_digest, message, &sig.commitment);
if expected_chal != sig.challenge {
return Err(DualringPrfError::Rejected);
}
let x = challenge_to_field_x(&sig.challenge, &leg_params);
let leg = legendre_prf_u256(&leg_k, &x, &leg_params)?;
let gld = gold_prf_u256(&gold_k, &x, &gold_params)?;
if leg != sig.legendre_out || gld != sig.gold_out {
return Err(DualringPrfError::Rejected);
}
Ok(())
}
#[must_use = "batch verification outcome must be checked"]
pub fn verify_dualring_prf_batch_u256(
ring: &[DualringPrfMemberSecrets256],
items: &[DualringPrfBatchItem256],
) -> Result<(), DualringPrfError> {
let mut all_ok = Choice::from(1u8);
for (msg, idx, sig) in items {
let item_ok = match dualring_prf_verify_u256(ring, *idx, msg, sig) {
Ok(()) => Choice::from(1u8),
Err(_) => Choice::from(0u8),
};
all_ok &= item_ok;
}
if bool::from(all_ok) {
Ok(())
} else {
Err(DualringPrfError::Rejected)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ring_digest_order_sensitive() {
let a = DualringPrfMemberSecrets256 {
legendre_key_le: [1u8; 32],
gold_key_le: [2u8; 32],
};
let b = DualringPrfMemberSecrets256 {
legendre_key_le: [3u8; 32],
gold_key_le: [4u8; 32],
};
assert_ne!(
dualring_prf_ring_digest(&[a.clone(), b.clone()]),
dualring_prf_ring_digest(&[b, a]),
);
}
#[test]
fn ring_digest_len_encoded() {
let m = DualringPrfMemberSecrets256 {
legendre_key_le: [0u8; 32],
gold_key_le: [0u8; 32],
};
assert_ne!(
dualring_prf_ring_digest(core::slice::from_ref(&m)),
dualring_prf_ring_digest(&[m.clone(), m]),
);
}
}