mod banderwagon;
use crate::{
bls12381::primitives::group::{Scalar, ScalarReadCfg, G1},
transcript::{Summary, Transcript},
zk::{
bulletproofs::circuit::{self, prove, verify},
pedersen_to_plain,
},
Secret,
};
use banderwagon::{vrf_batch_checked, vrf_batch_checked_circuit, vrf_recv, F, G};
use bytes::{Buf, BufMut, Bytes};
use commonware_codec::{
Encode, EncodeFixed, EncodeSize, Error as CodecError, FixedArray, FixedSize, Read, ReadExt,
Write,
};
use commonware_formatting::hex;
use commonware_math::algebra::{Additive as _, CryptoGroup, Random};
use commonware_parallel::Strategy;
use commonware_utils::{
ordered::{Map, Set},
Array, Span, TryCollect, TryFromIterator,
};
use core::{
fmt::{Debug, Display},
hash::{Hash, Hasher},
ops::Deref,
};
use rand_core::CryptoRng;
use std::num::NonZeroU32;
use zeroize::Zeroizing;
const SCHNORR_NS: &[u8] = b"_COMMONWARE_CRYPTOGRAPHY_BANDERSNATCH_SCHNORR";
const BULLETPROOFS_DST: &[u8] = b"_COMMONWARE_CRYPTOGRAPHY_GOLDEN_DKG_BULLETPROOFS";
const WIRES_PER_PLAYER: usize = 2247;
const WIRES_BASE: usize = 1327;
const fn lg_len_for_players(num_players: u32) -> u8 {
let internal = WIRES_PER_PLAYER * (num_players as usize) + WIRES_BASE;
let mut padded: usize = 1;
let mut lg: u8 = 0;
while padded < internal {
padded <<= 1;
lg += 1;
}
lg
}
pub struct Setup {
inner: circuit::Setup<G1>,
max_players: NonZeroU32,
}
impl Setup {
pub fn new(max_players: NonZeroU32) -> Self {
let lg_len = lg_len_for_players(max_players.get());
let inner = circuit::Setup::hashed(BULLETPROOFS_DST, lg_len, G1::generator());
Self { inner, max_players }
}
#[must_use]
pub const fn supports(&self, num_players: u32) -> bool {
num_players <= self.max_players.get()
}
pub(super) const fn max_players(&self) -> NonZeroU32 {
self.max_players
}
pub(super) const fn inner(&self) -> &circuit::Setup<G1> {
&self.inner
}
}
impl Write for Setup {
fn write(&self, buf: &mut impl BufMut) {
self.max_players.get().write(buf);
self.inner.write(buf);
}
}
impl EncodeSize for Setup {
fn encode_size(&self) -> usize {
self.max_players.get().encode_size() + self.inner.encode_size()
}
}
impl Read for Setup {
type Cfg = NonZeroU32;
fn read_cfg(buf: &mut impl Buf, expected_max_players: &Self::Cfg) -> Result<Self, CodecError> {
let max_players_raw = u32::read(buf)?;
let max_players = NonZeroU32::new(max_players_raw)
.ok_or(CodecError::Invalid("Setup", "max_players must be nonzero"))?;
if max_players != *expected_max_players {
return Err(CodecError::Invalid("Setup", "max_players mismatch"));
}
let lg_len = lg_len_for_players(max_players.get());
let max_len = 1usize << lg_len;
let inner = circuit::Setup::<G1>::read_cfg(buf, &(max_len, ()))?;
if !inner.supports(lg_len) {
return Err(CodecError::Invalid("Setup", "inner setup too small"));
}
Ok(Self { inner, max_players })
}
}
#[derive(Clone, Debug)]
pub struct PrivateKey {
inner: Secret<F>,
}
impl Random for PrivateKey {
fn random(rng: impl CryptoRng) -> Self {
Self {
inner: Secret::new(F::random(rng)),
}
}
}
impl crate::Signer for PrivateKey {
type Signature = Signature;
type PublicKey = PublicKey;
fn public_key(&self) -> Self::PublicKey {
self.inner
.expose(|x| PublicKey::from_point(G::generator() * x))
}
fn sign(&self, namespace: &[u8], msg: &[u8]) -> Signature {
let pk = self.public();
let mut t = Transcript::new(SCHNORR_NS);
t.commit(namespace).commit(msg).commit(pk.raw.as_slice());
let k = self.inner.expose(|x| {
let mut nonce_t = t.fork(b"nonce");
let x_bytes = Zeroizing::new(x.encode_fixed::<{ F::SIZE }>());
nonce_t.commit(x_bytes.as_slice());
F::random(nonce_t.noise(b"k"))
});
let k_big = G::generator() * &k;
let k_big_bytes: [u8; G::SIZE] = k_big.encode_fixed();
t.commit(k_big_bytes.as_slice());
let e = F::random(t.noise(b"challenge"));
let s = self.inner.expose(|x| e * x + &k);
let mut raw = [0u8; Signature::SIZE];
raw[..G::SIZE].copy_from_slice(&k_big_bytes);
raw[G::SIZE..].copy_from_slice(&s.encode_fixed::<{ F::SIZE }>());
Signature { raw }
}
}
impl PrivateKey {
pub fn public(&self) -> PublicKey {
crate::Signer::public_key(self)
}
pub(super) fn vrf_recv(&self, msg: &Summary, sender: &PublicKey) -> Scalar {
self.inner
.expose(|inner| vrf_recv(msg, &sender.point, inner))
}
pub(super) fn vrf_batch_checked(
&self,
rng: &mut impl CryptoRng,
setup: &Setup,
transcript: &mut Transcript,
msg: &Summary,
receivers: impl IntoIterator<Item = PublicKey>,
strategy: &impl Strategy,
) -> (Map<PublicKey, Scalar>, VrfCommitments) {
let receivers = Map::from_iter_dedup(receivers.into_iter().map(|x| {
let point = x.point.clone();
(x, point)
}));
let (circuit, witness) = self
.inner
.expose(|x| vrf_batch_checked(msg, x, receivers.values()));
let claim = witness.claim(setup.inner());
let circuit_proof = prove(
&mut *rng,
transcript,
setup.inner(),
&circuit,
&claim,
&witness,
strategy,
)
.expect("proving should succeed");
let outputs = Map::try_from_iter(
receivers
.into_iter()
.zip(witness.values())
.map(|((receiver, _), output)| (receiver, output.clone())),
)
.expect("receivers was already deduplicated");
let commitments = Map::try_from_iter(outputs.keys().iter().cloned().zip(claim.commitments))
.expect("receivers was already deduplicated");
let pedersen_to_plain = {
let setup = pedersen_to_plain::Setup {
value_generator: *setup.inner().value_generator(),
blinding_generator: *setup.inner().blinding_generator(),
};
let mut out = Vec::new();
for (receiver, output) in outputs.iter_pairs() {
let commitment = *commitments
.get_value(receiver)
.expect("output should have commitment");
let proof = pedersen_to_plain::prove(
&mut *rng,
transcript,
&setup,
&pedersen_to_plain::Claim {
plain: commitment,
pedersen: commitment,
},
&pedersen_to_plain::Witness {
value: output.clone(),
blinding: Scalar::zero(),
},
);
out.push(proof);
}
out
};
let proof = Proof {
circuit_proof,
pedersen_to_plain,
};
(outputs, VrfCommitments { proof, commitments })
}
}
impl Write for PrivateKey {
fn write(&self, buf: &mut impl BufMut) {
self.inner
.expose(|x| buf.put_slice(&x.encode_fixed::<{ F::SIZE }>()));
}
}
impl Read for PrivateKey {
type Cfg = ();
fn read_cfg(buf: &mut impl Buf, _: &()) -> Result<Self, CodecError> {
let raw = Zeroizing::new(<[u8; Self::SIZE]>::read(buf)?);
let x: F = ReadExt::read(&mut raw.as_slice())?;
Ok(Self {
inner: Secret::new(x),
})
}
}
impl FixedSize for PrivateKey {
const SIZE: usize = F::SIZE;
}
#[derive(Clone, Eq, Hash, Ord, PartialEq, PartialOrd, FixedArray)]
pub struct Signature {
raw: [u8; G::SIZE + F::SIZE],
}
impl Write for Signature {
fn write(&self, buf: &mut impl BufMut) {
self.raw.write(buf);
}
}
impl Read for Signature {
type Cfg = ();
fn read_cfg(buf: &mut impl Buf, _: &()) -> Result<Self, CodecError> {
let raw = <[u8; Self::SIZE]>::read(buf)?;
Ok(Self { raw })
}
}
impl FixedSize for Signature {
const SIZE: usize = G::SIZE + F::SIZE;
}
impl crate::Signature for Signature {}
impl Span for Signature {}
impl Array for Signature {}
impl AsRef<[u8]> for Signature {
fn as_ref(&self) -> &[u8] {
&self.raw
}
}
impl Deref for Signature {
type Target = [u8];
fn deref(&self) -> &[u8] {
&self.raw
}
}
impl Debug for Signature {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{}", hex(&self.raw))
}
}
impl Display for Signature {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{}", hex(&self.raw))
}
}
#[derive(Clone, FixedArray)]
pub struct PublicKey {
raw: [u8; G::SIZE],
point: G,
}
impl PublicKey {
fn from_point(point: G) -> Self {
let raw: [u8; G::SIZE] = point.encode_fixed();
Self { raw, point }
}
}
impl crate::Verifier for PublicKey {
type Signature = Signature;
fn verify(&self, namespace: &[u8], msg: &[u8], sig: &Signature) -> bool {
let k_big: G = match ReadExt::read(&mut &sig.raw[..G::SIZE]) {
Ok(p) => p,
Err(_) => return false,
};
let s: F = match ReadExt::read(&mut &sig.raw[G::SIZE..]) {
Ok(s) => s,
Err(_) => return false,
};
let mut t = Transcript::new(SCHNORR_NS);
t.commit(namespace)
.commit(msg)
.commit(self.raw.as_slice())
.commit(sig.raw[..G::SIZE].as_ref());
let e = F::random(t.noise(b"challenge"));
let lhs = G::generator() * &s;
let rhs = k_big + &(self.point.clone() * &e);
lhs == rhs
}
}
impl crate::PublicKey for PublicKey {}
impl Write for PublicKey {
fn write(&self, buf: &mut impl BufMut) {
self.raw.write(buf);
}
}
impl Read for PublicKey {
type Cfg = ();
fn read_cfg(buf: &mut impl Buf, _: &()) -> Result<Self, CodecError> {
let raw = <[u8; Self::SIZE]>::read(buf)?;
let point: G = ReadExt::read(&mut raw.as_slice())?;
Ok(Self { raw, point })
}
}
impl FixedSize for PublicKey {
const SIZE: usize = G::SIZE;
}
impl Span for PublicKey {}
impl Array for PublicKey {}
impl AsRef<[u8]> for PublicKey {
fn as_ref(&self) -> &[u8] {
&self.raw
}
}
impl Deref for PublicKey {
type Target = [u8];
fn deref(&self) -> &[u8] {
&self.raw
}
}
impl Eq for PublicKey {}
impl PartialEq for PublicKey {
fn eq(&self, other: &Self) -> bool {
self.raw == other.raw
}
}
impl Ord for PublicKey {
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
self.raw.cmp(&other.raw)
}
}
impl PartialOrd for PublicKey {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Hash for PublicKey {
fn hash<H: Hasher>(&self, state: &mut H) {
self.raw.hash(state);
}
}
impl Debug for PublicKey {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{}", hex(self))
}
}
impl Display for PublicKey {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{}", hex(self))
}
}
#[derive(Clone)]
struct Proof {
circuit_proof: circuit::Proof<Scalar, G1>,
pedersen_to_plain: Vec<pedersen_to_plain::Proof<Scalar, G1>>,
}
impl Write for Proof {
fn write(&self, buf: &mut impl BufMut) {
self.circuit_proof.write(buf);
self.pedersen_to_plain.write(buf);
}
}
impl EncodeSize for Proof {
fn encode_size(&self) -> usize {
self.circuit_proof.encode_size() + self.pedersen_to_plain.encode_size()
}
}
impl Read for Proof {
type Cfg = NonZeroU32;
fn read_cfg(buf: &mut impl Buf, max_players: &Self::Cfg) -> Result<Self, CodecError> {
let max_proof_len = 1usize << lg_len_for_players(max_players.get());
let circuit_proof = circuit::Proof::<Scalar, G1>::read_cfg(
buf,
&(max_proof_len, ((), ScalarReadCfg::AllowZero)),
)?;
let range = commonware_codec::RangeCfg::new(0..=max_players.get() as usize);
let pedersen_to_plain = Vec::<pedersen_to_plain::Proof<Scalar, G1>>::read_cfg(
buf,
&(range, ((), ScalarReadCfg::AllowZero)),
)?;
Ok(Self {
circuit_proof,
pedersen_to_plain,
})
}
}
impl Write for VrfCommitments {
fn write(&self, buf: &mut impl BufMut) {
self.proof.write(buf);
self.commitments.write(buf);
}
}
impl EncodeSize for VrfCommitments {
fn encode_size(&self) -> usize {
self.proof.encode_size() + self.commitments.encode_size()
}
}
impl Read for VrfCommitments {
type Cfg = NonZeroU32;
fn read_cfg(buf: &mut impl Buf, max_players: &Self::Cfg) -> Result<Self, CodecError> {
let proof = Proof::read_cfg(buf, max_players)?;
let range = commonware_codec::RangeCfg::new(0..=max_players.get() as usize);
let commitments = Read::read_cfg(buf, &(range, (), ()))?;
Ok(Self { proof, commitments })
}
}
#[derive(Clone)]
pub struct VrfCommitments {
proof: Proof,
commitments: Map<PublicKey, G1>,
}
impl VrfCommitments {
#[cfg(any(feature = "arbitrary", test))]
pub(super) fn perturb(&mut self, receiver: &PublicKey, delta: &G1) {
if let Some(c) = self.commitments.get_value_mut(receiver) {
*c += delta;
}
}
pub fn check_batch(
rng: &mut impl CryptoRng,
setup: &Setup,
transcript: &Transcript,
players: &Set<PublicKey>,
outputs: impl IntoIterator<Item = (PublicKey, Bytes, Self)>,
strategy: &impl Strategy,
) -> Map<PublicKey, Map<PublicKey, G1>> {
let outputs: Vec<(PublicKey, Bytes, Self)> = outputs
.into_iter()
.filter_map(|(sender, msg, commitments)| {
let mut buf: &[u8] = msg.as_ref();
let _: Summary = ReadExt::read(&mut buf).ok()?;
if commitments.proof.pedersen_to_plain.len() != commitments.commitments.len() {
return None;
}
if commitments
.commitments
.keys()
.iter()
.any(|pk| players.position(pk).is_none())
{
return None;
}
Some((sender, msg, commitments))
})
.collect();
let per_sender = setup.inner().eval_check_batched(
rng,
|vs, rng| {
let pp_setup = pedersen_to_plain::Setup {
value_generator: vs.value_generator().clone(),
blinding_generator: vs.blinding_generator().clone(),
};
let mut per_sender = Vec::with_capacity(outputs.len());
for (sender, msg, commitments) in &outputs {
let receivers: Vec<G> = commitments
.commitments
.keys()
.iter()
.map(|pk| pk.point.clone())
.collect();
let circuit =
vrf_batch_checked_circuit(msg.as_ref(), &sender.point, &receivers);
let claim = circuit::Claim {
commitments: commitments.commitments.values().to_vec(),
};
let mut t = transcript.fork(b"dealer vrf");
t.commit(sender.encode());
let Some(circuit_synth) = verify(
&mut *rng,
&mut t,
vs,
&circuit,
&claim,
commitments.proof.circuit_proof.clone(),
strategy,
) else {
per_sender.push(None);
continue;
};
let mut sender_acc = circuit_synth * &Scalar::random(&mut *rng);
for ((_, comm), pp_proof) in commitments
.commitments
.iter_pairs()
.zip(commitments.proof.pedersen_to_plain.iter().cloned())
{
let pp_claim = pedersen_to_plain::Claim {
plain: *comm,
pedersen: *comm,
};
let pp_synth = pedersen_to_plain::verify(
&mut *rng, &mut t, &pp_setup, &pp_claim, pp_proof,
);
sender_acc += &(pp_synth * &Scalar::random(&mut *rng));
}
per_sender.push(Some(sender_acc));
}
Some(per_sender)
},
strategy,
);
let Some(per_sender) = per_sender else {
return Map::default();
};
outputs
.into_iter()
.zip(per_sender)
.filter_map(|((sender, _, commitments), valid)| {
valid.then_some((sender, commitments.commitments))
})
.try_collect()
.expect("senders must be unique")
}
}
#[cfg(test)]
mod tests {
use super::*;
use commonware_macros::test_group;
use commonware_parallel::Sequential;
use commonware_utils::test_rng;
use std::sync::LazyLock;
static TEST_SETUP: LazyLock<Setup> = LazyLock::new(|| Setup::new(NonZeroU32::new(3).unwrap()));
#[test_group("slow")]
#[test]
fn vrf_batch_checked_roundtrips_through_check_batch() {
let mut rng = test_rng();
let sender_sk = PrivateKey::random(&mut rng);
let sender_pk = sender_sk.public();
let receiver_pks: Vec<PublicKey> = (0..3)
.map(|_| PrivateKey::random(&mut rng).public())
.collect();
let nonce = Summary::random(&mut rng);
let msg = Bytes::copy_from_slice(nonce.as_ref());
let outer_transcript = Transcript::new(b"vrf-batch-checked-test");
let mut prover_t = outer_transcript.fork(b"dealer vrf");
prover_t.commit(sender_pk.encode());
let (_outputs, commitments) = sender_sk.vrf_batch_checked(
&mut rng,
&TEST_SETUP,
&mut prover_t,
&nonce,
receiver_pks.iter().cloned(),
&Sequential,
);
let players: Set<PublicKey> = receiver_pks.iter().cloned().try_collect().unwrap();
let result = VrfCommitments::check_batch(
&mut rng,
&TEST_SETUP,
&outer_transcript,
&players,
std::iter::once((sender_pk.clone(), msg, commitments.clone())),
&Sequential,
);
assert_eq!(result.len(), 1);
let checked = result
.get_value(&sender_pk)
.expect("sender should appear in batch result");
assert_eq!(checked, &commitments.commitments);
}
#[test_group("slow")]
#[test]
fn check_batch_rejects_perturbed_commitments() {
let mut rng = test_rng();
let sender_sk = PrivateKey::random(&mut rng);
let sender_pk = sender_sk.public();
let receiver_pks: Vec<PublicKey> = (0..3)
.map(|_| PrivateKey::random(&mut rng).public())
.collect();
let nonce = Summary::random(&mut rng);
let msg = Bytes::copy_from_slice(nonce.as_ref());
let outer_transcript = Transcript::new(b"vrf-batch-checked-test");
let mut prover_t = outer_transcript.fork(b"dealer vrf");
prover_t.commit(sender_pk.encode());
let (_outputs, mut commitments) = sender_sk.vrf_batch_checked(
&mut rng,
&TEST_SETUP,
&mut prover_t,
&nonce,
receiver_pks.iter().cloned(),
&Sequential,
);
commitments.perturb(&receiver_pks[0], &G1::generator());
let players: Set<PublicKey> = receiver_pks.iter().cloned().try_collect().unwrap();
let result = VrfCommitments::check_batch(
&mut rng,
&TEST_SETUP,
&outer_transcript,
&players,
std::iter::once((sender_pk, msg, commitments)),
&Sequential,
);
assert!(result.is_empty());
}
#[test]
fn check_batch_rejects_short_pedersen_to_plain_vector() {
let mut rng = test_rng();
let sender_sk = PrivateKey::random(&mut rng);
let sender_pk = sender_sk.public();
let receiver_pks: Vec<PublicKey> = (0..3)
.map(|_| PrivateKey::random(&mut rng).public())
.collect();
let nonce = Summary::random(&mut rng);
let msg = Bytes::copy_from_slice(nonce.as_ref());
let outer_transcript = Transcript::new(b"vrf-batch-checked-test");
let mut prover_t = outer_transcript.fork(b"dealer vrf");
prover_t.commit(sender_pk.encode());
let (_outputs, mut commitments) = sender_sk.vrf_batch_checked(
&mut rng,
&TEST_SETUP,
&mut prover_t,
&nonce,
receiver_pks.iter().cloned(),
&Sequential,
);
commitments.proof.pedersen_to_plain.pop().unwrap();
assert!(
commitments.proof.pedersen_to_plain.len() < commitments.commitments.len(),
"test setup expects fewer proofs than commitments",
);
let players: Set<PublicKey> = receiver_pks.iter().cloned().try_collect().unwrap();
let result = VrfCommitments::check_batch(
&mut rng,
&TEST_SETUP,
&outer_transcript,
&players,
std::iter::once((sender_pk, msg, commitments)),
&Sequential,
);
assert!(result.is_empty());
}
#[test_group("slow")]
#[test]
fn check_batch_falls_back_to_per_sender_on_failure() {
let mut rng = test_rng();
let senders: Vec<(PrivateKey, PublicKey)> = (0..2)
.map(|_| {
let sk = PrivateKey::random(&mut rng);
let pk = sk.public();
(sk, pk)
})
.collect();
let receiver_pks: Vec<PublicKey> = (0..3)
.map(|_| PrivateKey::random(&mut rng).public())
.collect();
let outer_transcript = Transcript::new(b"vrf-batch-checked-test");
let mut prepared = Vec::new();
for (sk, pk) in &senders {
let nonce = Summary::random(&mut rng);
let msg = Bytes::copy_from_slice(nonce.as_ref());
let mut prover_t = outer_transcript.fork(b"dealer vrf");
prover_t.commit(pk.encode());
let (_outputs, commitments) = sk.vrf_batch_checked(
&mut rng,
&TEST_SETUP,
&mut prover_t,
&nonce,
receiver_pks.iter().cloned(),
&Sequential,
);
prepared.push((pk.clone(), msg, commitments));
}
prepared[1].2.perturb(&receiver_pks[0], &G1::generator());
let players: Set<PublicKey> = receiver_pks.iter().cloned().try_collect().unwrap();
let result = VrfCommitments::check_batch(
&mut rng,
&TEST_SETUP,
&outer_transcript,
&players,
prepared.iter().cloned(),
&Sequential,
);
assert_eq!(result.len(), 1);
let good_pk = &senders[0].1;
let bad_pk = &senders[1].1;
assert_eq!(result.get_value(good_pk), Some(&prepared[0].2.commitments));
assert!(result.get_value(bad_pk).is_none());
}
#[test]
fn setup_codec_roundtrip() {
let s = Setup::new(NonZeroU32::new(3).unwrap());
let bytes = s.encode();
let decoded = Setup::read_cfg(&mut bytes.as_ref(), &NonZeroU32::new(3).unwrap()).unwrap();
assert_eq!(decoded.max_players(), s.max_players());
assert_eq!(decoded.encode(), bytes);
}
}