use fawkes_crypto::{circuit::{
bitify::{c_into_bits_le, c_into_bits_le_strict, c_comp, c_from_bits_le},
bool::CBool,
eddsaposeidon::c_eddsaposeidon_verify,
num::CNum,
poseidon::{c_poseidon_merkle_proof_root, c_poseidon, c_poseidon_merkle_tree_root, c_poseidon_sponge, CMerkleProof},
cs::{RCS, CS}
}, ff_uint::PrimeFieldParams};
use fawkes_crypto::core::{signal::Signal, sizedvec::SizedVec,};
use fawkes_crypto::ff_uint::{Num, NumRepr};
use crate::{circuit::{account::CAccount, note::CNote, key::{c_derive_key_eta, c_derive_key_p_d}}};
use crate::native::tx::{TransferPub, TransferSec, Tx};
use crate::native::params::PoolParams;
use crate::constants::{HEIGHT, IN, OUT, BALANCE_SIZE_BITS, ENERGY_SIZE_BITS, POOLID_SIZE_BITS};
#[derive(Clone, Signal)]
#[Value = "TransferPub<C::Fr>"]
pub struct CTransferPub<C:CS> {
pub root: CNum<C>,
pub nullifier: CNum<C>,
pub out_commit: CNum<C>,
pub delta: CNum<C>, pub memo: CNum<C>,
}
#[derive(Clone, Signal)]
#[Value = "Tx<C::Fr>"]
pub struct CTx<C:CS> {
pub input: (CAccount<C>, SizedVec<CNote<C>, { IN }>),
pub output: (CAccount<C>, SizedVec<CNote<C>, { OUT}>)
}
#[derive(Clone, Signal)]
#[Value = "TransferSec<C::Fr>"]
pub struct CTransferSec<C:CS> {
pub tx: CTx<C>,
pub in_proof: (CMerkleProof<C, { HEIGHT }>, SizedVec<CMerkleProof<C, { HEIGHT }>, { IN }>),
pub eddsa_s: CNum<C>,
pub eddsa_r: CNum<C>,
pub eddsa_a: CNum<C>,
}
pub fn c_nullfifier<C:CS, P: PoolParams<Fr = C::Fr>>(
in_account_hash: &CNum<C>,
eta: &CNum<C>,
path: &CNum<C>,
params: &P,
) -> CNum<C> {
let intermediate_hash = c_poseidon(
[in_account_hash.clone(), eta.clone(), path.clone()].as_ref(),
params.nullifier_intermediate(),
);
c_poseidon(
[in_account_hash.clone(), intermediate_hash].as_ref(),
params.compress(),
)
}
pub fn c_tx_hash<C:CS, P: PoolParams<Fr = C::Fr>>(
in_hash: &[CNum<C>],
out_commitment: &CNum<C>,
params: &P,
) -> CNum<C> {
let data = in_hash.iter().chain(core::iter::once(out_commitment)).cloned().collect::<Vec<_>>();
c_poseidon_sponge(&data, params.sponge())
}
pub fn c_tx_verify<C:CS, P: PoolParams<Fr = C::Fr>>(
s: &CNum<C>,
r: &CNum<C>,
a: &CNum<C>,
tx_hash: &CNum<C>,
params: &P,
) -> CBool<C> {
c_eddsaposeidon_verify(s, r, a, tx_hash, params.eddsa(), params.jubjub())
}
pub fn c_out_commitment_hash<C:CS, P:PoolParams<Fr=C::Fr>>(items:&[CNum<C>], params: &P) -> CNum<C> {
assert!(items.len()==OUT+1);
c_poseidon_merkle_tree_root(items, params.compress())
}
pub fn c_parse_delta<C:CS, P:PoolParams<Fr=C::Fr>>(delta: &CNum<C>) -> (CNum<C>, CNum<C>, CNum<C>, CNum<C>) {
fn c_parse_uint<C:CS>(bits: &mut &[CBool<C>], len:usize) -> CNum<C> {
let res = c_from_bits_le(&bits[0..len]);
*bits = &bits[len..];
res
}
fn c_parse_int<C:CS>(bits: &mut &[CBool<C>], len:usize) -> CNum<C> {
let two_component_term = - bits[len-1].as_num() * Num::from_uint(NumRepr::ONE << len as u32).unwrap();
two_component_term + c_parse_uint(bits, len)
}
let delta_bits_vec = c_into_bits_le(delta, BALANCE_SIZE_BITS+ENERGY_SIZE_BITS+HEIGHT+POOLID_SIZE_BITS);
let mut delta_bits = delta_bits_vec.as_slice();
(
c_parse_int(&mut delta_bits, BALANCE_SIZE_BITS),
c_parse_int(&mut delta_bits, ENERGY_SIZE_BITS),
c_parse_uint(&mut delta_bits, HEIGHT),
c_parse_uint(&mut delta_bits, POOLID_SIZE_BITS),
)
}
pub fn c_transfer<C:CS, P:PoolParams<Fr=C::Fr>>(
p: &CTransferPub<C>,
s: &CTransferSec<C>,
params: &P,
) {
let (delta_value, delta_energy, current_index, poolid) = c_parse_delta::<C,P>(&p.delta);
let mut total_value = delta_value;
let mut total_enegry = delta_energy;
let input_index = s.tx.input.0.i.as_num();
let output_index = s.tx.output.0.i.as_num();
let in_account_hash = s.tx.input.0.hash(params);
let in_note_hash = s.tx.input.1.iter().map(|n| n.hash(params)).collect::<Vec<_>>();
let in_hash = [[in_account_hash.clone()].as_ref(), in_note_hash.as_slice()].concat();
let mut t:CNum<C> = p.derive_const(&Num::ZERO);
for i in 0..IN {
for j in i+1..IN {
t+=(&in_note_hash[i]-&in_note_hash[j]).is_zero().as_num();
}
}
t.assert_zero();
let out_account_hash = s.tx.output.0.hash(params);
let out_note_hash = s.tx.output.1.iter().map(|e| e.hash(params)).collect::<Vec<_>>();
let out_hash = [[out_account_hash].as_ref(), out_note_hash.as_slice()].concat();
let mut t:CNum<C> = p.derive_const(&Num::ZERO);
let mut out_note_zero_num:CNum<C> = p.derive_const(&Num::ZERO);
for i in 0..OUT {
out_note_zero_num+=s.tx.output.1[i].is_zero().as_num();
for j in i+1..OUT {
t+=(&out_note_hash[i]-&out_note_hash[j]).is_zero().as_num();
}
}
t -= &out_note_zero_num*(&out_note_zero_num-Num::ONE)/Num::from(2u64);
t.assert_zero();
let out_ch = c_out_commitment_hash(&out_hash, params);
(&out_ch - &p.out_commit).assert_zero();
let eta = c_derive_key_eta(&s.eddsa_a, params);
let eta_bits = c_into_bits_le_strict(&eta);
(&s.tx.input.0.p_d - c_derive_key_p_d(&s.tx.input.0.d.as_num(), &eta_bits, params).x).assert_zero();
(&s.tx.output.0.p_d - c_derive_key_p_d(&s.tx.output.0.d.as_num(), &eta_bits, params).x).assert_zero();
for i in 0..IN {
(&s.tx.input.1[i].p_d - c_derive_key_p_d(&s.tx.input.1[i].d.as_num(), &eta_bits, params).x).assert_zero();
}
{
let ref input_pos_index = c_from_bits_le(s.in_proof.0.path.as_slice());
(&p.nullifier - c_nullfifier(&in_account_hash, &eta, input_pos_index, params)).assert_zero();
let cur_root = c_poseidon_merkle_proof_root(&in_account_hash, &s.in_proof.0, params.compress());
(cur_root.is_eq(&p.root) | s.tx.input.0.is_initial(&poolid)).assert_const(&true);
c_comp(input_index, output_index, HEIGHT).assert_const(&false);
c_comp(output_index, ¤t_index, HEIGHT).assert_const(&false);
total_enegry += s.tx.input.0.b.as_num() * (¤t_index - input_pos_index);
}
for i in 0..IN {
let note_value = s.tx.input.1[i].b.as_num();
let ref note_index = c_from_bits_le(s.in_proof.1[i].path.as_slice());
let cur_root = c_poseidon_merkle_proof_root(&in_note_hash[i], &s.in_proof.1[i], params.compress());
((cur_root - &p.root) * note_value).assert_zero();
let note_index_ok = (!c_comp(input_index, note_index, HEIGHT)) & c_comp(output_index, note_index, HEIGHT);
let note_dummy = s.tx.input.1[i].is_dummy_raw().is_zero();
(note_index_ok | note_dummy).assert_const(&true);
total_enegry += note_value * (¤t_index - note_index);
}
(&p.memo + Num::ONE).assert_nonzero();
let tx_hash = c_tx_hash(&in_hash, &out_ch, params);
c_tx_verify(&s.eddsa_s, &s.eddsa_r, &s.eddsa_a, &tx_hash, params).assert_const(&true);
total_value += s.tx.input.0.b.as_num() - s.tx.output.0.b.as_num();
for note in s.tx.input.1.iter() {
total_value += note.b.as_num();
}
for note in s.tx.output.1.iter() {
total_value -= note.b.as_num();
}
total_value.assert_zero();
total_enegry += s.tx.input.0.e.as_num();
total_enegry -= s.tx.output.0.e.as_num();
c_into_bits_le(&total_enegry, <C::Fr as PrimeFieldParams>::MODULUS_BITS as usize - 2);
}