ghost-node/pallets/exodus/src/impls/frost.rs
Uncle Stretch fd8e52b520
adopt BIP-340 for all Secp256k1 networks in EXODUS
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2026-09-09 03:41:28 +03:00

447 lines
19 KiB
Rust

use ghost_traits::exodus::{
IdentifierConverter, MerkleTreeBuilder,
FlexibleRoundOptimizedSchnorrThresholdSignature,
};
use sp_std::{
vec::Vec, result::Result,
collections::{btree_set::BTreeSet, btree_map::BTreeMap},
};
use rand_chacha::rand_core::{CryptoRng, RngCore};
use ghost_helpers::networks::NetworkCurve;
use crate::{AuthIndex, ExodusHash, ExodusError};
impl FlexibleRoundOptimizedSchnorrThresholdSignature<AuthIndex, ExodusError> for NetworkCurve {
type Packages<'a> = BTreeMap<AuthIndex, &'a [u8]>;
type NoncePackages<'a> = BTreeMap<AuthIndex, (&'a [u8], &'a [u8])>;
fn header_bytes_len(&self) -> usize { 5 }
fn signature_bytes_len(&self) -> usize {
match self {
NetworkCurve::Secp256k1 => 64,
NetworkCurve::Ed25519 => 64,
}
}
fn element_bytes_len(&self) -> usize {
match self {
NetworkCurve::Secp256k1 => 33,
NetworkCurve::Ed25519 => 32,
}
}
fn scalar_bytes_len(&self) -> usize {
match self {
NetworkCurve::Secp256k1 => 32,
NetworkCurve::Ed25519 => 32,
}
}
fn verify_signature_share(
&self,
index: AuthIndex,
participants_iter: impl Iterator<Item = AuthIndex>,
signature_share_bytes: &[u8],
binding_factor_bytes: &[u8],
nonce_hiding_bytes: &[u8],
nonce_binding_bytes: &[u8],
group_commitment_bytes: &[u8],
verifying_share_bytes: &[u8],
verifying_key_bytes: &[u8],
message: &[u8],
) -> Result<(), ExodusError> {
with_ciphersuite!(self, |f| {
type InnerGroup = <Ciphersuite as frost_core::Ciphersuite>::Group;
type InnerField = <InnerGroup as frost_core::Group>::Field;
let identifier = Self::non_zero_index(index)?
.try_into()
.map_err(|_| ExodusError::InvalidParticipantId)?;
let identifier_set = participants_iter
.filter_map(|auth_index| {
let non_zero_index = Self::non_zero_index(auth_index).ok()?;
non_zero_index.try_into().ok()
})
.collect::<BTreeSet<f::Identifier>>();
let lambda_i =
frost_core::compute_lagrange_coefficient(&identifier_set, None, identifier)
.map_err(|err| match err {
f::Error::IncorrectNumberOfIdentifiers => ExodusError::IncorrectNumberOfIdentifiers,
f::Error::UnknownIdentifier => ExodusError::UnknownIdentifier,
f::Error::DuplicatedIdentifier => ExodusError::DuplicatedIdentifier,
_ => ExodusError::Unknown,
})?;
let signature_share =
f::round2::SignatureShare::deserialize(signature_share_bytes)
.map_err(|_| ExodusError::DeserializationError)?;
let group_commitment_serialization =
<InnerGroup as frost_core::Group>::Serialization::try_from(group_commitment_bytes.as_ref())
.map_err(|_| ExodusError::DeserializationError)?;
let mut group_commitment_element =
<InnerGroup as frost_core::Group>::deserialize(&group_commitment_serialization)
.map_err(|_| ExodusError::DeserializationError)?;
let binding_factor_serialization =
<InnerField as frost_core::Field>::Serialization::try_from(binding_factor_bytes.as_ref())
.map_err(|_| ExodusError::DeserializationError)?;
let binding_factor_scalar =
<InnerField as frost_core::Field>::deserialize(&binding_factor_serialization)
.map_err(|_| ExodusError::DeserializationError)?;
let verifying_share =
f::keys::VerifyingShare::deserialize(verifying_share_bytes)
.map_err(|_| ExodusError::DeserializationError)?;
let verifying_key = f::VerifyingKey::deserialize(verifying_key_bytes)
.map_err(|_| ExodusError::DeserializationError)?;
let hiding_element_serialization =
<InnerGroup as frost_core::Group>::Serialization::try_from(nonce_hiding_bytes.as_ref())
.map_err(|_| ExodusError::DeserializationError)?;
let hiding_element =
<InnerGroup as frost_core::Group>::deserialize(&hiding_element_serialization)
.map_err(|_| ExodusError::DeserializationError)?;
let binding_element_serialization =
<InnerGroup as frost_core::Group>::Serialization::try_from(nonce_binding_bytes.as_ref())
.map_err(|_| ExodusError::DeserializationError)?;
let binding_element =
<InnerGroup as frost_core::Group>::deserialize(&binding_element_serialization)
.map_err(|_| ExodusError::DeserializationError)?;
let mut r_i_element = hiding_element + (binding_element * binding_factor_scalar);
let group_commitment_wrapper =
frost_core::GroupCommitment::<Ciphersuite>::from_element(group_commitment_element);
if !group_commitment_wrapper.is_normalized() {
r_i_element = -r_i_element;
group_commitment_element = -group_commitment_element;
}
let challenge = <Ciphersuite as frost_core::Ciphersuite>::challenge(
&group_commitment_element,
&verifying_key,
message,
).map_err(|err| match err {
f::Error::SerializationError => ExodusError::SerializationError,
_ => ExodusError::Unknown,
})?;
let commitment_share =
frost_core::round1::GroupCommitmentShare::from_element(r_i_element);
signature_share.verify(
identifier,
&commitment_share,
&verifying_share,
lambda_i,
&challenge,
).map_err(|err| match err {
f::Error::InvalidSignatureShare { .. } => {
ExodusError::InvalidSignatureShare
},
_ => ExodusError::Unknown,
})
})
}
fn accumulate_signature_scalar(
&self,
signature_scalar_bytes: &[u8],
signature_share_bytes: &[u8],
) -> Result<Vec<u8>, ExodusError> {
with_ciphersuite!(self, |f| {
type InnerGroup = <Ciphersuite as frost_core::Ciphersuite>::Group;
type InnerField = <InnerGroup as frost_core::Group>::Field;
let signature_scalar_serialization =
<InnerField as frost_core::Field>::Serialization::try_from(signature_scalar_bytes)
.map_err(|_| ExodusError::DeserializationError)?;
let mut signature_scalar =
<InnerField as frost_core::Field>::deserialize(&signature_scalar_serialization)
.map_err(|_| ExodusError::DeserializationError)?;
let signature_share_serialization =
<InnerField as frost_core::Field>::Serialization::try_from(signature_share_bytes)
.map_err(|_| ExodusError::DeserializationError)?;
let signature_share =
<InnerField as frost_core::Field>::deserialize(&signature_share_serialization)
.map_err(|_| ExodusError::DeserializationError)?;
signature_scalar += signature_share;
Ok(<InnerField as frost_core::Field>::serialize(&signature_scalar).to_vec())
})
}
fn is_signature_valid(
&self,
verifying_key_bytes: &[u8],
r_element_bytes: &[u8],
z_scalar_bytes: &[u8],
message: &[u8],
) -> Result<(), ExodusError> {
with_ciphersuite!(self, |f| {
type InnerGroup = <Ciphersuite as frost_core::Ciphersuite>::Group;
type InnerField = <InnerGroup as frost_core::Group>::Field;
let verifying_key = f::VerifyingKey::deserialize(verifying_key_bytes)
.map_err(|_| ExodusError::DeserializationError)?;
let r_element_serialization =
<InnerGroup as frost_core::Group>::Serialization::try_from(r_element_bytes)
.map_err(|_| ExodusError::DeserializationError)?;
let r_element =
<InnerGroup as frost_core::Group>::deserialize(&r_element_serialization)
.map_err(|_| ExodusError::DeserializationError)?;
let z_scalar_serialization =
<InnerField as frost_core::Field>::Serialization::try_from(z_scalar_bytes)
.map_err(|_| ExodusError::DeserializationError)?;
let z_scalar =
<InnerField as frost_core::Field>::deserialize(&z_scalar_serialization)
.map_err(|_| ExodusError::DeserializationError)?;
let signature = f::Signature::new(r_element, z_scalar);
verifying_key.verify(message, &signature)
.map_err(|_| ExodusError::InvalidSignature)
})
}
fn generate_nonce_commitment<R: CryptoRng + RngCore>(
&self,
secret_key_package_bytes: &[u8],
mut rng: R
) -> Result<(Vec<u8>, Vec<u8>, Vec<u8>), ExodusError> {
with_ciphersuite!(self, |f| {
let signing_share =
f::keys::KeyPackage::deserialize(secret_key_package_bytes)
.map(|key_package| *key_package.signing_share())
.map_err(|_| ExodusError::DeserializationError)?;
let (signing_nonce, signing_commitment) =
f::round1::commit(&signing_share, &mut rng);
let signing_nonce_bytes = signing_nonce.serialize()
.map_err(|_| ExodusError::SerializationError)?;
let hiding_nonce_bytes = signing_commitment.hiding()
.serialize()
.map_err(|_| ExodusError::SerializationError)?;
let binding_nonce_bytes = signing_commitment.binding()
.serialize()
.map_err(|_| ExodusError::SerializationError)?;
Ok((signing_nonce_bytes, hiding_nonce_bytes, binding_nonce_bytes))
})
}
fn generate_group_commitment(
&self,
authority_index: AuthIndex,
nonce_commitments_bytes: &Self::NoncePackages<'_>,
verifying_key_bytes: &[u8],
message: &[u8],
) -> Result<(Vec<u8>, Vec<u8>, Vec<ExodusHash>, ExodusHash), ExodusError> {
with_ciphersuite!(self, |f| {
type InnerGroup = <Ciphersuite as frost_core::Ciphersuite>::Group;
type InnerField = <InnerGroup as frost_core::Group>::Field;
let verifying_key = f::VerifyingKey::deserialize(verifying_key_bytes)
.map_err(|_| ExodusError::DeserializationError)?;
let signing_commitments = nonce_commitments_bytes
.iter()
.filter_map(|(&index, &(hiding_bytes, binding_bytes))| {
let non_zero_index = Self::non_zero_index(index).ok()?;
let identifier = non_zero_index.try_into().ok()?;
let hiding_serialization = <InnerGroup as frost_core::Group>::Serialization::try_from(hiding_bytes.as_ref()).ok()?;
let hiding_nonce = f::round1::NonceCommitment::deserialize(&hiding_serialization).ok()?;
let binding_serialization = <InnerGroup as frost_core::Group>::Serialization::try_from((*binding_bytes).as_ref()).ok()?;
let binding_nonce = f::round1::NonceCommitment::deserialize(&binding_serialization).ok()?;
let commitments = f::round1::SigningCommitments::new(hiding_nonce, binding_nonce);
Some((identifier, commitments))
})
.collect::<BTreeMap<_, _>>();
if nonce_commitments_bytes.len() != signing_commitments.len() {
return Err(ExodusError::InvalidParticipantId);
}
let signing_package = f::SigningPackage::new(signing_commitments, message);
let binding_factors_list_wrapped =
frost_core::compute_binding_factor_list::<Ciphersuite>(&signing_package, &verifying_key, &[])
.map_err(|err| match err {
f::Error::SerializationError => ExodusError::SerializationError,
_ => ExodusError::Unknown,
})?;
let group_commitment =
frost_core::compute_group_commitment::<Ciphersuite>(&signing_package, &binding_factors_list_wrapped)
.map_err(|err| match err {
f::Error::IdentityCommitment => ExodusError::IdentityCommitment,
f::Error::UnknownIdentifier => ExodusError::UnknownIdentifier,
_ => ExodusError::Unknown,
})?;
let binding_factors_list = nonce_commitments_bytes
.iter()
.filter_map(|(&index, _)| {
let non_zero_index = Self::non_zero_index(index).ok()?;
let identifier = non_zero_index.try_into().ok()?;
let binding_factor = binding_factors_list_wrapped
.get(&identifier)
.map(|binding_factor| {
let fixed_bytes =
<InnerField as frost_core::Field>::Serialization::try_from(
binding_factor.serialize().as_slice()
).ok()?;
<InnerField as frost_core::Field>::deserialize(&fixed_bytes).ok()
})
.flatten()?;
Some((identifier, binding_factor))
})
.collect::<BTreeMap<_, _>>();
if nonce_commitments_bytes.len() != binding_factors_list.len() {
return Err(ExodusError::InvalidParticipantId);
}
let binding_factor_tree = Self::build_merkle_tree(
&binding_factors_list,
|binding_factor| {
let serialized = <InnerField as frost_core::Field>::serialize(binding_factor);
Ok(serialized.as_ref().to_vec())
}
)?;
let (self_binding_factor_bytes, binding_factors_proof) =
Self::generate_merkle_proof_from_tree(
&binding_factors_list,
&binding_factor_tree,
authority_index,
|binding_factor| {
let serialized = <InnerField as frost_core::Field>::serialize(binding_factor);
Ok(serialized.as_ref().to_vec())
}
)?;
let group_commitment_bytes =
<InnerGroup as frost_core::Group>::serialize(&group_commitment.to_element())
.map(|serialization| serialization.to_vec())
.map_err(|_| ExodusError::SerializationError)?;
let binding_factors_root = binding_factor_tree
.last()
.cloned()
.ok_or(ExodusError::IncorrectNumberOfPackages)?;
Ok((
group_commitment_bytes,
self_binding_factor_bytes,
binding_factors_proof,
binding_factors_root,
))
})
}
fn init_signing_package(
&self,
nonce_commitments_bytes: &Self::NoncePackages<'_>,
message: &[u8],
) -> Result<Vec<u8>, ExodusError> {
with_ciphersuite!(self, |f| {
type InnerGroup = <Ciphersuite as frost_core::Ciphersuite>::Group;
let signing_commitments = nonce_commitments_bytes
.iter()
.map(|(&index, &(hiding_bytes, binding_bytes))| {
let identifier = Self::non_zero_index(index)?
.try_into()
.map_err(|_| ExodusError::InvalidParticipantId)?;
let hiding_serialization =
<InnerGroup as frost_core::Group>::Serialization::try_from(hiding_bytes.as_ref())
.map_err(|_| ExodusError::DeserializationError)?;
let hiding_nonce =
f::round1::NonceCommitment::deserialize(&hiding_serialization)
.map_err(|_| ExodusError::DeserializationError)?;
let binding_serialization =
<InnerGroup as frost_core::Group>::Serialization::try_from((*binding_bytes).as_ref())
.map_err(|_| ExodusError::DeserializationError)?;
let binding_nonce =
f::round1::NonceCommitment::deserialize(&binding_serialization)
.map_err(|_| ExodusError::DeserializationError)?;
let commitments = f::round1::SigningCommitments::new(hiding_nonce, binding_nonce);
Ok((identifier, commitments))
})
.collect::<Result<BTreeMap<f::Identifier, f::round1::SigningCommitments>, ExodusError>>()?;
f::SigningPackage::new(signing_commitments, message)
.serialize()
.map_err(|_| ExodusError::SerializationError)
})
}
fn partial_sign_message(
&self,
signing_package_bytes: &[u8],
signer_nonces_bytes: &[u8],
secret_share_bytes: &[u8],
) -> Result<Vec<u8>, ExodusError> {
with_ciphersuite!(self, |f| {
let signing_package = f::SigningPackage::deserialize(signing_package_bytes)
.map_err(|_| ExodusError::DeserializationError)?;
let signer_nonces = f::round1::SigningNonces::deserialize(signer_nonces_bytes)
.map_err(|_| ExodusError::DeserializationError)?;
let secret_share = f::keys::KeyPackage::deserialize(secret_share_bytes)
.map_err(|_| ExodusError::DeserializationError)?;
let signature_shares =
f::round2::sign(&signing_package, &signer_nonces, &secret_share)
.map_err(|err| match err {
f::Error::IncorrectNumberOfCommitments => ExodusError::IncorrectNumberOfCommitments,
f::Error::MissingCommitment => ExodusError::MissingCommitment,
f::Error::IncorrectCommitment => ExodusError::IncorrectCommitment,
f::Error::SerializationError => ExodusError::SerializationError,
f::Error::DuplicatedIdentifier => ExodusError::DuplicatedIdentifier,
f::Error::UnknownIdentifier => ExodusError::UnknownIdentifier,
f::Error::IncorrectNumberOfIdentifiers => ExodusError::IncorrectNumberOfIdentifiers,
_ => ExodusError::Unknown,
})?;
Ok(signature_shares.serialize())
})
}
}