use super::consts::{C_POINT_G, C_POINT_X, SC_1DIV8};
use super::hash::HashHelper;
use super::hashtoec::hp;
use super::{Point, Scalar, mul8, random_scalar};
use crate::base::sig::ClsagSig;
use crate::error::{Error, Result};
use crate::rng::RngCore;
pub const CRYPTO_HDS_CLSAG_GGX_LAYER_0: &[u8; 32] = b"ZANO_HDS_CLSAG_GGX_LAYER_ZERO__\x00";
pub const CRYPTO_HDS_CLSAG_GGX_LAYER_1: &[u8; 32] = b"ZANO_HDS_CLSAG_GGX_LAYER_ONE___\x00";
pub const CRYPTO_HDS_CLSAG_GGX_LAYER_2: &[u8; 32] = b"ZANO_HDS_CLSAG_GGX_LAYER_TWO___\x00";
pub const CRYPTO_HDS_CLSAG_GGX_CHALLENGE: &[u8; 32] = b"ZANO_HDS_CLSAG_GGX_CHALLENGE___\x00";
#[derive(Clone, Copy, Debug)]
pub struct ClsagGgxInputRef {
pub stealth_address: Point,
pub amount_commitment: Point,
pub blinded_asset_id: Point,
}
struct RingContext {
input_hash: [u8; 32],
agg_coeff0: Scalar,
agg_coeff1: Scalar,
agg_coeff2: Scalar,
w_pub_g: Vec<Point>,
w_pub_x: Vec<Point>,
w_key_image_g: Point,
w_key_image_x: Point,
}
fn ring_context(
m: &[u8],
ring: &[ClsagGgxInputRef],
ki: &Point,
pseudo_out_amount_commitment: &Point,
pseudo_out_blinded_asset_id: &Point,
k1_div8: &Point,
k2_div8: &Point,
) -> RingContext {
let mut hsc = HashHelper::new();
hsc.add_bytes_mod_l(m);
for r in ring {
hsc.add_point(&r.stealth_address);
hsc.add_point(&r.amount_commitment);
hsc.add_point(&r.blinded_asset_id);
}
hsc.add_point(&pseudo_out_amount_commitment.mul(&SC_1DIV8));
hsc.add_point(&pseudo_out_blinded_asset_id.mul(&SC_1DIV8));
hsc.add_point(ki);
hsc.add_point(k1_div8);
hsc.add_point(k2_div8);
let input_hash = hsc.calc_raw_hash();
hsc.add_bytes(CRYPTO_HDS_CLSAG_GGX_LAYER_0);
hsc.add_bytes(&input_hash);
let agg_coeff0 = hsc.calc_hash();
hsc.add_bytes(CRYPTO_HDS_CLSAG_GGX_LAYER_1);
hsc.add_bytes(&input_hash);
let agg_coeff1 = hsc.calc_hash();
hsc.add_bytes(CRYPTO_HDS_CLSAG_GGX_LAYER_2);
hsc.add_bytes(&input_hash);
let agg_coeff2 = hsc.calc_hash();
let mut w_pub_g = Vec::with_capacity(ring.len());
let mut w_pub_x = Vec::with_capacity(ring.len());
for r in ring {
let a_i = mul8(&r.amount_commitment);
let q_i = mul8(&r.blinded_asset_id);
let term1 = r.stealth_address.mul(&agg_coeff0);
let term2 = a_i.sub(pseudo_out_amount_commitment).mul(&agg_coeff1);
w_pub_g.push(term1.add(&term2));
w_pub_x.push(q_i.sub(pseudo_out_blinded_asset_id).mul(&agg_coeff2));
}
let k1 = mul8(k1_div8);
let k2 = mul8(k2_div8);
let w_key_image_g = ki.mul(&agg_coeff0).add(&k1.mul(&agg_coeff1));
let w_key_image_x = k2.mul(&agg_coeff2);
RingContext {
input_hash,
agg_coeff0,
agg_coeff1,
agg_coeff2,
w_pub_g,
w_pub_x,
w_key_image_g,
w_key_image_x,
}
}
fn ring_step(
ctx: &RingContext,
ring: &[ClsagGgxInputRef],
sig_rg: &Scalar,
sig_rx: &Scalar,
i: usize,
c: &Scalar,
) -> Scalar {
let hp_i = hp(&ring[i].stealth_address.compress());
let mut h = HashHelper::new();
h.add_bytes(CRYPTO_HDS_CLSAG_GGX_CHALLENGE);
h.add_bytes(&ctx.input_hash);
h.add_point(&C_POINT_G.mul(sig_rg).add(&ctx.w_pub_g[i].mul(c)));
h.add_point(&hp_i.mul(sig_rg).add(&ctx.w_key_image_g.mul(c)));
h.add_point(&C_POINT_X.mul(sig_rx).add(&ctx.w_pub_x[i].mul(c)));
h.add_point(&hp_i.mul(sig_rx).add(&ctx.w_key_image_x.mul(c)));
h.calc_hash()
}
#[allow(clippy::too_many_arguments)]
pub fn generate_clsag_ggx(
rnd: &mut dyn RngCore,
m: &[u8],
ring: &[ClsagGgxInputRef],
ki: &Point,
pseudo_out_amount_commitment: &Point,
pseudo_out_blinded_asset_id: &Point,
secret_0_xp: &Scalar,
secret_1_f: &Scalar,
secret_2_t: &Scalar,
secret_index: u64,
) -> Result<ClsagSig> {
let ring_size = ring.len();
if ring_size == 0 {
return Err(Error::msg("ring size is zero"));
}
if secret_index >= ring_size as u64 {
return Err(Error::msg("secretIndex out of range"));
}
let real = secret_index as usize;
let ki_base = hp(&ring[real].stealth_address.compress());
let key_image = ki_base.mul(secret_0_xp);
if key_image != *ki {
return Err(Error::msg("CLSAG_GGX keyImage mismatch"));
}
if C_POINT_G.mul(secret_0_xp) != ring[real].stealth_address {
return Err(Error::msg("CLSAG_GGX secret_0_xp mismatch"));
}
if C_POINT_G.mul(secret_1_f)
!= mul8(&ring[real].amount_commitment).sub(pseudo_out_amount_commitment)
{
return Err(Error::msg("CLSAG_GGX secret_1_f mismatch"));
}
if C_POINT_X.mul(secret_2_t)
!= mul8(&ring[real].blinded_asset_id).sub(pseudo_out_blinded_asset_id)
{
return Err(Error::msg("CLSAG_GGX secret_2_t mismatch"));
}
let k1_div8 = ki_base.mul(&SC_1DIV8.mul(secret_1_f));
let k2_div8 = ki_base.mul(&SC_1DIV8.mul(secret_2_t));
let ctx = ring_context(
m,
ring,
ki,
pseudo_out_amount_commitment,
pseudo_out_blinded_asset_id,
&k1_div8,
&k2_div8,
);
let w_sec_key_g = ctx
.agg_coeff0
.mul(secret_0_xp)
.add(&ctx.agg_coeff1.mul(secret_1_f));
let w_sec_key_x = ctx.agg_coeff2.mul(secret_2_t);
if C_POINT_G.mul(&w_sec_key_g) != ctx.w_pub_g[real] {
return Err(Error::msg("CLSAG_GGX w_sec_key_g mismatch"));
}
if C_POINT_X.mul(&w_sec_key_x) != ctx.w_pub_x[real] {
return Err(Error::msg("CLSAG_GGX w_sec_key_x mismatch"));
}
let alpha_g = random_scalar(rnd);
let alpha_x = random_scalar(rnd);
let mut hsc = HashHelper::new();
hsc.add_bytes(CRYPTO_HDS_CLSAG_GGX_CHALLENGE);
hsc.add_bytes(&ctx.input_hash);
hsc.add_point(&C_POINT_G.mul(&alpha_g));
hsc.add_point(&ki_base.mul(&alpha_g));
hsc.add_point(&C_POINT_X.mul(&alpha_x));
hsc.add_point(&ki_base.mul(&alpha_x));
let mut c_prev = hsc.calc_hash();
let mut rg: Vec<Scalar> = (0..ring_size).map(|_| random_scalar(rnd)).collect();
let mut rx: Vec<Scalar> = (0..ring_size).map(|_| random_scalar(rnd)).collect();
let mut sig_c: Option<Scalar> = None;
let mut i = (real + 1) % ring_size;
for _ in 0..ring_size - 1 {
if i == 0 {
sig_c = Some(c_prev.clone());
}
c_prev = ring_step(&ctx, ring, &rg[i], &rx[i], i, &c_prev);
i = (i + 1) % ring_size;
}
if real == 0 {
sig_c = Some(c_prev.clone());
}
rg[real] = alpha_g.sub(&c_prev.mul(&w_sec_key_g));
rx[real] = alpha_x.sub(&c_prev.mul(&w_sec_key_x));
Ok(ClsagSig {
c: sig_c.expect("the ring always sets c"),
rg,
rx,
k1: k1_div8,
k2: k2_div8,
})
}
pub fn verify_clsag_ggx(
m: &[u8],
ring: &[ClsagGgxInputRef],
ki: &Point,
pseudo_out_amount_commitment: &Point,
pseudo_out_blinded_asset_id: &Point,
sig: &ClsagSig,
) -> Result<bool> {
let ring_size = ring.len();
if ring_size == 0 {
return Err(Error::msg("ring size is zero"));
}
if sig.rg.len() != ring_size || sig.rx.len() != ring_size {
return Err(Error::msg("malformed signature"));
}
let ctx = ring_context(
m,
ring,
ki,
pseudo_out_amount_commitment,
pseudo_out_blinded_asset_id,
&sig.k1,
&sig.k2,
);
let mut c = sig.c.clone();
for i in 0..ring_size {
c = ring_step(&ctx, ring, &sig.rg[i], &sig.rx[i], i, &c);
}
Ok(c == sig.c)
}