use super::Error;
use super::secp::{self, ProjectivePoint};
use crate::tss::PartyId;
use crate::tss::TssError;
use crate::tss::b64::B64Bytes;
use crate::tss::bigint::be_to_decimal;
use purecrypto::hash::sha256;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
pub(crate) fn peer_key_str(p: &PartyId) -> String {
be_to_decimal(strip(&p.key))
}
pub(crate) fn strip(b: &[u8]) -> &[u8] {
let start = b.iter().position(|&x| x != 0).unwrap_or(b.len());
&b[start..]
}
pub(crate) fn commit_digest(tag: &str, dealer: &PartyId, vs_bytes: &[Vec<u8>]) -> Vec<u8> {
let mut data = Vec::new();
data.extend_from_slice(tag.as_bytes());
data.push(b'|');
data.extend_from_slice(strip(&dealer.key));
data.push(b'|');
data.extend_from_slice(&(vs_bytes.len() as u64).to_le_bytes());
for c in vs_bytes {
data.extend_from_slice(&(c.len() as u64).to_le_bytes());
data.extend_from_slice(c);
}
sha256(&data).to_vec()
}
#[derive(Serialize, Deserialize)]
pub(crate) struct EchoMsg {
#[serde(rename = "digests")]
pub digests: HashMap<String, B64Bytes>,
}
pub(crate) fn verify_echoes(
my_digests: &HashMap<String, Vec<u8>>,
self_key: &str,
echoers: &[PartyId],
msgs: &[EchoMsg],
all_parties: &[PartyId],
source: &str,
) -> Result<(), Error> {
let by_key: HashMap<String, &PartyId> =
all_parties.iter().map(|p| (peer_key_str(p), p)).collect();
let max_digests = all_parties.len();
let fail = |cause: String, culprit: &PartyId| -> Error {
Error::Tss(Box::new(TssError::new(
cause,
source,
0,
None,
vec![culprit.clone()],
)))
};
for (n, echoer) in echoers.iter().enumerate() {
let ec = &msgs[n];
if ec.digests.is_empty() {
return Err(fail(
format!("{source} echo from {echoer} is empty"),
echoer,
));
}
if ec.digests.len() > max_digests {
return Err(fail(
format!(
"{source} echo from {echoer} has {} digests (max {max_digests})",
ec.digests.len()
),
echoer,
));
}
let echoer_key = peer_key_str(echoer);
for p in all_parties {
let k = peer_key_str(p);
if k == echoer_key {
continue;
}
if !ec.digests.contains_key(&k) {
return Err(fail(
format!(
"echo from {echoer} omitted dealer {p} — would enable an equivocation cover-up"
),
echoer,
));
}
}
for (dealer_key, their_digest) in &ec.digests {
if *dealer_key == echoer_key {
return Err(fail(
format!("echo from {echoer} contains a self-entry (protocol violation)"),
echoer,
));
}
let Some(mine) = my_digests.get(dealer_key) else {
let dealer = by_key.get(dealer_key).copied();
return Err(fail(
format!(
"echo from {echoer} mentions unknown dealer (key={dealer_key}){}",
dealer.map(|d| format!(" {d}")).unwrap_or_default()
),
echoer,
));
};
if mine == &their_digest.0 {
continue;
}
if *dealer_key == self_key {
return Err(fail(
format!("echo from {echoer} disagrees with my canonical commitments"),
echoer,
));
}
match by_key.get(dealer_key).copied() {
Some(dealer) => {
return Err(fail(
format!(
"echo from {echoer} reports a different commitment for {dealer} than I received"
),
dealer,
));
}
None => {
return Err(Error::Validation(format!(
"{source} echo from {echoer} disagrees on unmapped dealer {dealer_key}"
)));
}
}
}
}
Ok(())
}
pub(crate) fn flatten_point_xy(pts: &[ProjectivePoint]) -> Vec<B64Bytes> {
let mut out = Vec::with_capacity(2 * pts.len());
for p in pts {
let (x, y) = secp::affine_be(p);
out.push(B64Bytes(x));
out.push(B64Bytes(y));
}
out
}
pub(crate) fn unflatten_point_xy(flat: &[B64Bytes]) -> Result<Vec<ProjectivePoint>, Error> {
if flat.len() % 2 != 0 {
return Err(Error::Validation(format!(
"flat point slice length {} not even",
flat.len()
)));
}
let mut out = Vec::with_capacity(flat.len() / 2);
for (i, pair) in flat.chunks_exact(2).enumerate() {
let p = point_from_be_xy(&pair[0].0, &pair[1].0)
.ok_or_else(|| Error::Validation(format!("point [{i}] off-curve")))?;
out.push(p);
}
Ok(out)
}
pub(crate) fn point_from_be_xy(x_be: &[u8], y_be: &[u8]) -> Option<ProjectivePoint> {
let x = strip(x_be);
let y = strip(y_be);
if x.len() > 32 || y.len() > 32 {
return None;
}
let mut sec1 = [0u8; 65];
sec1[0] = 0x04;
sec1[1 + (32 - x.len())..33].copy_from_slice(x);
sec1[33 + (32 - y.len())..65].copy_from_slice(y);
secp::from_sec1(&sec1)
}
pub(crate) fn other_parties(parties: &[PartyId], self_id: &PartyId) -> Vec<PartyId> {
parties
.iter()
.filter(|p| p.cmp_key(self_id) != std::cmp::Ordering::Equal)
.cloned()
.collect()
}
pub(crate) fn pair_base_sid(ssid: &[u8], a: &[u8], b: &[u8], ext_sender: &[u8]) -> Vec<u8> {
let (a, b, ext) = (strip(a), strip(b), strip(ext_sender));
let mut data = Vec::new();
data.extend_from_slice(ssid);
data.push(b'|');
let (lo, hi) = if be_le(a, b) { (a, b) } else { (b, a) };
data.extend_from_slice(lo);
data.push(b'|');
data.extend_from_slice(hi);
data.push(b'|');
data.extend_from_slice(ext);
sha256(&data).to_vec()
}
fn be_le(a: &[u8], b: &[u8]) -> bool {
if a.len() != b.len() {
return a.len() < b.len();
}
a <= b
}