use alloc::vec::Vec;
pub use lib_q_lattice_zkp::DualRingOpeningProof;
use lib_q_lattice_zkp::{
AjtaiCommitment,
AjtaiCommitmentKey,
AjtaiOpening,
ProofError,
VerifyError,
prove_dual_ring_opening,
verify_dual_ring_opening,
};
use lib_q_sha3::{
ExtendableOutput,
Update,
XofReader,
};
use rand_core::{
CryptoRng,
Rng,
};
use crate::ring::federation_digest;
use crate::sign::federation_signing_context;
const DUALRING_LB_CTX_TAG: &[u8] = b"lib-q-ring-sig/dualring-lb-v1";
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct DualRingLbChallengeState {
pub federation_digest: [u8; 32],
pub per_member_challenge_digest: Vec<[u8; 32]>,
}
#[must_use]
pub fn dualring_lb_challenge_state(
ring: &[AjtaiCommitment],
message: &[u8],
) -> DualRingLbChallengeState {
let federation_digest = federation_digest(ring);
let mut per_member_challenge_digest = Vec::with_capacity(ring.len());
for i in 0..ring.len() {
let mut h = lib_q_sha3::Shake256::default();
h.update(b"lib-q-ring-sig/dualring-lb-challenge");
h.update(&federation_digest);
h.update(&(i as u64).to_le_bytes());
h.update(&(message.len() as u64).to_le_bytes());
h.update(message);
let mut s = [0u8; 32];
let mut r = h.finalize_xof();
XofReader::read(&mut r, &mut s);
per_member_challenge_digest.push(s);
}
DualRingLbChallengeState {
federation_digest,
per_member_challenge_digest,
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct DualRingLbSignature {
pub proof: DualRingOpeningProof,
}
impl From<DualRingOpeningProof> for DualRingLbSignature {
fn from(proof: DualRingOpeningProof) -> Self {
Self { proof }
}
}
impl From<DualRingLbSignature> for DualRingOpeningProof {
fn from(sig: DualRingLbSignature) -> Self {
sig.proof
}
}
#[must_use]
pub fn dualring_lb_signing_context(ring: &[AjtaiCommitment], message: &[u8]) -> Vec<u8> {
let st = dualring_lb_challenge_state(ring, message);
let mut v = federation_signing_context(ring, message);
v.push(2);
v.extend_from_slice(DUALRING_LB_CTX_TAG);
for s in &st.per_member_challenge_digest {
v.extend_from_slice(s);
}
v
}
#[allow(clippy::too_many_arguments)]
pub fn sign_dualring_lb<R: Rng + CryptoRng>(
rng: &mut R,
crs: &AjtaiCommitmentKey,
member_opening: &AjtaiOpening,
member_com: &AjtaiCommitment,
ring: &[AjtaiCommitment],
message: &[u8],
tau: usize,
z_inf_bound: i32,
max_attempts: usize,
) -> Result<DualRingLbSignature, ProofError> {
let signer_idx = ring
.iter()
.position(|c| c == member_com)
.ok_or(ProofError::InvalidParameters)?;
let ctx = dualring_lb_signing_context(ring, message);
let proof = prove_dual_ring_opening(
rng,
crs,
member_opening,
ring,
signer_idx,
&ctx,
tau,
z_inf_bound,
max_attempts,
)?;
Ok(DualRingLbSignature { proof })
}
#[allow(clippy::too_many_arguments)]
pub fn verify_dualring_lb(
crs: &AjtaiCommitmentKey,
ring: &[AjtaiCommitment],
message: &[u8],
sig: &DualRingLbSignature,
tau: usize,
z_inf_bound: i32,
) -> Result<(), VerifyError> {
let ctx = dualring_lb_signing_context(ring, message);
verify_dual_ring_opening(crs, ring, &sig.proof, &ctx, tau, z_inf_bound)
}
#[cfg(test)]
mod tests {
use alloc::vec;
use lib_q_lattice_zkp::{
AjtaiCommitmentKey,
AjtaiOpening,
AjtaiParameters,
};
use lib_q_ring::{
ModuleVec,
Poly,
};
use rand_chacha::ChaCha8Rng;
use rand_core::SeedableRng;
use super::*;
#[inline]
fn test_deterministic_seed32(tag: u64) -> [u8; 32] {
let mut s = [0u8; 32];
s[0..8].copy_from_slice(&tag.to_le_bytes());
s
}
fn pilot_crs() -> AjtaiCommitmentKey {
AjtaiCommitmentKey {
seed: [0x5Du8; 32],
params: AjtaiParameters::new(2, 1),
}
}
#[test]
fn dualring_lb_challenge_state_matches_signing_context_absorption() {
let key = pilot_crs();
let o0 = AjtaiOpening {
message: ModuleVec(vec![Poly::zero(), Poly::zero()]),
randomness: ModuleVec(vec![Poly::zero()]),
};
let c0 = lib_q_lattice_zkp::commit(&key, &o0);
let msg = b"ctx-bind";
let st = dualring_lb_challenge_state(core::slice::from_ref(&c0), msg);
assert_eq!(st.per_member_challenge_digest.len(), 1);
let full = dualring_lb_signing_context(core::slice::from_ref(&c0), msg);
assert!(
full.windows(32)
.any(|w| w == st.per_member_challenge_digest[0]),
"signing context must contain the per-member digest"
);
}
#[test]
fn dualring_lb_ring_of_one_matches_opening_proof() {
let key = pilot_crs();
let tau = 39;
let z = 20_000_000;
let max = 512;
let o = AjtaiOpening {
message: ModuleVec(vec![Poly::zero(), Poly::zero()]),
randomness: ModuleVec(vec![Poly::zero()]),
};
let com = lib_q_lattice_zkp::commit(&key, &o);
let ring = [com.clone()];
let msg = b"singleton";
let mut rng = ChaCha8Rng::from_seed(test_deterministic_seed32(0x51A1_u64));
let sig =
sign_dualring_lb(&mut rng, &key, &o, &com, &ring, msg, tau, z, max).expect("sign");
verify_dualring_lb(&key, &ring, msg, &sig, tau, z).expect("verify ring of 1");
}
#[test]
fn dualring_lb_wrong_message_rejected() {
let key = pilot_crs();
let tau = 39;
let z = 20_000_000;
let max = 512;
let mut o = AjtaiOpening {
message: ModuleVec(vec![Poly::zero(), Poly::zero()]),
randomness: ModuleVec(vec![Poly::zero()]),
};
o.randomness.0[0].coeffs[0] = 1;
let com = lib_q_lattice_zkp::commit(&key, &o);
let ring = [com.clone()];
let msg = b"signed-once";
let mut rng = ChaCha8Rng::from_seed(test_deterministic_seed32(0xC0FFEE_u64));
let sig =
sign_dualring_lb(&mut rng, &key, &o, &com, &ring, msg, tau, z, max).expect("sign");
verify_dualring_lb(&key, &ring, msg, &sig, tau, z).expect("verify");
assert!(
verify_dualring_lb(&key, &ring, b"other-msg", &sig, tau, z).is_err(),
"verify must reject wrong message when commitment is non-zero"
);
}
#[test]
fn dualring_lb_wrong_ring_member_rejected() {
let key = pilot_crs();
let tau = 39;
let z = 20_000_000;
let max = 512;
let o_a = AjtaiOpening {
message: ModuleVec(vec![Poly::zero(), Poly::zero()]),
randomness: ModuleVec(vec![Poly::zero()]),
};
let mut o_b = AjtaiOpening {
message: ModuleVec(vec![Poly::zero(), Poly::zero()]),
randomness: ModuleVec(vec![Poly::zero()]),
};
o_b.message.0[0].coeffs[0] = 3;
let com_a = lib_q_lattice_zkp::commit(&key, &o_a);
let com_b = lib_q_lattice_zkp::commit(&key, &o_b);
let ring = [com_a.clone(), com_b.clone()];
let msg = b"fed-dual";
let mut rng = ChaCha8Rng::from_seed(test_deterministic_seed32(0xD06_u64));
let sig = sign_dualring_lb(&mut rng, &key, &o_b, &com_b, &ring, msg, tau, z, max)
.expect("sign b");
let wrong_ring = [com_a.clone(), com_a];
assert!(
verify_dualring_lb(&key, &wrong_ring, msg, &sig, tau, z).is_err(),
"proof for member B must not verify as member A"
);
}
}