use alloc::vec::Vec;
use k256::{ProjectivePoint, Scalar};
use rand_core::CryptoRng;
use crate::{
arith::sbig::{SBig, SRange},
curve::{decode_scalar, encode_point, encode_scalar, is_identity, random_nonzero_scalar},
error::{Error, Result},
params::Params,
transcript::{Challenge, Transcript, uint_to_be},
};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct DlogProof {
pub e: SBig,
pub z: Scalar,
}
fn sbig_to_scalar(e: &SBig) -> Result<Scalar> {
let bytes = uint_to_be(e.mag(), 32)?;
let s = decode_scalar(&bytes)?;
Ok(if bool::from(e.is_neg()) { -s } else { s })
}
fn base(tx: &Transcript, x_pub: &ProjectivePoint) -> Transcript {
let mut t = tx.clone();
t.append("zk", b"dlog");
t.append("X", &encode_point(x_pub));
t
}
fn derive_e(t: &Transcript, a: &ProjectivePoint, params: &Params) -> SBig {
let mut t = t.clone();
t.append("A", &encode_point(a));
let mut ch: Challenge = t.challenge("e");
ch.pm_pow2(params.ell)
}
fn e_range(params: &Params) -> SRange {
SRange::pm_pow2(params.ell)
}
impl DlogProof {
pub fn prove<R: CryptoRng + ?Sized>(
rng: &mut R,
tx: &Transcript,
params: &Params,
x_pub: &ProjectivePoint,
x: &Scalar,
) -> Result<Self> {
if params.ell == 0 || params.ell > 255 || is_identity(x_pub) {
return Err(Error::OutOfRange);
}
let t = base(tx, x_pub);
let alpha = random_nonzero_scalar(rng);
let a = ProjectivePoint::GENERATOR * alpha;
let e = derive_e(&t, &a, params);
let z = alpha + sbig_to_scalar(&e)? * x;
Ok(Self { e, z })
}
pub fn verify(&self, tx: &Transcript, params: &Params, x_pub: &ProjectivePoint) -> Result<()> {
if params.ell == 0 || params.ell > 255 || is_identity(x_pub) {
return Err(Error::VerificationFailed);
}
if !self.e.in_range(e_range(params)) {
return Err(Error::OutOfRange);
}
let es = sbig_to_scalar(&self.e)?;
let a = ProjectivePoint::GENERATOR * self.z - *x_pub * es;
if is_identity(&a) {
return Err(Error::VerificationFailed);
}
let t = base(tx, x_pub);
if derive_e(&t, &a, params) == self.e {
Ok(())
} else {
Err(Error::VerificationFailed)
}
}
pub fn encode(&self, params: &Params) -> Result<Vec<u8>> {
let mut out = self.e.to_bytes(e_range(params))?;
out.extend_from_slice(&encode_scalar(&self.z));
Ok(out)
}
pub fn encoded_len(params: &Params) -> usize {
1 + (params.ell.saturating_sub(1) as usize).div_ceil(8).max(1) + 32
}
pub fn decode(bytes: &[u8], params: &Params) -> Result<Self> {
if bytes.len() != Self::encoded_len(params) {
return Err(Error::InvalidEncoding);
}
let (eb, zb) = bytes.split_at(bytes.len() - 32);
let e = SBig::from_bytes(eb, e_range(params))?;
let z = decode_scalar(zb)?;
Ok(Self { e, z })
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::params::PARAMS_128;
use rand_chacha::ChaCha8Rng;
use rand_core::SeedableRng;
fn setup() -> (ChaCha8Rng, Transcript, ProjectivePoint, Scalar) {
let mut rng = ChaCha8Rng::seed_from_u64(1);
let x = random_nonzero_scalar(&mut rng);
(
rng,
Transcript::new("test"),
ProjectivePoint::GENERATOR * x,
x,
)
}
#[test]
fn honest_and_roundtrip() {
let (mut rng, tx, xp, x) = setup();
let p = DlogProof::prove(&mut rng, &tx, &PARAMS_128, &xp, &x).unwrap();
p.verify(&tx, &PARAMS_128, &xp).unwrap();
let enc = p.encode(&PARAMS_128).unwrap();
assert_eq!(enc.len(), 17 + 32);
let q = DlogProof::decode(&enc, &PARAMS_128).unwrap();
assert_eq!(p, q);
q.verify(&tx, &PARAMS_128, &xp).unwrap();
assert!(DlogProof::decode(&enc[1..], &PARAMS_128).is_err());
let mut longer = enc.clone();
longer.push(0);
assert!(DlogProof::decode(&longer, &PARAMS_128).is_err());
}
#[test]
fn many_seeds() {
let (mut rng, tx, xp, x) = setup();
for _ in 0..50 {
let p = DlogProof::prove(&mut rng, &tx, &PARAMS_128, &xp, &x).unwrap();
p.verify(&tx, &PARAMS_128, &xp).unwrap();
}
}
#[test]
fn tampering_fails() {
let (mut rng, tx, xp, x) = setup();
let p = DlogProof::prove(&mut rng, &tx, &PARAMS_128, &xp, &x).unwrap();
let mut b = p.clone();
b.z += Scalar::ONE;
assert!(b.verify(&tx, &PARAMS_128, &xp).is_err());
for d in [1i64, -1] {
let mut b = p.clone();
b.e = b.e.add(&SBig::from_i64(d));
assert!(b.verify(&tx, &PARAMS_128, &xp).is_err());
}
let mut b = p.clone();
b.e = SBig::from_i64(1).add(&SBig::from_uint(
crypto_bigint::BoxedUint::one_with_precision(192).shl(127),
));
assert_eq!(b.verify(&tx, &PARAMS_128, &xp), Err(Error::OutOfRange));
let other = xp + ProjectivePoint::GENERATOR;
assert!(p.verify(&tx, &PARAMS_128, &other).is_err());
assert!(
p.verify(&tx, &PARAMS_128, &ProjectivePoint::IDENTITY)
.is_err()
);
let mut tx2 = Transcript::new("test");
tx2.append("x", b"y");
assert!(p.verify(&tx2, &PARAMS_128, &xp).is_err());
}
#[test]
fn wrong_witness_fails() {
let (mut rng, tx, xp, x) = setup();
let p = DlogProof::prove(&mut rng, &tx, &PARAMS_128, &xp, &(x + Scalar::ONE)).unwrap();
assert!(p.verify(&tx, &PARAMS_128, &xp).is_err());
}
#[test]
fn decode_rejects_noncanonical() {
let (mut rng, tx, xp, x) = setup();
let p = DlogProof::prove(&mut rng, &tx, &PARAMS_128, &xp, &x).unwrap();
let enc = p.encode(&PARAMS_128).unwrap();
let mut b = enc.clone();
for v in b[17..].iter_mut() {
*v = 0xff;
}
assert!(DlogProof::decode(&b, &PARAMS_128).is_err());
let mut b = enc.clone();
b[0] = 2;
assert!(DlogProof::decode(&b, &PARAMS_128).is_err());
let mut b = enc.clone();
b[0] = 1;
for v in b[1..17].iter_mut() {
*v = 0;
}
assert!(DlogProof::decode(&b, &PARAMS_128).is_err());
let mut b = enc.clone();
b[0] = 0;
b[1] = 0xff;
assert!(DlogProof::decode(&b, &PARAMS_128).is_err());
}
}