use winterfell::{
crypto::{hashers::Rp64_256, DefaultRandomCoin, MerkleTree},
math::{fields::f64::BaseElement, FieldElement, ToElements},
matrix::ColMatrix,
AcceptableOptions, Air, AirContext, Assertion, BatchingMethod, DefaultConstraintCommitment,
DefaultConstraintEvaluator, DefaultTraceLde, EvaluationFrame, FieldExtension, Proof,
ProofOptions, Prover, StarkDomain, TraceInfo, TracePolyTable, TraceTable,
TransitionConstraintDegree,
};
use winterfell::crypto::hashers::Rp64_256 as Rescue;
const STATE_W: usize = 12;
const NUM_ROUNDS: usize = 7;
const ROWS_PER_HASH: usize = NUM_ROUNDS * 2 + 1; const NUM_HASHES: usize = 5;
const HASH_ROWS: usize = NUM_HASHES * ROWS_PER_HASH; const CONSERVATION_ROW: usize = HASH_ROWS; pub const TRACE_LEN: usize = 128;
pub const TRACE_W: usize = STATE_W + 1;
const SEL_COL: usize = STATE_W;
fn hash_start(h: usize) -> usize {
h * ROWS_PER_HASH
}
#[derive(Clone, Debug)]
pub struct CtPublicInputs {
pub s_old: [BaseElement; 4],
pub s_new: [BaseElement; 4],
pub r_old: [BaseElement; 4],
pub r_new: [BaseElement; 4],
pub amt: [BaseElement; 4],
}
impl ToElements<BaseElement> for CtPublicInputs {
fn to_elements(&self) -> Vec<BaseElement> {
let mut v = Vec::with_capacity(4 * NUM_HASHES);
for d in [
&self.s_old,
&self.s_new,
&self.r_old,
&self.r_new,
&self.amt,
] {
v.extend_from_slice(d);
}
v
}
}
pub struct CtAir {
context: AirContext<BaseElement>,
pub_inputs: CtPublicInputs,
}
impl Air for CtAir {
type BaseField = BaseElement;
type PublicInputs = CtPublicInputs;
fn new(trace_info: TraceInfo, pub_inputs: CtPublicInputs, options: ProofOptions) -> Self {
let mut degrees: Vec<TransitionConstraintDegree> = (0..STATE_W)
.map(|_| TransitionConstraintDegree::with_cycles(7, vec![128, 128]))
.collect();
degrees.push(TransitionConstraintDegree::new(1)); for _ in 0..7 {
degrees.push(TransitionConstraintDegree::with_cycles(1, vec![128]));
}
Self {
context: AirContext::new(trace_info, degrees, 4 * NUM_HASHES, options),
pub_inputs,
}
}
fn context(&self) -> &AirContext<BaseElement> {
&self.context
}
fn evaluate_transition<E: FieldElement<BaseField = BaseElement>>(
&self,
frame: &EvaluationFrame<E>,
periodic_values: &[E],
result: &mut [E],
) {
let cur = frame.current();
let next = frame.next();
let ark1 = &periodic_values[0..STATE_W];
let ark2 = &periodic_values[STATE_W..2 * STATE_W];
let is_first_half = periodic_values[2 * STATE_W];
let is_conservation = periodic_values[2 * STATE_W + 1];
let is_active = periodic_values[2 * STATE_W + 2];
let one = E::ONE;
let zero = E::ZERO;
let mds = Rescue::MDS;
let cur_pow7: Vec<E> = (0..STATE_W)
.map(|i| {
let c = cur[i];
let c2 = c * c;
let c4 = c2 * c2;
c4 * c2 * c
})
.collect();
let mut mds_cur_pow7 = vec![E::ZERO; STATE_W];
for i in 0..STATE_W {
for j in 0..STATE_W {
mds_cur_pow7[i] += E::from(mds[i][j]) * cur_pow7[j];
}
}
let _next_pow7: Vec<E> = (0..STATE_W)
.map(|i| {
let n = next[i];
let n2 = n * n;
let n4 = n2 * n2;
n4 * n2 * n
})
.collect();
let inv_mds = Rescue::INV_MDS;
let next_sub_ark2: Vec<E> = (0..STATE_W).map(|i| next[i] - ark2[i]).collect();
let mut inv_mds_next_sub_ark2 = vec![E::ZERO; STATE_W];
for i in 0..STATE_W {
for j in 0..STATE_W {
inv_mds_next_sub_ark2[i] += E::from(inv_mds[i][j]) * next_sub_ark2[j];
}
}
let inv_mds_pow7: Vec<E> = (0..STATE_W)
.map(|i| {
let v = inv_mds_next_sub_ark2[i];
let v2 = v * v;
let v4 = v2 * v2;
v4 * v2 * v
})
.collect();
for i in 0..STATE_W {
let fwd = next[i] - mds_cur_pow7[i] - ark1[i];
let bwd = cur[i] - inv_mds_pow7[i];
result[i] = is_active * (is_first_half * fwd + (one - is_first_half) * bwd);
}
result[SEL_COL] = zero;
result[STATE_W + 1] = is_conservation * (cur[0] - cur[8] - cur[2]);
result[STATE_W + 2] = is_conservation * (cur[4] + cur[8] - cur[6]);
result[STATE_W + 3] = is_conservation * cur[1];
result[STATE_W + 4] = is_conservation * cur[3];
result[STATE_W + 5] = is_conservation * cur[5];
result[STATE_W + 6] = is_conservation * cur[7];
result[STATE_W + 7] = is_conservation * cur[9];
}
fn get_assertions(&self) -> Vec<Assertion<BaseElement>> {
let mut assertions = Vec::new();
let digests = [
&self.pub_inputs.s_old,
&self.pub_inputs.s_new,
&self.pub_inputs.r_old,
&self.pub_inputs.r_new,
&self.pub_inputs.amt,
];
for (h, digest) in digests.iter().enumerate() {
let output_row = hash_start(h) + ROWS_PER_HASH - 1;
for (d, &val) in digest.iter().enumerate() {
assertions.push(Assertion::single(4 + d, output_row, val));
}
}
assertions
}
fn get_periodic_column_values(&self) -> Vec<Vec<BaseElement>> {
let mut ark1_cols: Vec<Vec<BaseElement>> = (0..STATE_W)
.map(|_| vec![BaseElement::ZERO; TRACE_LEN])
.collect();
let mut ark2_cols: Vec<Vec<BaseElement>> = (0..STATE_W)
.map(|_| vec![BaseElement::ZERO; TRACE_LEN])
.collect();
let mut is_first_half_col = vec![BaseElement::ZERO; TRACE_LEN];
let mut is_conservation_col = vec![BaseElement::ZERO; TRACE_LEN];
for h in 0..NUM_HASHES {
for r in 0..NUM_ROUNDS {
let fwd_step = hash_start(h) + r * 2;
let bwd_step = fwd_step + 1;
for col in 0..STATE_W {
ark1_cols[col][fwd_step] = Rescue::ARK1[r][col];
ark2_cols[col][bwd_step] = Rescue::ARK2[r][col];
}
is_first_half_col[fwd_step] = BaseElement::ONE;
}
}
is_conservation_col[CONSERVATION_ROW] = BaseElement::ONE;
let mut is_active_col = vec![BaseElement::ZERO; TRACE_LEN];
for h in 0..NUM_HASHES {
for step in hash_start(h)..(hash_start(h) + ROWS_PER_HASH - 1) {
is_active_col[step] = BaseElement::ONE;
}
}
let mut cols: Vec<Vec<BaseElement>> = Vec::new();
cols.extend(ark1_cols);
cols.extend(ark2_cols);
cols.push(is_first_half_col);
cols.push(is_conservation_col);
cols.push(is_active_col);
cols
}
}
pub struct CtWitness {
pub s_old_bal: u128,
pub s_new_bal: u128,
pub r_old_bal: u128,
pub r_new_bal: u128,
pub amount: u128,
pub s_old_nonce: u128,
pub s_new_nonce: u128,
pub r_old_nonce: u128,
pub r_new_nonce: u128,
pub amt_nonce: u128,
pub ct_hash_s_old: [u8; 16],
pub ct_hash_s_new: [u8; 16],
pub ct_hash_r_old: [u8; 16],
pub ct_hash_r_new: [u8; 16],
}
fn u128_to_felts(v: u128) -> [BaseElement; 2] {
[
BaseElement::new(v as u64),
BaseElement::new((v >> 64) as u64),
]
}
fn bytes16_to_felts(b: &[u8; 16]) -> [BaseElement; 2] {
let lo = u64::from_le_bytes(b[..8].try_into().unwrap());
let hi = u64::from_le_bytes(b[8..16].try_into().unwrap());
[BaseElement::new(lo), BaseElement::new(hi)]
}
fn build_rescue_input(bal: u128, nonce: u128, ct_hash: &[u8; 16]) -> [BaseElement; STATE_W] {
let mut state = [BaseElement::ZERO; STATE_W];
state[0] = BaseElement::new(6);
let bal_felts = u128_to_felts(bal);
let nonce_felts = u128_to_felts(nonce);
let ct_felts = bytes16_to_felts(ct_hash);
state[4] = bal_felts[0];
state[5] = bal_felts[1];
state[6] = nonce_felts[0];
state[7] = nonce_felts[1];
state[8] = ct_felts[0];
state[9] = ct_felts[1];
state
}
fn build_amount_rescue_input(amount: u128, nonce: u128) -> [BaseElement; STATE_W] {
let mut state = [BaseElement::ZERO; STATE_W];
state[0] = BaseElement::new(4); let amt_felts = u128_to_felts(amount);
let nonce_felts = u128_to_felts(nonce);
state[4] = amt_felts[0];
state[5] = amt_felts[1];
state[6] = nonce_felts[0];
state[7] = nonce_felts[1];
state
}
fn fill_rescue_trace(
trace: &mut TraceTable<BaseElement>,
start_row: usize,
initial_state: [BaseElement; STATE_W],
hash_idx: usize,
) {
let mut state = initial_state;
for col in 0..STATE_W {
trace.set(col, start_row, state[col]);
}
trace.set(SEL_COL, start_row, BaseElement::new(hash_idx as u64));
let mut row = start_row;
for r in 0..NUM_ROUNDS {
for i in 0..STATE_W {
let s = state[i];
let s2 = s * s;
let s4 = s2 * s2;
state[i] = s4 * s2 * s;
}
let mut tmp = [BaseElement::ZERO; STATE_W];
for i in 0..STATE_W {
for j in 0..STATE_W {
tmp[i] += Rescue::MDS[i][j] * state[j];
}
}
state = tmp;
for i in 0..STATE_W {
state[i] += Rescue::ARK1[r][i];
}
row += 1;
for col in 0..STATE_W {
trace.set(col, row, state[col]);
}
trace.set(SEL_COL, row, BaseElement::new(hash_idx as u64));
const INV_ALPHA: u64 = 10540996611094048183;
for i in 0..STATE_W {
state[i] = state[i].exp(INV_ALPHA.into());
}
let mut tmp = [BaseElement::ZERO; STATE_W];
for i in 0..STATE_W {
for j in 0..STATE_W {
tmp[i] += Rescue::MDS[i][j] * state[j];
}
}
state = tmp;
for i in 0..STATE_W {
state[i] += Rescue::ARK2[r][i];
}
row += 1;
for col in 0..STATE_W {
trace.set(col, row, state[col]);
}
trace.set(SEL_COL, row, BaseElement::new(hash_idx as u64));
}
}
pub fn build_trace(w: &CtWitness) -> TraceTable<BaseElement> {
let mut trace = TraceTable::new(TRACE_W, TRACE_LEN);
fill_rescue_trace(
&mut trace,
hash_start(0),
build_rescue_input(w.s_old_bal, w.s_old_nonce, &w.ct_hash_s_old),
0,
);
fill_rescue_trace(
&mut trace,
hash_start(1),
build_rescue_input(w.s_new_bal, w.s_new_nonce, &w.ct_hash_s_new),
1,
);
fill_rescue_trace(
&mut trace,
hash_start(2),
build_rescue_input(w.r_old_bal, w.r_old_nonce, &w.ct_hash_r_old),
2,
);
fill_rescue_trace(
&mut trace,
hash_start(3),
build_rescue_input(w.r_new_bal, w.r_new_nonce, &w.ct_hash_r_new),
3,
);
fill_rescue_trace(
&mut trace,
hash_start(4),
build_amount_rescue_input(w.amount, w.amt_nonce),
4,
);
let row = CONSERVATION_ROW;
trace.set(0, row, BaseElement::new(w.s_old_bal as u64));
trace.set(1, row, BaseElement::new((w.s_old_bal >> 64) as u64));
trace.set(2, row, BaseElement::new(w.s_new_bal as u64));
trace.set(3, row, BaseElement::new((w.s_new_bal >> 64) as u64));
trace.set(4, row, BaseElement::new(w.r_old_bal as u64));
trace.set(5, row, BaseElement::new((w.r_old_bal >> 64) as u64));
trace.set(6, row, BaseElement::new(w.r_new_bal as u64));
trace.set(7, row, BaseElement::new((w.r_new_bal >> 64) as u64));
trace.set(8, row, BaseElement::new(w.amount as u64));
trace.set(9, row, BaseElement::new((w.amount >> 64) as u64));
trace.set(SEL_COL, row, BaseElement::new(99));
trace
}
pub struct CtProver {
options: ProofOptions,
}
impl CtProver {
pub fn new() -> Self {
Self {
options: ProofOptions::new(
40, 8, 20, FieldExtension::None,
8, 255, BatchingMethod::Algebraic,
BatchingMethod::Algebraic,
),
}
}
pub fn prove(&self, witness: &CtWitness) -> Result<(Vec<u8>, CtPublicInputs), String> {
let s_old = witness.s_old_bal;
let s_new = witness.s_new_bal;
let r_old = witness.r_old_bal;
let r_new = witness.r_new_bal;
let amt = witness.amount;
if [s_old, s_new, r_old, r_new, amt]
.iter()
.any(|&value| value > u64::MAX as u128)
{
return Err("Confidential transfer balances and amount must fit in u64".into());
}
if amt == 0 {
return Err("Amount must be > 0".into());
}
let expected_s_new = s_old
.checked_sub(amt)
.ok_or("Sender balance underflow: old_balance < amount")?;
if s_new != expected_s_new {
return Err(format!("Conservation violated: {s_old} - {amt} != {s_new}"));
}
let expected_r_new = r_old.checked_add(amt).ok_or("Recipient balance overflow")?;
if r_new != expected_r_new {
return Err(format!("Conservation violated: {r_old} + {amt} != {r_new}"));
}
let s_old_digest = rescue_commit(s_old, witness.s_old_nonce, &witness.ct_hash_s_old);
let s_new_digest = rescue_commit(s_new, witness.s_new_nonce, &witness.ct_hash_s_new);
let r_old_digest = rescue_commit(r_old, witness.r_old_nonce, &witness.ct_hash_r_old);
let r_new_digest = rescue_commit(r_new, witness.r_new_nonce, &witness.ct_hash_r_new);
let amt_digest = rescue_commit_amount(amt, witness.amt_nonce);
let pub_inputs = CtPublicInputs {
s_old: s_old_digest,
s_new: s_new_digest,
r_old: r_old_digest,
r_new: r_new_digest,
amt: amt_digest,
};
let trace = build_trace(witness);
let proof =
Prover::prove(self, trace).map_err(|e| format!("Proof generation failed: {e}"))?;
let proof_bytes = proof.to_bytes();
Ok((proof_bytes, pub_inputs))
}
}
impl Prover for CtProver {
type BaseField = BaseElement;
type Air = CtAir;
type Trace = TraceTable<BaseElement>;
type HashFn = Rp64_256;
type VC = MerkleTree<Rp64_256>;
type RandomCoin = DefaultRandomCoin<Rp64_256>;
type TraceLde<E: FieldElement<BaseField = BaseElement>> =
DefaultTraceLde<E, Rp64_256, MerkleTree<Rp64_256>>;
type ConstraintEvaluator<'a, E: FieldElement<BaseField = BaseElement>> =
DefaultConstraintEvaluator<'a, CtAir, E>;
type ConstraintCommitment<E: FieldElement<BaseField = BaseElement>> =
DefaultConstraintCommitment<E, Rp64_256, MerkleTree<Rp64_256>>;
fn get_pub_inputs(&self, trace: &Self::Trace) -> CtPublicInputs {
let digest_of = |h: usize| -> [BaseElement; 4] {
let row = hash_start(h) + ROWS_PER_HASH - 1;
[
trace.get(4, row),
trace.get(5, row),
trace.get(6, row),
trace.get(7, row),
]
};
CtPublicInputs {
s_old: digest_of(0),
s_new: digest_of(1),
r_old: digest_of(2),
r_new: digest_of(3),
amt: digest_of(4),
}
}
fn options(&self) -> &ProofOptions {
&self.options
}
fn new_trace_lde<E: FieldElement<BaseField = BaseElement>>(
&self,
trace_info: &TraceInfo,
main_trace: &ColMatrix<BaseElement>,
domain: &StarkDomain<BaseElement>,
partition_option: winterfell::PartitionOptions,
) -> (Self::TraceLde<E>, TracePolyTable<E>) {
DefaultTraceLde::new(trace_info, main_trace, domain, partition_option)
}
fn new_evaluator<'a, E: FieldElement<BaseField = BaseElement>>(
&self,
air: &'a Self::Air,
aux_rand_elements: Option<winterfell::AuxRandElements<E>>,
composition_coefficients: winterfell::ConstraintCompositionCoefficients<E>,
) -> Self::ConstraintEvaluator<'a, E> {
DefaultConstraintEvaluator::new(air, aux_rand_elements, composition_coefficients)
}
fn build_constraint_commitment<E: FieldElement<BaseField = BaseElement>>(
&self,
composition_poly_trace: winterfell::CompositionPolyTrace<E>,
num_constraint_composition_columns: usize,
domain: &StarkDomain<BaseElement>,
partition_options: winterfell::PartitionOptions,
) -> (
Self::ConstraintCommitment<E>,
winterfell::CompositionPoly<E>,
) {
DefaultConstraintCommitment::new(
composition_poly_trace,
num_constraint_composition_columns,
domain,
partition_options,
)
}
}
pub fn rescue_commit(bal: u128, nonce: u128, ct_hash: &[u8; 16]) -> [BaseElement; 4] {
let mut state = build_rescue_input(bal, nonce, ct_hash);
Rescue::apply_permutation(&mut state);
[state[4], state[5], state[6], state[7]]
}
pub fn rescue_commit_amount(amount: u128, nonce: u128) -> [BaseElement; 4] {
let mut state = build_amount_rescue_input(amount, nonce);
Rescue::apply_permutation(&mut state);
[state[4], state[5], state[6], state[7]]
}
pub fn digest_to_bytes(d: &[BaseElement; 4]) -> [u8; 32] {
let mut out = [0u8; 32];
for (i, e) in d.iter().enumerate() {
out[i * 8..(i + 1) * 8].copy_from_slice(&e.as_int().to_le_bytes());
}
out
}
pub fn bytes_to_digest(b: &[u8; 32]) -> [BaseElement; 4] {
let mut d = [BaseElement::ZERO; 4];
for i in 0..4 {
let v = u64::from_le_bytes(b[i * 8..(i + 1) * 8].try_into().unwrap());
d[i] = BaseElement::new(v);
}
d
}
pub fn verify_ct_proof(proof_bytes: &[u8], pub_inputs: &CtPublicInputs) -> Result<(), String> {
if proof_bytes.is_empty() {
return Err("Empty proof".into());
}
if proof_bytes.len() > 1024 * 1024 {
return Err("Proof too large".into());
}
let proof =
Proof::from_bytes(proof_bytes).map_err(|e| format!("Proof deserialization failed: {e}"))?;
let acceptable = AcceptableOptions::OptionSet(vec![ProofOptions::new(
40,
8,
20,
FieldExtension::None,
8,
255,
BatchingMethod::Algebraic,
BatchingMethod::Algebraic,
)]);
winterfell::verify::<CtAir, Rp64_256, DefaultRandomCoin<Rp64_256>, MerkleTree<Rp64_256>>(
proof,
pub_inputs.clone(),
&acceptable,
)
.map_err(|e| format!("STARK verification failed: {e}"))
}
#[cfg(test)]
mod zk_tests {
use super::*;
fn ct_hash(ct: &[u8]) -> [u8; 16] {
let h = blake3::hash(ct);
let mut out = [0u8; 16];
out.copy_from_slice(&h.as_bytes()[..16]);
out
}
#[test]
fn prove_and_verify_valid_transfer() {
let s_old = 1_000_000_000u128;
let amount = 500_000_000u128;
let s_new = s_old - amount;
let r_old = 200_000_000u128;
let r_new = r_old + amount;
let dummy_ct = [0xABu8; 44];
let witness = CtWitness {
s_old_bal: s_old,
s_new_bal: s_new,
r_old_bal: r_old,
r_new_bal: r_new,
amount,
s_old_nonce: 0xDEADBEEF_01020304u128,
s_new_nonce: 0xDEADBEEF_05060708u128,
r_old_nonce: 0xCAFEBABE_01020304u128,
r_new_nonce: 0xCAFEBABE_05060708u128,
amt_nonce: 0x1234567890ABCDEFu128,
ct_hash_s_old: ct_hash(&dummy_ct),
ct_hash_s_new: ct_hash(&[0xCCu8; 44]),
ct_hash_r_old: ct_hash(&[0xDDu8; 44]),
ct_hash_r_new: ct_hash(&[0xEEu8; 44]),
};
let prover = CtProver::new();
let (proof_bytes, pub_inputs) = prover.prove(&witness).expect("Proof generation failed");
assert!(!proof_bytes.is_empty(), "Proof must not be empty");
assert_eq!(pub_inputs.to_elements().len(), 4 * NUM_HASHES);
verify_ct_proof(&proof_bytes, &pub_inputs).expect("Proof verification failed");
}
#[test]
fn prover_rejects_conservation_violation() {
let witness = CtWitness {
s_old_bal: 1_000u128,
s_new_bal: 600u128, r_old_bal: 200u128,
r_new_bal: 500u128,
amount: 300u128,
s_old_nonce: 1,
s_new_nonce: 2,
r_old_nonce: 3,
r_new_nonce: 4,
amt_nonce: 5,
ct_hash_s_old: [0u8; 16],
ct_hash_s_new: [0u8; 16],
ct_hash_r_old: [0u8; 16],
ct_hash_r_new: [0u8; 16],
};
let err = CtProver::new().prove(&witness).unwrap_err();
assert!(err.contains("Conservation violated"), "got: {err}");
}
#[test]
fn prover_rejects_zero_amount() {
let witness = CtWitness {
s_old_bal: 1000,
s_new_bal: 1000,
r_old_bal: 0,
r_new_bal: 0,
amount: 0,
s_old_nonce: 1,
s_new_nonce: 2,
r_old_nonce: 3,
r_new_nonce: 4,
amt_nonce: 5,
ct_hash_s_old: [0u8; 16],
ct_hash_s_new: [0u8; 16],
ct_hash_r_old: [0u8; 16],
ct_hash_r_new: [0u8; 16],
};
let err = CtProver::new().prove(&witness).unwrap_err();
assert!(err.contains("Amount must be > 0"), "got: {err}");
}
#[test]
fn prover_rejects_underflow() {
let witness = CtWitness {
s_old_bal: 100,
s_new_bal: 0,
r_old_bal: 0,
r_new_bal: 200,
amount: 200, s_old_nonce: 1,
s_new_nonce: 2,
r_old_nonce: 3,
r_new_nonce: 4,
amt_nonce: 5,
ct_hash_s_old: [0u8; 16],
ct_hash_s_new: [0u8; 16],
ct_hash_r_old: [0u8; 16],
ct_hash_r_new: [0u8; 16],
};
let err = CtProver::new().prove(&witness).unwrap_err();
assert!(err.contains("underflow"), "got: {err}");
}
#[test]
fn tampered_proof_fails_verification() {
let witness = CtWitness {
s_old_bal: 1000,
s_new_bal: 700,
r_old_bal: 200,
r_new_bal: 500,
amount: 300,
s_old_nonce: 11,
s_new_nonce: 22,
r_old_nonce: 33,
r_new_nonce: 44,
amt_nonce: 55,
ct_hash_s_old: [1u8; 16],
ct_hash_s_new: [2u8; 16],
ct_hash_r_old: [3u8; 16],
ct_hash_r_new: [4u8; 16],
};
let (proof_bytes, mut pub_inputs) = CtProver::new().prove(&witness).unwrap();
pub_inputs.amt[0] += BaseElement::ONE;
let err = verify_ct_proof(&proof_bytes, &pub_inputs).unwrap_err();
assert!(
err.contains("verification") || err.contains("failed"),
"got: {err}"
);
}
#[test]
fn rescue_commitment_is_deterministic() {
let d1 = rescue_commit(12345, 99999, &[0xABu8; 16]);
let d2 = rescue_commit(12345, 99999, &[0xABu8; 16]);
assert_eq!(d1, d2);
}
#[test]
fn rescue_commitment_differs_on_different_inputs() {
let d1 = rescue_commit(12345, 99999, &[0xABu8; 16]);
let d2 = rescue_commit(12346, 99999, &[0xABu8; 16]); assert_ne!(d1, d2);
let d3 = rescue_commit(12345, 99999, &[0xACu8; 16]); assert_ne!(d1, d3);
}
#[test]
fn digest_roundtrip() {
let d = rescue_commit(999, 777, &[0x55u8; 16]);
let b = digest_to_bytes(&d);
let d2 = bytes_to_digest(&b);
assert_eq!(d, d2);
}
}