#![allow(dead_code)]
#![allow(clippy::needless_range_loop)]
use super::Error;
use super::bn::{self, Modulus};
use crate::frost::hashing::sha512_256i_tagged;
use purecrypto::bignum::BoxedUint;
use purecrypto::rng::RngCore;
pub(crate) const ITERATIONS: usize = 80;
pub(crate) struct ProofMod {
pub w: BoxedUint,
pub x: Vec<BoxedUint>, pub a: BoxedUint,
pub b: BoxedUint,
pub z: Vec<BoxedUint>, }
impl ProofMod {
pub(crate) fn to_parts(&self) -> Vec<Vec<u8>> {
let mut out = vec![bn::to_be(&self.w), bn::to_be(&self.a), bn::to_be(&self.b)];
out.extend(self.x.iter().map(bn::to_be));
out.extend(self.z.iter().map(bn::to_be));
out
}
pub(crate) fn from_parts(parts: &[Vec<u8>]) -> Option<ProofMod> {
if parts.len() != 3 + 2 * ITERATIONS {
return None;
}
let w = bn::from_be(&parts[0]);
let a = bn::from_be(&parts[1]);
let b = bn::from_be(&parts[2]);
let x = parts[3..3 + ITERATIONS]
.iter()
.map(|p| bn::from_be(p))
.collect();
let z = parts[3 + ITERATIONS..]
.iter()
.map(|p| bn::from_be(p))
.collect();
Some(ProofMod { w, x, a, b, z })
}
}
fn is_qr(v: &BoxedUint, n: &BoxedUint) -> bool {
bn::jacobi(v, n) == 1
}
fn challenges(session: &[u8], w: &BoxedUint, n: &BoxedUint) -> Vec<BoxedUint> {
let mut y: Vec<BoxedUint> = Vec::with_capacity(ITERATIONS);
for i in 0..ITERATIONS {
let mut ops: Vec<Vec<u8>> = Vec::with_capacity(2 + i);
ops.push(bn::to_be(w));
ops.push(bn::to_be(n));
for yj in &y {
ops.push(bn::to_be(yj));
}
let refs: Vec<&[u8]> = ops.iter().map(|v| v.as_slice()).collect();
let h = sha512_256i_tagged(session, &refs);
y.push(bn::rem(&bn::from_be(&h), n));
}
y
}
fn random_qnr<R: RngCore>(n: &BoxedUint, rng: &mut R) -> BoxedUint {
loop {
let w = bn::rand_below(n, rng);
if !w.is_zero() && bn::jacobi(&w, n) == -1 {
return w;
}
}
}
pub(crate) fn prove<R: RngCore>(
session: &[u8],
n: &BoxedUint,
p: &BoxedUint,
q: &BoxedUint,
rng: &mut R,
) -> Result<ProofMod, Error> {
let phi = bn::mul(&bn::sub(p, &bn::one()), &bn::sub(q, &bn::one()));
let w = random_qnr(n, rng);
let y = challenges(session, &w, n);
let inv_n = bn::mod_inv(n, &phi)
.ok_or_else(|| Error::Validation("modproof: N not invertible mod φ".into()))?;
let e0 = bn::add(&phi, &bn::u64(4)).shr_bits(3);
let expo = bn::rem(&bn::mul(&e0, &e0), &phi);
let modn = Modulus::new(n);
let mut x = vec![bn::u64(0); ITERATIONS];
let mut z = vec![bn::u64(0); ITERATIONS];
let mut a_bits: u128 = 1 << ITERATIONS;
let mut b_bits: u128 = 1 << ITERATIONS;
for i in 0..ITERATIONS {
let mut found = false;
for j in 0..4u8 {
let (ab, bb) = (j & 1, (j >> 1) & 1);
let mut yi = y[i].clone();
if ab == 1 {
yi = modn.sub(&bn::u64(0), &yi); }
if bb == 1 {
yi = modn.mul(&w, &yi); }
if is_qr(&yi, p) && is_qr(&yi, q) {
x[i] = modn.pow_secret(&yi, &expo); z[i] = modn.pow_secret(&y[i], &inv_n);
a_bits |= (ab as u128) << i;
b_bits |= (bb as u128) << i;
found = true;
break;
}
}
if !found {
return Err(Error::Validation(
"modproof: modulus is not a Blum integer".into(),
));
}
}
Ok(ProofMod {
w,
x,
a: bn::from_u128(a_bits),
b: bn::from_u128(b_bits),
z,
})
}
pub(crate) fn verify<R: RngCore>(
session: &[u8],
n: &BoxedUint,
pf: &ProofMod,
rng: &mut R,
) -> bool {
if pf.x.len() != ITERATIONS || pf.z.len() != ITERATIONS {
return false;
}
if is_qr(&pf.w, n) || pf.w.is_zero() || !pf.w.lt(n) {
return false;
}
if !bn::is_one(&bn::gcd(&pf.w, n)) {
return false;
}
for v in pf.z.iter().chain(pf.x.iter()) {
if v.is_zero() || !v.lt(n) {
return false;
}
}
if pf.a.bit_len() != ITERATIONS + 1 || pf.b.bit_len() != ITERATIONS + 1 {
return false;
}
if !n.is_odd() || bn::is_probable_prime(n, rng, 30) {
return false;
}
let y = challenges(session, &pf.w, n);
let modn = Modulus::new(n);
let four = bn::u64(4);
for i in 0..ITERATIONS {
if modn.pow_pub(&pf.z[i], n) != y[i] {
return false;
}
let left = modn.pow_pub(&pf.x[i], &four);
let mut right = y[i].clone();
if bn::bit(&pf.a, i) == 1 {
right = modn.sub(&bn::u64(0), &right);
}
if bn::bit(&pf.b, i) == 1 {
right = modn.mul(&pf.w, &right);
}
if left != right {
return false;
}
}
true
}
#[cfg(test)]
mod tests {
use super::super::testvec::{dec, fixtures};
use super::*;
fn load(v: &serde_json::Value) -> ProofMod {
ProofMod {
w: dec(&v["W"]),
x: v["X"].as_array().unwrap().iter().map(dec).collect(),
a: dec(&v["A"]),
b: dec(&v["B"]),
z: v["Z"].as_array().unwrap().iter().map(dec).collect(),
}
}
#[test]
fn go_modproof_verifies() {
let f = fixtures();
let mp = &f["modproof"];
let session = mp["session"].as_str().unwrap().as_bytes();
let n = dec(&mp["n"]);
let pf = load(mp);
let mut rng = purecrypto::rng::OsRng;
assert!(
verify(session, &n, &pf, &mut rng),
"Go modproof must verify"
);
let mut bad = load(mp);
bad.z[3] = bn::add(&bad.z[3], &bn::one());
assert!(!verify(session, &n, &bad, &mut rng));
}
#[test]
fn rust_modproof_roundtrip() {
let f = fixtures();
let mp = &f["modproof"];
let (n, p, q) = (dec(&mp["n"]), dec(&mp["p"]), dec(&mp["q"]));
let session = b"rust-mod-session";
let mut rng = purecrypto::rng::OsRng;
let proof = prove(session, &n, &p, &q, &mut rng).unwrap();
assert!(verify(session, &n, &proof, &mut rng));
assert!(!verify(b"other", &n, &proof, &mut rng));
}
}