use alloc::vec::Vec;
use core::fmt;
use generic_ec::{NonZero, Point, Scalar};
use crate::{ciphersuite::NormalizedPoint, Ciphersuite, SignerIndex};
use super::{round1::PublicCommitments, round2::SigShare, utils};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(bound = "")
)]
pub struct Signature<C: Ciphersuite> {
pub r: crate::ciphersuite::NormalizedPoint<C, Point<C::Curve>>,
pub z: Scalar<C::Curve>,
}
impl<C: Ciphersuite> Signature<C> {
pub fn verify(
&self,
public_key: &NormalizedPoint<C, NonZero<Point<C::Curve>>>,
msg: &[u8],
) -> Result<(), InvalidSignature> {
let challenge = C::compute_challenge(&self.r, public_key, msg);
let lhs = Point::generator() * self.z;
let rhs = *self.r + **public_key * challenge;
if lhs == rhs {
Ok(())
} else {
Err(InvalidSignature)
}
}
pub fn serialized_len() -> usize {
C::NORMALIZED_POINT_SIZE + C::SCALAR_SIZE
}
pub fn write_to_slice(&self, out: &mut [u8]) {
let Some(point_out) = out.get_mut(..C::NORMALIZED_POINT_SIZE) else {
return;
};
point_out.copy_from_slice(C::serialize_normalized_point(&self.r).as_ref());
let Some(scalar_out) =
out.get_mut(C::NORMALIZED_POINT_SIZE..C::NORMALIZED_POINT_SIZE + C::SCALAR_SIZE)
else {
return;
};
scalar_out.copy_from_slice(C::serialize_scalar(&self.z).as_ref());
}
pub fn read_from_slice(bytes: &[u8]) -> Option<Self> {
let r = bytes.get(..C::NORMALIZED_POINT_SIZE)?;
let z = bytes.get(C::NORMALIZED_POINT_SIZE..C::NORMALIZED_POINT_SIZE + C::SCALAR_SIZE)?;
let r = C::deserialize_normalized_point(r).ok()?;
let z = C::deserialize_scalar(z).ok()?;
Some(Self { r, z })
}
}
pub struct AggregateOptions<'a, C: Ciphersuite> {
key_info: &'a crate::key_share::KeyInfo<C::Curve>,
signers: &'a [(SignerIndex, PublicCommitments<C::Curve>, SigShare<C::Curve>)],
msg: &'a [u8],
hd_additive_shift: Option<Scalar<C::Curve>>,
taproot_merkle_root: Option<Option<[u8; 32]>>,
}
impl<'a, C: Ciphersuite> AggregateOptions<'a, C> {
pub fn new(
key_info: &'a crate::key_share::KeyInfo<C::Curve>,
signers: &'a [(SignerIndex, PublicCommitments<C::Curve>, SigShare<C::Curve>)],
msg: &'a [u8],
) -> Self {
Self {
key_info,
signers,
msg,
hd_additive_shift: None,
taproot_merkle_root: None,
}
}
#[cfg(feature = "hd-wallet")]
pub fn set_derivation_path<Index>(
self,
path: impl IntoIterator<Item = Index>,
) -> Result<
Self,
crate::key_share::HdError<<hd_wallet::NonHardenedIndex as TryFrom<Index>>::Error>,
>
where
hd_wallet::NonHardenedIndex: TryFrom<Index>,
{
self.set_derivation_path_with_algo::<C::HdAlgo, _>(path)
}
#[cfg(feature = "hd-wallet")]
pub fn set_derivation_path_with_algo<HdAlgo: hd_wallet::HdWallet<C::Curve>, Index>(
self,
path: impl IntoIterator<Item = Index>,
) -> Result<
Self,
crate::key_share::HdError<<hd_wallet::NonHardenedIndex as TryFrom<Index>>::Error>,
>
where
hd_wallet::NonHardenedIndex: TryFrom<Index>,
{
use crate::key_share::HdError;
let public_key = self
.key_info
.extended_public_key()
.ok_or(HdError::DisabledHd)?;
let additive_shift = utils::derive_additive_shift::<C::Curve, HdAlgo, _>(public_key, path)
.map_err(HdError::InvalidPath)?;
Ok(self.dangerous_set_hd_additive_shift(additive_shift))
}
#[cfg(feature = "hd-wallet")]
pub(crate) fn dangerous_set_hd_additive_shift(
mut self,
hd_additive_shift: Scalar<C::Curve>,
) -> Self {
self.hd_additive_shift = Some(hd_additive_shift);
self
}
#[cfg(feature = "taproot")]
pub fn set_taproot_tweak(
mut self,
merkle_root: Option<[u8; 32]>,
) -> Result<Self, AggregateError> {
if !C::IS_TAPROOT {
return Err(Reason::NonTaprootCiphersuite.into());
}
self.taproot_merkle_root = Some(merkle_root);
Ok(self)
}
pub fn aggregate(self) -> Result<Signature<C>, AggregateError> {
aggregate_inner::<C>(
self.key_info,
self.hd_additive_shift,
self.taproot_merkle_root,
self.signers,
self.msg,
)
}
}
pub fn aggregate<C: Ciphersuite>(
key_info: &crate::key_share::KeyInfo<C::Curve>,
signers: &[(SignerIndex, PublicCommitments<C::Curve>, SigShare<C::Curve>)],
msg: &[u8],
) -> Result<Signature<C>, AggregateError> {
AggregateOptions::new(key_info, signers, msg).aggregate()
}
fn aggregate_inner<C: Ciphersuite>(
key_info: &crate::key_share::KeyInfo<C::Curve>,
hd_additive_shift: Option<Scalar<C::Curve>>,
#[rustfmt::skip]
#[cfg_attr(not(feature = "taproot"), allow(unused_variables))]
taproot_merkle_root: Option<Option<[u8; 32]>>,
signers: &[(SignerIndex, PublicCommitments<C::Curve>, SigShare<C::Curve>)],
msg: &[u8],
) -> Result<Signature<C>, AggregateError> {
let crate::key_share::DirtyKeyInfo {
shared_public_key: pk,
vss_setup,
..
} = &**key_info;
#[allow(unused_variables)]
let key_info = ();
let pk = if let Some(additive_shift) = hd_additive_shift {
let pk = pk + Point::generator() * additive_shift;
NonZero::from_point(pk).ok_or(Reason::HdChildPkZero)?
} else {
*pk
};
let pk = C::normalize_point(pk);
#[cfg(feature = "taproot")]
let pk = if C::IS_TAPROOT {
let merkle_root = taproot_merkle_root.ok_or(Reason::MissingTaprootMerkleRoot)?;
let t = crate::signing::taproot::tweak::<C>(pk, merkle_root)
.ok_or(Reason::TaprootTweakUndefined)?;
let pk = *pk + Point::generator() * t;
let pk = NonZero::from_point(pk).ok_or(Reason::TaprootChildPkZero)?;
C::normalize_point(pk)
} else {
pk
};
let mut comm_list = signers
.iter()
.map(|(j, comm, _sig_share)| {
utils::share_preimage(vss_setup, *j)
.map(|id| (id, *comm))
.ok_or(Reason::UnknownSigner(*j))
})
.collect::<Result<Vec<_>, _>>()?;
comm_list.sort_unstable_by_key(|(i, _)| *i);
if comm_list
.iter()
.skip(1)
.zip(&comm_list)
.any(|(current, prev)| current.0 == prev.0)
{
return Err(Reason::SameSignerTwice.into());
}
let binding_factor_list = utils::compute_binding_factors::<C>(*pk, &comm_list, msg);
let group_commitment = C::normalize_point(utils::compute_group_commitment::<C>(
&comm_list,
&binding_factor_list,
));
let z = signers
.iter()
.map(|(_j, _comm, sig_share)| sig_share.0)
.sum();
let sig = Signature {
r: group_commitment,
z,
};
sig.verify(&pk, msg).map_err(|_| Reason::InvalidSig)?;
Ok(sig)
}
#[derive(Debug)]
pub struct AggregateError(Reason);
#[derive(Debug)]
enum Reason {
UnknownSigner(SignerIndex),
SameSignerTwice,
InvalidSig,
HdChildPkZero,
#[cfg(feature = "taproot")]
MissingTaprootMerkleRoot,
#[cfg(feature = "taproot")]
NonTaprootCiphersuite,
#[cfg(feature = "taproot")]
TaprootTweakUndefined,
#[cfg(feature = "taproot")]
TaprootChildPkZero,
}
impl From<Reason> for AggregateError {
fn from(err: Reason) -> Self {
Self(err)
}
}
impl fmt::Display for AggregateError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.0 {
Reason::UnknownSigner(j) => write!(f, "unknown signer {j}"),
Reason::SameSignerTwice => {
f.write_str("same signer appears more than once in the list")
}
Reason::InvalidSig => f.write_str("invalid signature"),
Reason::HdChildPkZero => f.write_str("HD derivation error: child pk is zero"),
#[cfg(feature = "taproot")]
Reason::MissingTaprootMerkleRoot => f.write_str(
"taproot merkle tree is missing: it must be specified \
for taproot ciphersuite via `SigningOptions::set_taproot_tweak`",
),
#[cfg(feature = "taproot")]
Reason::NonTaprootCiphersuite => {
f.write_str("ciphersuite doesn't support taproot tweaks")
}
#[cfg(feature = "taproot")]
Reason::TaprootTweakUndefined => f.write_str("taproot tweak is undefined"),
#[cfg(feature = "taproot")]
Reason::TaprootChildPkZero => f.write_str("taproot tweak: child pk is zero"),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for AggregateError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match &self.0 {
Reason::UnknownSigner(_)
| Reason::SameSignerTwice
| Reason::InvalidSig
| Reason::HdChildPkZero => None,
#[cfg(feature = "taproot")]
Reason::MissingTaprootMerkleRoot
| Reason::NonTaprootCiphersuite
| Reason::TaprootTweakUndefined
| Reason::TaprootChildPkZero => None,
}
}
}
#[derive(Debug)]
pub struct InvalidSignature;
impl fmt::Display for InvalidSignature {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("invalid signature")
}
}
#[cfg(feature = "std")]
impl std::error::Error for InvalidSignature {}