use core::fmt::{Debug, Display, Formatter};
use digest::Digest;
use serde::{Deserialize, Serialize};
use sha2::Sha512;
use std::collections::HashMap;
use crate::{
encoding::Marshaling,
group::{HashFactory, PointCanCheckCanonicalAndSmallOrder, ScalarCanCheckCanonical},
share::poly::{self, PolyError, PriShare, PubPoly},
sign::{eddsa, error::SignatureError, schnorr},
Group, Point, Random, Scalar,
};
use thiserror::Error;
pub trait Suite: Group + HashFactory + Random + Clone {}
pub trait DistKeyShare<GROUP: Group>: Clone {
fn pri_share(&self) -> PriShare<<GROUP::POINT as Point>::SCALAR>;
fn commitments(&self) -> Vec<GROUP::POINT>;
}
#[derive(Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct DSS<SUITE: Suite, DKS: DistKeyShare<SUITE>> {
suite: SUITE,
pub(crate) secret: <SUITE::POINT as Point>::SCALAR,
pub public: SUITE::POINT,
pub index: usize,
pub participants: Vec<SUITE::POINT>,
pub t: usize,
long: DKS,
random: DKS,
long_poly: PubPoly<SUITE>,
random_poly: PubPoly<SUITE>,
pub msg: Vec<u8>,
pub partials: Vec<Option<PriShare<<SUITE::POINT as Point>::SCALAR>>>,
partials_idx: HashMap<usize, bool>,
signed: bool,
pub session_id: Vec<u8>,
}
impl<SUITE: Suite, DKS: DistKeyShare<SUITE> + Debug> Debug for DSS<SUITE, DKS> {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
f.debug_struct("DSS")
.field("suite", &self.suite)
.field("public", &self.public)
.field("index", &self.index)
.field("participants", &self.participants)
.field("t", &self.t)
.field("long_poly", &self.long_poly)
.field("random_poly", &self.random_poly)
.field("msg", &self.msg)
.field("partials", &self.partials)
.field("partials_idx", &self.partials_idx)
.field("signed", &self.signed)
.field("session_id", &self.session_id)
.finish()
}
}
impl<SUITE: Suite, DKS: DistKeyShare<SUITE> + Display> Display for DSS<SUITE, DKS> {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
write!(
f,
"DSS( suite: {}, public_key: {}, index: {},",
self.suite, self.public, self.index
)?;
write!(f, " participants: [")?;
let participants = self
.participants
.iter()
.map(|c| c.to_string())
.collect::<Vec<_>>()
.join(",");
write!(f, "{}],", participants)?;
write!(
f,
"threshold: {}, long_polynomial: {}, random_polynomial: {}, message: 0x{},",
self.t,
self.long_poly,
self.random_poly,
hex::encode(&self.msg)
)?;
write!(f, " partials: [")?;
let partials = self
.partials
.iter()
.map(|c| match c {
Some(p) => "Some(".to_string() + &p.to_string() + ")",
None => "None".to_string(),
})
.collect::<Vec<_>>()
.join(",");
write!(f, "{}],", partials)?;
write!(f, " partials_indexes: [")?;
let partials_indexes = self
.partials_idx
.iter()
.map(|c| "(".to_string() + &c.0.to_string() + ", " + &c.1.to_string() + ")")
.collect::<Vec<_>>()
.join(", ");
write!(f, "{}],", partials_indexes)?;
write!(
f,
"signed: {}, session_id: 0x{} )",
self.signed,
hex::encode(&self.session_id)
)
}
}
#[derive(Clone, Serialize, Deserialize, Debug, Default)]
pub struct PartialSig<SUITE: Suite> {
pub partial: PriShare<<SUITE::POINT as Point>::SCALAR>,
pub session_id: Vec<u8>,
pub signature: Vec<u8>,
}
impl<SUITE: Suite> PartialSig<SUITE> {
pub fn hash(&self, s: SUITE) -> Result<Vec<u8>, DSSError> {
let mut h = s.hash();
h.update(
&self
.partial
.hash(s)
.map_err(DSSError::PartialSignatureHash)?,
);
h.update(&self.session_id);
Ok(h.finalize().to_vec())
}
}
pub fn new_dss<SUITE: Suite, DKS: DistKeyShare<SUITE>>(
suite: SUITE,
secret: &<SUITE::POINT as Point>::SCALAR,
participants: &[SUITE::POINT],
long: &DKS,
random: &DKS,
msg: &[u8],
t: usize,
) -> Result<DSS<SUITE, DKS>, DSSError> {
let public = suite.point().mul(secret, None);
let mut i = 0;
let mut found = false;
for (j, p) in participants.iter().enumerate() {
if p.eq(&public) {
found = true;
i = j;
break;
}
}
if !found {
return Err(DSSError::PublicKeyNotInParticipants);
}
Ok(DSS::<SUITE, DKS> {
suite: suite.clone(),
secret: secret.clone(),
public,
index: i,
participants: participants.to_vec(),
long: long.clone(),
long_poly: PubPoly::new(&suite, Some(suite.point().base()), &long.commitments()),
random: random.clone(),
random_poly: PubPoly::new(&suite, Some(suite.point().base()), &random.commitments()),
msg: msg.to_vec(),
t,
partials_idx: HashMap::new(),
session_id: session_id(suite, long, random)?,
partials: Vec::new(),
signed: false,
})
}
impl<SUITE: Suite, DKS: DistKeyShare<SUITE>> DSS<SUITE, DKS> {
pub fn partial_sig(&mut self) -> Result<PartialSig<SUITE>, DSSError> {
let alpha = self.long.pri_share().v;
let beta = self.random.pri_share().v;
let hash = self.hash_sig()?;
let right = hash * alpha;
let mut ps = PartialSig {
partial: PriShare {
v: right + beta,
i: self.index,
},
session_id: self.session_id.clone(),
signature: Vec::new(),
};
let msg = ps.hash(self.suite.clone())?;
ps.signature = schnorr::sign(&self.suite, &self.secret, &msg)?;
if !self.signed {
self.partials_idx.insert(self.index, true);
self.partials.push(Some(ps.partial.clone()));
self.signed = true
}
Ok(ps)
}
pub fn process_partial_sig(&mut self, ps: PartialSig<SUITE>) -> Result<(), DSSError>
where
<SUITE::POINT as Point>::SCALAR: ScalarCanCheckCanonical,
SUITE::POINT: PointCanCheckCanonicalAndSmallOrder,
{
let public = find_pub(&self.participants, ps.partial.i)?;
let msg = ps.hash(self.suite.clone())?;
schnorr::verify(self.suite.clone(), &public, &msg, &ps.signature)?;
if ps.session_id != self.session_id {
return Err(DSSError::InvalidSessionId);
}
if self.partials_idx.contains_key(&ps.partial.i) {
return Err(DSSError::PartialAlreadyReceived);
}
let hash = self.hash_sig()?;
let idx = ps.partial.i;
let rand_share = self.random_poly.eval(idx);
let long_share = self.long_poly.eval(idx);
let mut right = self.suite.point().mul(&hash, Some(&long_share.v));
let right_clone = right.clone();
right = right.add(&rand_share.v, &right_clone);
let left = self.suite.point().mul(&ps.partial.v, None);
if !left.eq(&right) {
return Err(DSSError::InvalidPartialSignature);
}
self.partials_idx.insert(ps.partial.i, true);
self.partials.push(Some(ps.partial));
Ok(())
}
pub fn enough_partial_sig(&self) -> bool {
self.partials.len() >= self.t
}
pub fn signature(&self) -> Result<Vec<u8>, DSSError> {
if !self.enough_partial_sig() {
return Err(DSSError::NotEnoughPartials);
}
let gamma = poly::recover_secret(
self.suite.clone(),
&self.partials,
self.t,
self.participants.len(),
)
.map_err(DSSError::RecoverSecretError)?;
let mut buff = Vec::new();
self.random.commitments()[0]
.marshal_to(&mut buff)
.map_err(SignatureError::MarshallingError)?;
gamma
.marshal_to(&mut buff)
.map_err(SignatureError::MarshallingError)?;
Ok(buff)
}
fn hash_sig(&self) -> Result<<SUITE::POINT as Point>::SCALAR, DSSError> {
let mut h = Sha512::new();
self.random.commitments()[0]
.marshal_to(&mut h)
.map_err(SignatureError::MarshallingError)?;
self.long.commitments()[0]
.marshal_to(&mut h)
.map_err(SignatureError::MarshallingError)?;
h.update(self.msg.clone());
Ok(self.suite.scalar().set_bytes(&h.finalize()))
}
}
pub fn verify<POINT: Point>(public: &POINT, msg: &[u8], sig: &[u8]) -> Result<(), SignatureError> {
eddsa::verify(public, msg, sig)
}
fn find_pub<POINT: Point>(list: &[POINT], i: usize) -> Result<POINT, DSSError> {
if i >= list.len() {
return Err(DSSError::InvalidIndex);
}
Ok(list[i].clone())
}
fn session_id<SUITE: Suite, DKS: DistKeyShare<SUITE>>(
s: SUITE,
a: &DKS,
b: &DKS,
) -> Result<Vec<u8>, DSSError> {
let mut h = s.hash();
for p in a.commitments() {
p.marshal_to(&mut h)
.map_err(SignatureError::MarshallingError)?;
}
for p in b.commitments() {
p.marshal_to(&mut h)
.map_err(SignatureError::MarshallingError)?;
}
Ok(h.finalize().to_vec())
}
#[derive(Error, Debug)]
pub enum DSSError {
#[error("signature error")]
SignatureError(#[from] SignatureError),
#[error("public key not found in the list of participants")]
PublicKeyNotInParticipants,
#[error("invalid index")]
InvalidIndex,
#[error("not enough partial signature to sign")]
NotEnoughPartials,
#[error("could not recover secret")]
RecoverSecretError(PolyError),
#[error("could not hash partial signature")]
PartialSignatureHash(PolyError),
#[error("session id do not match")]
InvalidSessionId,
#[error("partial signature already received from peer")]
PartialAlreadyReceived,
#[error("partial signature not valid")]
InvalidPartialSignature,
}