use alloc::{borrow::Cow, vec::Vec};
use core::{fmt, iter};
use key_share::VssSetup;
use generic_ec::{Curve, NonZero, Point, Scalar, SecretScalar};
use crate::{
ciphersuite::{Ciphersuite, NormalizedPoint},
KeyShare, SignerIndex,
};
use super::{
round1::{PublicCommitments, SecretNonces},
utils,
};
#[derive(Debug, Copy, Clone)]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(bound = "")
)]
pub struct SigShare<E: Curve>(pub Scalar<E>);
pub struct SigningOptions<'a, C: Ciphersuite> {
key_share: &'a KeyShare<C::Curve>,
nonce: SecretNonces<C::Curve>,
msg: &'a [u8],
signers: &'a [(SignerIndex, PublicCommitments<C::Curve>)],
hd_additive_shift: Option<Scalar<C::Curve>>,
taproot_merkle_root: Option<Option<[u8; 32]>>,
}
impl<'a, C: Ciphersuite> SigningOptions<'a, C> {
pub fn new(
key_share: &'a KeyShare<C::Curve>,
nonce: SecretNonces<C::Curve>,
msg: &'a [u8],
signers: &'a [(SignerIndex, PublicCommitments<C::Curve>)],
) -> Self {
Self {
key_share,
nonce,
msg,
signers,
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_share
.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, SigningError> {
if !C::IS_TAPROOT {
return Err(Reason::NonTaprootCiphersuite.into());
}
self.taproot_merkle_root = Some(merkle_root);
Ok(self)
}
pub fn sign(self) -> Result<SigShare<C::Curve>, SigningError> {
sign_inner::<C>(
self.key_share,
self.hd_additive_shift,
self.taproot_merkle_root,
self.nonce,
self.msg,
self.signers,
)
}
}
pub fn sign<C: Ciphersuite>(
key_share: &KeyShare<C::Curve>,
nonce: SecretNonces<C::Curve>,
msg: &[u8],
signers: &[(SignerIndex, PublicCommitments<C::Curve>)],
) -> Result<SigShare<C::Curve>, SigningError> {
SigningOptions::<C>::new(key_share, nonce, msg, signers).sign()
}
fn sign_inner<C: Ciphersuite>(
key_share: &KeyShare<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]>>,
nonce: SecretNonces<C::Curve>,
msg: &[u8],
signers: &[(SignerIndex, PublicCommitments<C::Curve>)],
) -> Result<SigShare<C::Curve>, SigningError> {
let t = key_share.min_signers();
let crate::key_share::DirtyKeyShare {
i,
key_info:
crate::key_share::DirtyKeyInfo {
shared_public_key: pk,
vss_setup,
..
},
x,
} = &**key_share;
#[allow(unused_variables)]
let key_share = ();
let (x, pk) = if let Some(additive_shift) = hd_additive_shift {
apply_additive_shift(*i, vss_setup, Cow::Borrowed(x), *pk, additive_shift)
.map_err(Reason::HdShift)?
} else {
(Cow::Borrowed(x), *pk)
};
let (x, pk) = normalize_key_share(x, pk);
#[cfg(feature = "taproot")]
let (x, 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 (x, pk) =
apply_additive_shift(*i, vss_setup, x, *pk, t).map_err(Reason::TaprootShift)?;
normalize_key_share(x, pk)
} else {
(x, pk)
};
if signers.len() < usize::from(t) {
return Err(Reason::TooFewSigners {
min_signers: t,
n: signers.len(),
}
.into());
}
let signer_id = utils::share_preimage(vss_setup, *i).ok_or(Bug::RetrieveOwnShareId)?;
let mut comm_list = signers
.iter()
.map(|(j, comm)| {
if i == j && nonce.public_commitments() != *comm {
Err(Reason::NoncesDontMatchComm)
} else {
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);
let mut own_index_found = None;
for (signer_index, ((j, _), com_j_minus_one)) in comm_list
.iter()
.zip(iter::once(None).chain(comm_list.iter().map(Some)))
.enumerate()
{
if *j == signer_id {
own_index_found = Some(signer_index);
}
if let Some((j_minus_one, _)) = com_j_minus_one {
if j_minus_one == j {
return Err(Reason::SameSignerTwice.into());
}
}
}
let Some(i) = own_index_found else {
return Err(Reason::SignerNotInList.into());
};
let binding_factor_list = utils::compute_binding_factors::<C>(*pk, &comm_list, msg);
let binding_factor = binding_factor_list.get(i).ok_or(Bug::OwnBindingFactor)?.1;
debug_assert_eq!(binding_factor_list[i].0, signer_id);
let group_commitment = utils::compute_group_commitment::<C>(&comm_list, &binding_factor_list);
let nonce_share = nonce.hiding_nonce + (nonce.binding_nonce * binding_factor);
let (group_commitment, nonce_share) = match NormalizedPoint::try_normalize(group_commitment) {
Ok(group_commitment) => {
(group_commitment, nonce_share)
}
Err(neg_group_commitment) => {
(neg_group_commitment, -nonce_share)
}
};
let signers_list = comm_list.iter().map(|(i, _)| *i).collect::<Vec<_>>();
let lambda_i = if vss_setup.is_some() {
derive_interpolating_value(&signers_list, &signer_id)
.ok_or(Reason::DeriveInterpolationValue)?
} else {
Scalar::one()
};
let challenge = C::compute_challenge(&group_commitment, &pk, msg);
Ok(SigShare(nonce_share + (lambda_i * &*x * challenge)))
}
fn derive_interpolating_value<E: Curve>(
signers_list: &[NonZero<Scalar<E>>],
x_i: &NonZero<Scalar<E>>,
) -> Option<Scalar<E>> {
debug_assert!(
utils::is_sorted(signers_list),
"signers list must be sorted"
);
let mut x_i_observed = false;
let mut num = Scalar::one();
let mut denom = NonZero::<Scalar<E>>::one();
for (x_j, x_j_minus_one) in signers_list
.iter()
.zip(iter::once(None).chain(signers_list.iter().map(Some)))
{
if Some(x_j) == x_j_minus_one {
return None;
}
let Some(substraction) = NonZero::from_scalar(x_j - x_i) else {
x_i_observed = true;
continue;
};
num *= x_j.as_ref();
denom *= substraction;
}
if !x_i_observed {
return None;
}
Some(num * denom.invert())
}
fn apply_additive_shift<'a, E: Curve>(
i: u16,
vss_setup: &Option<VssSetup<E>>,
x: Cow<'a, NonZero<SecretScalar<E>>>,
pk: NonZero<Point<E>>,
additive_shift: Scalar<E>,
) -> Result<(Cow<'a, NonZero<SecretScalar<E>>>, NonZero<Point<E>>), ApplyAdditiveShiftError> {
let pk = pk + Point::generator() * additive_shift;
let pk = NonZero::from_point(pk).ok_or(ApplyAdditiveShiftError::ChildPkZero)?;
if vss_setup.is_some() || i == 0 {
let x = SecretScalar::new(&mut (&*x + additive_shift));
let x = NonZero::from_secret_scalar(x).ok_or(ApplyAdditiveShiftError::ChildShareZero)?;
Ok((Cow::Owned(x), pk))
} else {
Ok((x, pk))
}
}
fn normalize_key_share<C: Ciphersuite>(
x: Cow<NonZero<SecretScalar<C::Curve>>>,
pk: NonZero<Point<C::Curve>>,
) -> (
Cow<NonZero<SecretScalar<C::Curve>>>,
NormalizedPoint<C, NonZero<Point<C::Curve>>>,
) {
match NormalizedPoint::<C, _>::try_normalize(pk) {
Ok(pk) => {
(x, pk)
}
Err(neg_pk) => {
(Cow::Owned(-&*x), neg_pk)
}
}
}
#[derive(Debug)]
pub struct SigningError(Reason);
#[derive(Debug)]
#[cfg_attr(not(feature = "std"), allow(dead_code))]
enum Reason {
#[cfg(feature = "taproot")]
MissingTaprootMerkleRoot,
#[cfg(feature = "taproot")]
NonTaprootCiphersuite,
TooFewSigners {
min_signers: u16,
n: usize,
},
UnknownSigner(SignerIndex),
SameSignerTwice,
SignerNotInList,
NoncesDontMatchComm,
DeriveInterpolationValue,
HdShift(ApplyAdditiveShiftError),
#[cfg(feature = "taproot")]
TaprootTweakUndefined,
#[cfg(feature = "taproot")]
TaprootShift(ApplyAdditiveShiftError),
Bug(Bug),
}
#[derive(Debug)]
enum ApplyAdditiveShiftError {
ChildPkZero,
ChildShareZero,
}
#[derive(Debug)]
enum Bug {
RetrieveOwnShareId,
OwnBindingFactor,
}
impl fmt::Display for SigningError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.0 {
#[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"),
Reason::TooFewSigners { min_signers, n } => write!(
f,
"signers list contains {n} signers, although at \
least {min_signers} must take part in the signing"
),
Reason::UnknownSigner(j) => write!(f, "unknown signer with index {j}"),
Reason::SameSignerTwice => f.write_str(
"same signer appears more than once in the list \
of signers",
),
Reason::SignerNotInList => f.write_str("signer not in the list of participants"),
Reason::NoncesDontMatchComm => f.write_str("nonces don't match signer commitments"),
Reason::DeriveInterpolationValue => f.write_str(
"invalid list of signers: either this signer is \
not in the list, or some signer in the list is \
mentioned more than once",
),
Reason::HdShift(_) => f.write_str("HD derivation: apply additive shift"),
#[cfg(feature = "taproot")]
Reason::TaprootShift(_) => f.write_str("taproot tweak: apply additive shift"),
#[cfg(feature = "taproot")]
Reason::TaprootTweakUndefined => f.write_str("taproot tweak is undefined"),
Reason::Bug(_) => f.write_str("bug occurred"),
}
}
}
impl fmt::Display for ApplyAdditiveShiftError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::ChildPkZero => f.write_str("HD wallet derivation: child pk is zero"),
Self::ChildShareZero => f.write_str("HD wallet derivation: child share is zero"),
}
}
}
impl fmt::Display for Bug {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Bug::RetrieveOwnShareId => f.write_str("retrieve own share id"),
Bug::OwnBindingFactor => f.write_str("retrieve own binding factor"),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for SigningError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match &self.0 {
Reason::TooFewSigners { .. }
| Reason::UnknownSigner(_)
| Reason::NoncesDontMatchComm
| Reason::DeriveInterpolationValue
| Reason::SameSignerTwice
| Reason::SignerNotInList => None,
#[cfg(feature = "taproot")]
Reason::MissingTaprootMerkleRoot
| Reason::NonTaprootCiphersuite
| Reason::TaprootTweakUndefined => None,
#[cfg(feature = "taproot")]
Reason::TaprootShift(err) => Some(err),
Reason::Bug(bug) => Some(bug),
Reason::HdShift(err) => Some(err),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for ApplyAdditiveShiftError {}
#[cfg(feature = "std")]
impl std::error::Error for Bug {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Bug::RetrieveOwnShareId | Bug::OwnBindingFactor => None,
}
}
}
impl From<Reason> for SigningError {
fn from(err: Reason) -> Self {
SigningError(err)
}
}
impl From<Bug> for SigningError {
fn from(err: Bug) -> Self {
SigningError(Reason::Bug(err))
}
}