use super::clsag::{ClsagContext, ClsagCoordinator, ClsagParty};
use super::keygen::spend_public_key;
use super::transport::exchange;
use crate::base::sig::ClsagSig;
use crate::crypto::clsag::ClsagGgxInputRef;
use crate::crypto::{Point, Scalar, hp};
use crate::error::{Error, Result};
use crate::inputsigner::{ClsagRequest, InputSigner};
use crate::rng::{OsRng, RngCore, ShakeRng};
use purecrypto::hash::{Digest, Keccak256};
use serde::{Deserialize, Serialize};
use tsslib::frosttss::Key;
use tsslib::tss::Parameters;
#[derive(Clone, Serialize, Deserialize)]
struct Round1Msg {
ki: String,
commit_g: String,
commit_k: String,
}
#[derive(Clone, Serialize, Deserialize)]
struct Round2Msg {
resp: String,
}
fn point_hex(p: &Point) -> String {
hex::encode(p.compress())
}
fn parse_point(s: &str) -> Result<Point> {
crate::crypto::point_from_bytes(&hex::decode(s)?)
}
fn parse_scalar(s: &str) -> Result<Scalar> {
crate::crypto::scalar_from_canonical(&hex::decode(s)?)
}
struct ThresholdInput {
idx: usize,
party: ClsagParty,
r1: Vec<Round1Msg>,
partial_kis: Vec<Point>,
commit_gs: Vec<Point>,
commit_ks: Vec<Point>,
}
pub struct ThresholdInputSigner {
params: Parameters,
subset: Key,
spend_pub: Point,
inputs: Vec<ThresholdInput>,
sign_n: usize,
}
impl ThresholdInputSigner {
pub fn new(params: Parameters, key: &Key) -> Result<ThresholdInputSigner> {
let subset = key
.subset_for_parties(params.parties())
.map_err(|e| crate::err!("zanompc: {e}"))?;
let spend_pub = spend_public_key(&subset.group_public_key)?;
Ok(ThresholdInputSigner {
params,
subset,
spend_pub,
inputs: Vec::new(),
sign_n: 0,
})
}
pub fn spend_pub_key(&self) -> Point {
self.spend_pub
}
pub fn params(&self) -> &Parameters {
&self.params
}
pub fn subset(&self) -> &Key {
&self.subset
}
fn round1(&mut self, ki_base: &Point, topic: &str) -> Result<usize> {
let idx = self.inputs.len();
let party = ClsagParty::new(&self.subset, None, &mut OsRng)?;
let (my_ki, my_cg, my_ck) = party.round1(ki_base);
let mine = Round1Msg {
ki: point_hex(&my_ki),
commit_g: point_hex(&my_cg),
commit_k: point_hex(&my_ck),
};
let collected: Vec<Round1Msg> = exchange(&self.params, topic, &mine)?;
let mut partial_kis = Vec::with_capacity(collected.len());
let mut commit_gs = Vec::with_capacity(collected.len());
let mut commit_ks = Vec::with_capacity(collected.len());
for m in &collected {
partial_kis.push(parse_point(&m.ki)?);
commit_gs.push(parse_point(&m.commit_g)?);
commit_ks.push(parse_point(&m.commit_k)?);
}
self.inputs.push(ThresholdInput {
idx,
party,
r1: collected,
partial_kis,
commit_gs,
commit_ks,
});
Ok(idx)
}
}
impl InputSigner for ThresholdInputSigner {
fn key_image(&mut self, hi: &Scalar, in_e_pub: &Point) -> Result<Point> {
let ki_base = hp(&in_e_pub.compress());
let idx = self.inputs.len();
self.round1(&ki_base, &format!("zano:clsag:ki:{idx}"))?;
let input = &self.inputs[idx];
Ok(ki_base
.mul(hi)
.add(&super::sign::combine_points(&input.partial_kis)))
}
fn sign_clsag(&mut self, _rnd: &mut dyn RngCore, req: &ClsagRequest<'_>) -> Result<ClsagSig> {
if self.sign_n >= self.inputs.len() {
return Err(Error::msg(
"zanompc: sign_clsag called more times than key_image",
));
}
let n = self.sign_n;
self.sign_n += 1;
let ctx = ClsagContext {
message: req.msg,
ring: req.ring,
pseudo_out_amount_commitment: req.pseudo_out_amount_commitment,
pseudo_out_blinded_asset_id: req.pseudo_out_blinded_asset_id,
secret1_f: req.secret1_f,
secret2_t: req.secret2_t,
hi: req.hi,
spend_pub: &self.spend_pub,
secret_index: req.secret_index,
};
let mut det = transcript_rng(req.msg, req.ring, &self.inputs[n].r1);
let (mut coord, c_prev, agg0) = ClsagCoordinator::phase1(
&mut det,
&ctx,
&self.inputs[n].partial_kis,
&self.inputs[n].commit_gs,
&self.inputs[n].commit_ks,
)?;
let my_resp = self.inputs[n].party.round2(&c_prev, &agg0);
let topic = format!("zano:clsag:resp:{}", self.inputs[n].idx);
let collected: Vec<Round2Msg> = exchange(
&self.params,
&topic,
&Round2Msg {
resp: hex::encode(my_resp.to_bytes()),
},
)?;
let responses: Vec<Scalar> = collected
.iter()
.map(|m| parse_scalar(&m.resp))
.collect::<Result<_>>()?;
let (sig, _ki) = coord.phase2(&ctx, &responses)?;
Ok(sig)
}
}
fn transcript_rng(msg: &[u8], ring: &[ClsagGgxInputRef], r1: &[Round1Msg]) -> ShakeRng {
let mut h = Keccak256::new();
h.update(b"ZANO_THRESHOLD_CLSAG_DETRAND\x00");
h.update(msg);
for r in ring {
h.update(&r.stealth_address.compress());
h.update(&r.amount_commitment.compress());
h.update(&r.blinded_asset_id.compress());
}
for m in r1 {
h.update(&hex::decode(&m.ki).unwrap_or_default());
h.update(&hex::decode(&m.commit_g).unwrap_or_default());
h.update(&hex::decode(&m.commit_k).unwrap_or_default());
}
ShakeRng::new(&h.finalize())
}