#![no_std]
#![forbid(clippy::all)]
use core::array;
use ark_ec::{AffineRepr, CurveConfig, CurveGroup, short_weierstrass::SWCurveConfig};
use ark_ff::{
Field, UniformRand, Zero,
field_hashers::{DefaultFieldHasher, HashToField},
};
use ark_serialize::CanonicalSerialize;
use ark_std::rand::{RngCore as Rng, SeedableRng, rngs::StdRng, seq::SliceRandom};
use sha2::{Digest, Sha256};
use zeroize::{Zeroize, ZeroizeOnDrop};
type Curve = ark_secp256k1::Config;
type CurveAffine = ark_secp256k1::Affine;
type CurveProj = <CurveAffine as AffineRepr>::Group;
type Scalar = <Curve as CurveConfig>::ScalarField;
type Ciphertext = (CurveAffine, CurveAffine);
const GENERATOR: CurveAffine = <Curve as SWCurveConfig>::GENERATOR;
const OPEN_CARD_PRNG_SEED: &[u8] = b"CARDS-V1";
const PEDERSON_H_PRNG_SEED: &[u8] = b"PEDERSON-H-V1";
const PEDERSON_VECTOR_G_PRNG_SEED: &[u8] = b"PEDERSON-VECTOR-G-V1";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct PedersonCommitment(CurveAffine);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct PedersonWitness(Scalar);
#[derive(Debug, Clone, Copy)]
struct PedersonCommitKey<const N: usize> {
h: CurveProj,
gs: [CurveProj; N],
}
impl<const N: usize> Default for PedersonCommitKey<N> {
fn default() -> Self {
let mut h_drng = StdRng::from_seed(Sha256::digest(PEDERSON_H_PRNG_SEED).into());
let h = CurveProj::rand(&mut h_drng);
let mut gs_drng = StdRng::from_seed(Sha256::digest(PEDERSON_VECTOR_G_PRNG_SEED).into());
let gs = array::from_fn(|_| CurveProj::rand(&mut gs_drng));
Self { h, gs }
}
}
impl<const N: usize> PedersonCommitKey<N> {
fn commit_with_r(&self, m: Scalar, r: Scalar) -> CurveProj {
(GENERATOR * m) + (self.h * r)
}
fn commit<R: Rng>(&self, rng: &mut R, m: Scalar) -> (PedersonCommitment, PedersonWitness) {
let r = Scalar::rand(rng);
(
PedersonCommitment(self.commit_with_r(m, r).into_affine()),
PedersonWitness(r),
)
}
fn vector_commit_with_r(&self, ms: &[Scalar; N], r: Scalar) -> CurveProj {
(0..N).map(|i| self.gs[i] * ms[i]).sum::<CurveProj>() + (self.h * r)
}
fn vector_commit<R: Rng>(
&self,
rng: &mut R,
ms: &[Scalar; N],
) -> (PedersonCommitment, PedersonWitness) {
let r = Scalar::rand(rng);
(
PedersonCommitment(self.vector_commit_with_r(ms, r).into_affine()),
PedersonWitness(r),
)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct Transcript([u8; 32]);
impl Transcript {
const SERIALIZE_BUFFER_SIZE: usize = 256;
fn init(user_ctx: &[u8]) -> Self {
Self(Sha256::digest(user_ctx).into())
}
fn update_with_serialized<T: CanonicalSerialize>(h: &mut Sha256, label: &str, t: &T) {
let mut serialize_buffer = [0u8; Self::SERIALIZE_BUFFER_SIZE];
let serialized_size = t.compressed_size();
assert!(
serialized_size <= Self::SERIALIZE_BUFFER_SIZE,
"serialize buffer too small to serialize {label}: {serialized_size} < {}",
Self::SERIALIZE_BUFFER_SIZE
);
t.serialize_compressed(&mut serialize_buffer[..serialized_size])
.expect("infallible serialization");
h.update(serialized_size.to_be_bytes());
h.update(&serialize_buffer[..serialized_size]);
}
fn append<T: CanonicalSerialize>(self, label: &str, t: &T) -> Self {
let mut h = Sha256::new();
h.update(self.0);
h.update(label.len().to_be_bytes());
h.update(label.as_bytes());
Self::update_with_serialized(&mut h, label, t);
Self(h.finalize().into())
}
fn append_vec<T: CanonicalSerialize>(self, label: &str, v: &[T]) -> Self {
let mut h = Sha256::new();
h.update(self.0);
h.update(label.len().to_be_bytes());
h.update(label.as_bytes());
for (i, t) in v.iter().enumerate() {
h.update(i.to_be_bytes());
Self::update_with_serialized(&mut h, label, t);
}
Self(h.finalize().into())
}
fn derive_challenge_scalars<const N: usize>(&self, dst: &[u8]) -> [Scalar; N] {
<DefaultFieldHasher<Sha256> as HashToField<Scalar>>::new(dst).hash_to_field(&self.0)
}
}
#[derive(Clone, PartialEq, Eq, Zeroize, ZeroizeOnDrop)]
#[cfg_attr(test, derive(Debug))]
pub struct SecretKey(Scalar);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Verified<T>(T);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct PublicKey(CurveAffine);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct OwnershipProof {
a: CurveAffine, z: Scalar, }
impl OwnershipProof {
const DLOG_DST: &[u8] = b"ziffle/DLOG/v1";
fn challenge(pk: &PublicKey, a: &CurveAffine, ctx: &[u8]) -> Scalar {
let [e] = Transcript::init(ctx)
.append("pk", pk)
.append("a", a)
.derive_challenge_scalars(Self::DLOG_DST);
e
}
fn new<R: Rng>(rng: &mut R, sk: &SecretKey, pk: PublicKey, ctx: &[u8]) -> Self {
let w = Scalar::rand(rng);
let a = (GENERATOR * w).into_affine();
let e = Self::challenge(&pk, &a, ctx);
let z = w + e * sk.0;
OwnershipProof { a, z }
}
#[must_use]
pub fn verify(&self, pk: PublicKey, ctx: &[u8]) -> Option<Verified<PublicKey>> {
let e = Self::challenge(&pk, &self.a, ctx);
let lhs = (GENERATOR.into_group() * self.z).into_affine();
let rhs = (self.a.into_group() + (pk.0 * e)).into_affine();
(lhs == rhs).then_some(Verified(pk))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct AggregatePublicKey(CurveAffine);
impl AggregatePublicKey {
#[must_use]
pub fn new(pks: &[Verified<PublicKey>]) -> Self {
let apk: CurveProj = pks.iter().map(|pk| pk.0.0).sum();
Self(apk.into_affine())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RevealTokenProof {
t_g: CurveAffine,
t_c1: CurveAffine,
z: Scalar,
}
impl RevealTokenProof {
const DLEQ_DST: &[u8] = b"ziffle/DLEQ/v1";
fn challenge(
pk: PublicKey,
share: CurveAffine,
c1: CurveAffine,
t_g: CurveAffine,
t_c1: CurveAffine,
ctx: &[u8],
) -> Scalar {
let [e]: [_; 1] = Transcript::init(ctx)
.append("pk", &pk)
.append("share", &share)
.append("c1", &c1)
.append("t_g", &t_g)
.append("t_c1", &t_c1)
.derive_challenge_scalars(Self::DLEQ_DST);
e
}
fn new<R: Rng>(
rng: &mut R,
sk: Scalar,
pk: PublicKey,
share: CurveAffine,
c1: CurveProj,
ctx: &[u8],
) -> Self {
let w = Scalar::rand(rng);
let t_g = (GENERATOR * w).into_affine();
let t_c1 = (c1 * w).into_affine();
let e = Self::challenge(pk, share, c1.into_affine(), t_g, t_c1, ctx);
let z = w - (e * sk);
Self { t_g, t_c1, z }
}
#[must_use]
pub fn verify(
&self,
pk: Verified<PublicKey>,
token: RevealToken,
card: MaskedCard,
ctx: &[u8],
) -> Option<Verified<RevealToken>> {
let Verified(pk) = pk;
let RevealToken(share) = token;
let MaskedCard((c1, _)) = card;
let e = Self::challenge(pk, share, c1, self.t_g, self.t_c1, ctx);
if self.t_g != ((GENERATOR * self.z) + (pk.0.into_group() * e)).into_affine() {
return None;
}
if self.t_c1 != ((c1.into_group() * self.z) + (share.into_group() * e)).into_affine() {
return None;
}
Some(Verified(token))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RevealToken(CurveAffine);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AggregateRevealToken(CurveAffine);
impl AggregateRevealToken {
#[must_use]
pub fn new(pks: &[Verified<RevealToken>]) -> Self {
let art: CurveProj = pks.iter().map(|t| t.0.0.into_group()).sum();
Self(art.into_affine())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MaskedCard(Ciphertext);
impl MaskedCard {
pub fn reveal_token<R: Rng>(
&self,
rng: &mut R,
sk: &SecretKey,
pk: PublicKey,
ctx: &[u8],
) -> (RevealToken, RevealTokenProof) {
let SecretKey(sk) = sk;
let c1 = self.0.0.into_group();
let share = (c1 * sk).into_affine();
let proof = RevealTokenProof::new(rng, *sk, pk, share, c1, ctx);
(RevealToken(share), proof)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MaskedDeck<const N: usize>([Ciphertext; N]);
impl<const N: usize> Verified<MaskedDeck<N>> {
pub fn get(&self, idx: usize) -> Option<MaskedCard> {
self.0.0.get(idx).copied().map(MaskedCard)
}
}
macro_rules! usize_to_u64 {
($i:expr) => {
u64::try_from($i).expect("usize <= 64 bits")
};
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct MultiExpArg<const N: usize> {
c_alpha: PedersonCommitment,
c_beta: PedersonCommitment,
ct_mxp0: (CurveAffine, CurveAffine),
ct_mxp1: (CurveAffine, CurveAffine),
o_alpha: [Scalar; N],
o_r: Scalar,
beta: Scalar,
o_beta: Scalar,
tau: Scalar,
}
macro_rules! ct_mspp {
($cts:expr, $ss:expr) => {
$cts.iter()
.zip($ss)
.map(|((c1, c2), s)| (c1.into_group() * s, c2.into_group() * s))
.reduce(|(c1_acc, c2_acc), (c1, c2)| (c1_acc + c1, c2_acc + c2))
.expect("N > 0")
};
}
struct ProveMultiExpArgInputs<'a, const N: usize> {
ck: &'a PedersonCommitKey<N>,
apk: AggregatePublicKey,
xpi: &'a [Scalar; N],
w_xpi: PedersonWitness,
next: &'a [Ciphertext; N],
rho: &'a [Scalar; N],
ts: Transcript,
}
struct VerifyMultiExpArgInputs<'a, const N: usize> {
ck: &'a PedersonCommitKey<N>,
apk: AggregatePublicKey,
prev: &'a [Ciphertext; N],
next: &'a [Ciphertext; N],
x_base: Scalar,
c_xpi: PedersonCommitment,
ts: Transcript,
}
impl<const N: usize> MultiExpArg<N> {
const X_DST: &[u8] = b"ziffle/BG12MultiExpArgX/v1";
fn challenge_x(
ts: Transcript,
c_alpha: PedersonCommitment,
c_beta: PedersonCommitment,
ct_mxp0: Ciphertext,
ct_mxp1: Ciphertext,
) -> Scalar {
let [x] = ts
.append("c_alpha", &c_alpha)
.append("c_beta", &c_beta)
.append("ct_mxp0", &ct_mxp0)
.append("ct_mxp1", &ct_mxp1)
.derive_challenge_scalars(Self::X_DST);
x
}
fn new<R: Rng>(
rng: &mut R,
ProveMultiExpArgInputs {
ck,
apk: AggregatePublicKey(pk),
xpi,
w_xpi: PedersonWitness(w_xpi),
next,
rho,
ts,
}: ProveMultiExpArgInputs<N>,
) -> Self {
let alpha: [Scalar; N] = array::from_fn(|_| Scalar::rand(rng));
let beta = Scalar::rand(rng);
let (c_alpha, PedersonWitness(w_alpha)) = ck.vector_commit(rng, &alpha);
let (c_beta, PedersonWitness(w_beta)) = ck.commit(rng, beta);
let tau0 = Scalar::rand(rng);
let ct_mxp0 = {
let (c1, c2) = ct_mspp!(next, alpha);
(
((GENERATOR * tau0) + c1).into_affine(),
((GENERATOR * beta) + (pk * tau0) + c2).into_affine(),
)
};
let rho_agg: Scalar = -(0..N).map(|i| rho[i] * xpi[i]).sum::<Scalar>();
let ct_mxp1 = {
let (c1, c2) = ct_mspp!(next, xpi);
(
((GENERATOR * rho_agg) + c1).into_affine(),
((pk * rho_agg) + c2).into_affine(),
)
};
let x = Self::challenge_x(ts, c_alpha, c_beta, ct_mxp0, ct_mxp1);
let o_alpha: [Scalar; N] = array::from_fn(|i| alpha[i] + (x * xpi[i]));
let o_r: Scalar = w_alpha + (x * w_xpi);
let tau = tau0 + (x * rho_agg);
Self {
c_alpha,
c_beta,
ct_mxp0,
ct_mxp1,
o_alpha,
o_r,
beta,
o_beta: w_beta,
tau,
}
}
#[must_use]
fn verify(
&self,
VerifyMultiExpArgInputs {
ck,
apk: AggregatePublicKey(pk),
prev,
next,
x_base,
c_xpi: PedersonCommitment(c_xpi),
ts,
}: VerifyMultiExpArgInputs<N>,
) -> bool {
let x = Self::challenge_x(ts, self.c_alpha, self.c_beta, self.ct_mxp0, self.ct_mxp1);
let check1 = || {
let xs: [Scalar; N] = array::from_fn(|i| x_base.pow([usize_to_u64!(i + 1)]));
let (prod_c1, prod_c2) = ct_mspp!(prev, xs);
let (ct_mxp1_c1, ct_mxp1_c2) = self.ct_mxp1;
prod_c1.into_affine() == ct_mxp1_c1 && prod_c2.into_affine() == ct_mxp1_c2
};
let check2 = || {
let lhs = (c_xpi.into_group() * x) + self.c_alpha.0.into_group();
let rhs = ck.vector_commit_with_r(&self.o_alpha, self.o_r);
lhs.into_affine() == rhs.into_affine()
};
let check3 = || self.c_beta.0 == ck.commit_with_r(self.beta, self.o_beta);
let check4 = || {
let (ct_mxp0_c1, ct_mxp0_c2) = self.ct_mxp0;
let (ct_mxp1_c1, ct_mxp1_c2) = self.ct_mxp1;
let lhs_c1 = ct_mxp0_c1.into_group() + (ct_mxp1_c1.into_group() * x);
let lhs_c2 = ct_mxp0_c2.into_group() + (ct_mxp1_c2.into_group() * x);
let (prod_c1, prod_c2) = ct_mspp!(next, self.o_alpha);
let rhs_c1 = (GENERATOR * self.tau) + prod_c1;
let rhs_c2 = (GENERATOR * self.beta) + (pk * self.tau) + prod_c2;
lhs_c1.into_affine() == rhs_c1.into_affine()
&& lhs_c2.into_affine() == rhs_c2.into_affine()
};
check1() && check2() && check3() && check4()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct SingleValueProductArg<const N: usize> {
c_d: PedersonCommitment,
c_sdelta: PedersonCommitment,
c_cdelta: PedersonCommitment,
a_tilde: [Scalar; N],
b_tilde: [Scalar; N],
r_tilde: Scalar,
s_tilde: Scalar,
}
struct ProveSvpArgInputs<'a, const N: usize> {
ck: &'a PedersonCommitKey<N>,
y: Scalar,
z: Scalar,
pi: &'a [Scalar; N],
xpi: &'a [Scalar; N],
w_pi: PedersonWitness,
w_xpi: PedersonWitness,
ts: Transcript,
}
struct VerifySvpArgInputs<'a, const N: usize> {
ck: &'a PedersonCommitKey<N>,
x_base: Scalar,
y: Scalar,
z: Scalar,
c_pi: PedersonCommitment,
c_xpi: PedersonCommitment,
ts: Transcript,
}
impl<const N: usize> SingleValueProductArg<N> {
const X_DST: &[u8] = b"ziffle/BG12ProductArgX/v1";
fn challenge_x(
ts: Transcript,
c_d: PedersonCommitment,
c_sdelta: PedersonCommitment,
c_cdelta: PedersonCommitment,
) -> Scalar {
let [x] = ts
.append("c_d", &c_d)
.append("c_sdelta", &c_sdelta)
.append("c_cdelta", &c_cdelta)
.derive_challenge_scalars(Self::X_DST);
x
}
fn new<R: Rng>(
rng: &mut R,
ProveSvpArgInputs {
ck,
y,
z,
pi,
xpi,
w_pi: PedersonWitness(w_pi),
w_xpi: PedersonWitness(w_xpi),
ts,
}: ProveSvpArgInputs<N>,
) -> Self {
let d: [Scalar; N] = array::from_fn(|_| Scalar::rand(rng));
let (c_d, PedersonWitness(w_d)) = ck.vector_commit(rng, &d);
let mut sdelta: [Scalar; N] = [Scalar::zero(); N];
sdelta[0] = d[0];
(1..N - 1).for_each(|i| sdelta[i] = Scalar::rand(rng));
let (c_sdelta, PedersonWitness(w_sdelta)) = {
let mut v = [Scalar::zero(); N];
(0..N - 1).for_each(|i| v[i] = -sdelta[i] * d[i + 1]);
ck.vector_commit(rng, &v)
};
let a: [Scalar; N] = array::from_fn(|i| (y * pi[i]) + xpi[i] - z);
let mut b = [Scalar::ONE; N];
b[0] = a[0];
(1..N).for_each(|i| b[i] = b[i - 1] * a[i]);
let (c_cdelta, PedersonWitness(w_cdelta)) = {
let mut v = [Scalar::zero(); N];
(0..N - 1)
.for_each(|i| v[i] = sdelta[i + 1] - (a[i + 1] * sdelta[i]) - (b[i] * d[i + 1]));
ck.vector_commit(rng, &v)
};
let x = Self::challenge_x(ts, c_d, c_sdelta, c_cdelta);
let a_tilde: [Scalar; N] = array::from_fn(|i| (x * a[i]) + d[i]);
let b_tilde: [Scalar; N] = array::from_fn(|i| (x * b[i]) + sdelta[i]);
let w_a = (y * w_pi) + w_xpi;
let r_tilde: Scalar = (x * w_a) + w_d;
let s_tilde: Scalar = (x * w_cdelta) + w_sdelta;
Self {
c_d,
c_sdelta,
c_cdelta,
a_tilde,
b_tilde,
r_tilde,
s_tilde,
}
}
#[must_use]
fn verify(
&self,
VerifySvpArgInputs {
ck,
x_base,
y,
z,
c_pi: PedersonCommitment(c_pi),
c_xpi: PedersonCommitment(c_xpi),
ts,
}: VerifySvpArgInputs<N>,
) -> bool {
let x = Self::challenge_x(ts, self.c_d, self.c_sdelta, self.c_cdelta);
let c_mz = ck.vector_commit_with_r(&[-z; N], Scalar::zero());
let c_a = (c_pi.into_group() * y) + c_xpi.into_group();
let check1 = || {
let c_d_dmz = self.c_d.0.into_group() + ((c_a + c_mz) * x);
let c_a_tilde_r_tilde = ck.vector_commit_with_r(&self.a_tilde, self.r_tilde);
c_d_dmz.into_affine() == c_a_tilde_r_tilde.into_affine()
};
let check2 = || {
let c_sdelta_cdelta = self.c_sdelta.0.into_group() + (self.c_cdelta.0.into_group() * x);
let mut v = [Scalar::zero(); N];
(0..N - 1).for_each(|i| {
v[i] = (x * self.b_tilde[i + 1]) - (self.b_tilde[i] * self.a_tilde[i + 1]);
});
let c_a_tilde_b_tilde = ck.vector_commit_with_r(&v, self.s_tilde);
c_sdelta_cdelta.into_affine() == c_a_tilde_b_tilde.into_affine()
};
let check3 = || self.b_tilde[0] == self.a_tilde[0];
let check4 = || {
let public_prod: Scalar = (1..=N)
.map(|i| {
(y * Scalar::new(usize_to_u64!(i).into())) + (x_base.pow([usize_to_u64!(i)]))
- z
})
.product();
self.b_tilde[N - 1] == (x * public_prod)
};
check1() && check2() && check3() && check4()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ShuffleProof<const N: usize> {
c_pi: PedersonCommitment,
c_xpi: PedersonCommitment,
mexp_arg: MultiExpArg<N>,
prod_arg: SingleValueProductArg<N>,
}
struct ShuffleProofInputs<'a, const N: usize> {
ck: &'a PedersonCommitKey<N>,
apk: AggregatePublicKey,
perm: &'a [usize; N],
prev: &'a [Ciphertext; N],
next: &'a [Ciphertext; N],
rho: &'a [Scalar; N],
ctx: &'a [u8],
}
impl<const N: usize> ShuffleProof<N> {
const X_DST: &[u8] = b"ziffle/BG12X/v1";
const YZ_DST: &[u8] = b"ziffle/BG12YZ/v1";
fn challenge_x(
apk: AggregatePublicKey,
prev: &[Ciphertext; N],
next: &[Ciphertext; N],
c_pi: PedersonCommitment,
ctx: &[u8],
) -> (Transcript, Scalar) {
let ts = Transcript::init(ctx)
.append("apk", &apk)
.append_vec("prev", prev)
.append_vec("next", next)
.append("c_pi", &c_pi);
let [x] = ts.derive_challenge_scalars(Self::X_DST);
(ts, x)
}
fn challenge_yz(ts: Transcript, c_xpi: PedersonCommitment) -> (Transcript, Scalar, Scalar) {
let ts = ts.append("c_xpi", &c_xpi);
let [y, z] = ts.derive_challenge_scalars(Self::YZ_DST);
(ts, y, z)
}
fn new<R: Rng>(
rng: &mut R,
ShuffleProofInputs {
ck,
apk,
perm,
prev,
next,
rho,
ctx,
}: ShuffleProofInputs<N>,
) -> Self {
let pi: [_; N] = array::from_fn(|i| Scalar::new(usize_to_u64!(perm[i] + 1).into()));
let (c_pi, w_pi) = ck.vector_commit(rng, &pi);
let (ts, x) = Self::challenge_x(apk, prev, next, c_pi, ctx);
let xpi: [Scalar; N] = array::from_fn(|i| x.pow([usize_to_u64!(perm[i]) + 1]));
let (c_xpi, w_xpi) = ck.vector_commit(rng, &xpi);
let (ts, y, z) = Self::challenge_yz(ts, c_xpi);
let mexp_arg = MultiExpArg::new(
rng,
ProveMultiExpArgInputs {
ck,
apk,
xpi: &xpi,
w_xpi,
next,
rho,
ts: ts.clone(),
},
);
let prod_arg = SingleValueProductArg::new(
rng,
ProveSvpArgInputs {
ck,
y,
z,
pi: &pi,
xpi: &xpi,
w_pi,
w_xpi,
ts,
},
);
Self {
c_pi,
c_xpi,
mexp_arg,
prod_arg,
}
}
#[must_use]
fn verify(
&self,
ck: &PedersonCommitKey<N>,
apk: AggregatePublicKey,
prev: &[Ciphertext; N],
next: &[Ciphertext; N],
ctx: &[u8],
) -> Option<Verified<MaskedDeck<N>>> {
let (ts, x) = Self::challenge_x(apk, prev, next, self.c_pi, ctx);
let (ts, y, z) = Self::challenge_yz(ts, self.c_xpi);
if !self.mexp_arg.verify(VerifyMultiExpArgInputs {
ck,
apk,
prev,
next,
x_base: x,
c_xpi: self.c_xpi,
ts: ts.clone(),
}) {
return None;
}
if !self.prod_arg.verify(VerifySvpArgInputs {
ck,
x_base: x,
y,
z,
c_pi: self.c_pi,
c_xpi: self.c_xpi,
ts,
}) {
return None;
}
Some(Verified(MaskedDeck(*next)))
}
}
fn open_deck<const N: usize>() -> [CurveAffine; N] {
let mut drng = StdRng::from_seed(Sha256::digest(OPEN_CARD_PRNG_SEED).into());
array::from_fn(|_| (GENERATOR * Scalar::rand(&mut drng)).into_affine())
}
fn remask_card<R: Rng>(
rng: &mut R,
AggregatePublicKey(pk): AggregatePublicKey,
(c1, c2): Ciphertext,
) -> (Ciphertext, Scalar) {
let r = Scalar::rand(rng);
let c1 = c1 + GENERATOR * r;
let c2 = c2 + (pk * r);
((c1.into_affine(), c2.into_affine()), r)
}
fn shuffle_remask_prove<const N: usize, R: Rng>(
rng: &mut R,
ck: &PedersonCommitKey<N>,
apk: AggregatePublicKey,
prev: &[Ciphertext; N],
ctx: &[u8],
) -> (MaskedDeck<N>, ShuffleProof<N>) {
let mut perm: [usize; N] = array::from_fn(|idx| idx);
perm.as_mut_slice().shuffle(rng);
let next = &mut [(CurveAffine::identity(), CurveAffine::identity()); N];
let rho = &mut [Scalar::zero(); N];
(0..N).for_each(|i| {
let (c, r) = remask_card(rng, apk, prev[perm[i]]);
next[i] = c;
rho[i] = r;
});
let proof = ShuffleProof::new(
rng,
ShuffleProofInputs {
ck,
apk,
perm: &perm,
prev,
next,
rho,
ctx,
},
);
let Verified(next) = proof
.verify(ck, apk, prev, next, ctx)
.expect("invalid shuffle proof");
(next, proof)
}
#[derive(Debug, Clone, Copy)]
pub struct Shuffle<const N: usize> {
commit_key: PedersonCommitKey<N>,
open_deck: [CurveAffine; N],
}
impl<const N: usize> Default for Shuffle<N> {
fn default() -> Self {
Self {
commit_key: PedersonCommitKey::default(),
open_deck: open_deck(),
}
}
}
impl<const N: usize> Shuffle<N> {
const _N_GREATER_THAN_1: () = assert!(N > 1);
fn initial_deck(&self) -> [Ciphertext; N] {
array::from_fn(|i| (CurveAffine::identity(), self.open_deck[i]))
}
#[must_use]
pub fn keygen<R: Rng>(
&self,
rng: &mut R,
ctx: &[u8],
) -> (SecretKey, PublicKey, OwnershipProof) {
let sk = Scalar::rand(rng);
let pk = GENERATOR * sk;
let (sk, pk) = (SecretKey(sk), PublicKey(pk.into_affine()));
let proof = OwnershipProof::new(rng, &sk, pk, ctx);
(sk, pk, proof)
}
#[must_use]
pub fn shuffle_initial_deck<R: Rng>(
&self,
rng: &mut R,
apk: AggregatePublicKey,
ctx: &[u8],
) -> (MaskedDeck<N>, ShuffleProof<N>) {
shuffle_remask_prove(rng, &self.commit_key, apk, &self.initial_deck(), ctx)
}
#[must_use]
pub fn shuffle_deck<R: Rng>(
&self,
rng: &mut R,
apk: AggregatePublicKey,
prev: &Verified<MaskedDeck<N>>,
ctx: &[u8],
) -> (MaskedDeck<N>, ShuffleProof<N>) {
shuffle_remask_prove(rng, &self.commit_key, apk, &prev.0.0, ctx)
}
#[must_use]
pub fn verify_initial_shuffle(
&self,
apk: AggregatePublicKey,
next: MaskedDeck<N>,
proof: ShuffleProof<N>,
ctx: &[u8],
) -> Option<Verified<MaskedDeck<N>>> {
proof.verify(&self.commit_key, apk, &self.initial_deck(), &next.0, ctx)
}
#[must_use]
pub fn verify_shuffle(
&self,
apk: AggregatePublicKey,
prev: &Verified<MaskedDeck<N>>,
next: MaskedDeck<N>,
proof: ShuffleProof<N>,
ctx: &[u8],
) -> Option<Verified<MaskedDeck<N>>> {
proof.verify(&self.commit_key, apk, &prev.0.0, &next.0, ctx)
}
#[must_use]
pub fn reveal_card(&self, art: AggregateRevealToken, card: MaskedCard) -> Option<usize> {
let AggregateRevealToken(art) = art;
let MaskedCard((_, c2)) = card;
let pt = (c2.into_group() - art.into_group()).into_affine();
self.open_deck.iter().position(|&oc| oc == pt)
}
}
macro_rules! impl_valid_and_serde_unit {
(@impl_check) => {
fn check(&self) -> Result<(), ark_serialize::SerializationError> {
self.0.check()
}
};
(@impl_ser) => {
fn serialize_with_mode<W: ark_serialize::Write>(
&self,
writer: W,
compress: ark_serialize::Compress,
) -> Result<(), ark_serialize::SerializationError> {
self.0.serialize_with_mode(writer, compress)
}
fn serialized_size(&self, compress: ark_serialize::Compress) -> usize {
self.0.serialized_size(compress)
}
};
(@impl_deser) => {
fn deserialize_with_mode<R: ark_serialize::Read>(
reader: R,
compress: ark_serialize::Compress,
validate: ark_serialize::Validate,
) -> Result<Self, ark_serialize::SerializationError> {
ark_serialize::CanonicalDeserialize::deserialize_with_mode(reader, compress, validate).map(Self)
}
};
($t:tt< $N:ident >) => {
impl<const $N: usize> ark_serialize::Valid for $t<$N> {
impl_valid_and_serde_unit!(@impl_check );
}
impl<const $N: usize> ark_serialize::CanonicalSerialize for $t<$N> {
impl_valid_and_serde_unit!(@impl_ser );
}
impl<const $N: usize> ark_serialize::CanonicalDeserialize for $t<$N> {
impl_valid_and_serde_unit!(@impl_deser);
}
};
($t:ty) => {
impl ark_serialize::Valid for $t {
impl_valid_and_serde_unit!(@impl_check );
}
impl ark_serialize::CanonicalSerialize for $t {
impl_valid_and_serde_unit!(@impl_ser );
}
impl ark_serialize::CanonicalDeserialize for $t {
impl_valid_and_serde_unit!(@impl_deser);
}
};
}
impl_valid_and_serde_unit!(PublicKey);
impl_valid_and_serde_unit!(SecretKey);
impl_valid_and_serde_unit!(PedersonCommitment);
impl_valid_and_serde_unit!(RevealToken);
impl_valid_and_serde_unit!(MaskedDeck<N>);
impl ark_serialize::CanonicalSerialize for AggregatePublicKey {
impl_valid_and_serde_unit!(@impl_ser );
}
macro_rules! impl_valid_and_deser {
(@impl_check $($field:ident),+) => {
fn check(&self) -> Result<(), ark_serialize::SerializationError> {
$( ark_serialize::Valid::check(&self.$field)?; )*
Ok(())
}
};
(@impl_ser $($field:ident),+) => {
fn serialize_with_mode<W: ark_serialize::Write>(
&self,
mut writer: W,
compress: ark_serialize::Compress,
) -> Result<(), ark_serialize::SerializationError> {
$( self.$field.serialize_with_mode(&mut writer, compress)?; )*
Ok(())
}
fn serialized_size(&self, compress: ark_serialize::Compress) -> usize {
[
$( self.$field.serialized_size(compress), )*
].iter().sum()
}
};
(@impl_deser $($field:ident),+) => {
fn deserialize_with_mode<R: ark_serialize::Read>(
mut reader: R,
compress: ark_serialize::Compress,
validate: ark_serialize::Validate,
) -> Result<Self, ark_serialize::SerializationError> {
Ok(Self {
$( $field: ark_serialize::CanonicalDeserialize::deserialize_with_mode(
&mut reader, compress, validate
)?, )*
})
}
};
($t:tt< $N:ident > { $($field:ident),+ }) => {
impl<const $N: usize> ark_serialize::Valid for $t<$N> {
impl_valid_and_deser!(@impl_check $($field),*);
}
impl<const $N: usize> ark_serialize::CanonicalSerialize for $t<$N> {
impl_valid_and_deser!(@impl_ser $($field),*);
}
impl<const $N: usize> ark_serialize::CanonicalDeserialize for $t<$N> {
impl_valid_and_deser!(@impl_deser $($field),*);
}
};
($t:ty { $($field:ident),+ }) => {
impl ark_serialize::Valid for $t {
impl_valid_and_deser!(@impl_check $($field),*);
}
impl ark_serialize::CanonicalSerialize for $t {
impl_valid_and_deser!(@impl_ser $($field),*);
}
impl ark_serialize::CanonicalDeserialize for $t {
impl_valid_and_deser!(@impl_deser $($field),*);
}
};
}
impl_valid_and_deser!(MultiExpArg<N> {
c_alpha, c_beta, ct_mxp0, ct_mxp1, o_alpha, o_r, beta, o_beta, tau
});
impl_valid_and_deser!(SingleValueProductArg<N> {
c_d, c_sdelta, c_cdelta, a_tilde, b_tilde, r_tilde, s_tilde
});
impl_valid_and_deser!(ShuffleProof<N> {
c_pi, c_xpi, mexp_arg, prod_arg
});
impl_valid_and_deser!(RevealTokenProof { t_g, t_c1, z });
impl_valid_and_deser!(OwnershipProof { a, z });
#[cfg(test)]
mod test;