Skip to content

Commit

Permalink
feat(crypto): CRP-2352 implement tSchnorr in crypto component
Browse files Browse the repository at this point in the history
  • Loading branch information
altkdf committed Apr 3, 2024
1 parent 528c5a3 commit e98988c
Show file tree
Hide file tree
Showing 8 changed files with 456 additions and 16 deletions.
8 changes: 3 additions & 5 deletions rs/crypto/internal/crypto_lib/threshold_sig/tecdsa/src/lib.rs
Expand Up @@ -925,7 +925,6 @@ impl From<ThresholdEcdsaError> for ThresholdBip340VerifySigShareInternalError {
///
/// The values provided must be consistent with when the signature share
/// was created
#[allow(clippy::too_many_arguments)]
pub fn verify_bip340_signature_share(
sig_share: &ThresholdBip340SignatureShareInternal,
derivation_path: &DerivationPath,
Expand Down Expand Up @@ -970,7 +969,6 @@ impl From<ThresholdEcdsaError> for ThresholdBip340CombineSigSharesInternalError
///
/// The signature shares must be verified prior to use, and there must
/// be at least reconstruction_threshold many of them.
#[allow(clippy::too_many_arguments)]
pub fn combine_bip340_signature_shares(
derivation_path: &DerivationPath,
message: &[u8],
Expand Down Expand Up @@ -1017,18 +1015,18 @@ impl From<ThresholdEcdsaError> for ThresholdBip340VerifySignatureInternalError {
/// `derivation_path`, this function also verifies that the signature
/// was generated correctly with regards to the provided presignature
/// transcript and randomness.
pub fn verify_bip340_threshold_signature(
pub fn verify_threshold_bip340_signature(
signature: &ThresholdBip340CombinedSignatureInternal,
derivation_path: &DerivationPath,
hashed_message: &[u8],
message: &[u8],
randomness: Randomness,
presig_transcript: &IDkgTranscriptInternal,
key_transcript: &IDkgTranscriptInternal,
) -> Result<(), ThresholdBip340VerifySignatureInternalError> {
signature
.verify(
derivation_path,
hashed_message,
message,
randomness,
presig_transcript,
key_transcript,
Expand Down
Expand Up @@ -266,6 +266,7 @@ impl ThresholdBip340SignatureShareInternal {
}
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ThresholdBip340SignatureShareInternalSerializationError(pub String);

#[derive(Debug, Clone, Serialize, Deserialize)]
Expand All @@ -276,13 +277,43 @@ pub struct ThresholdBip340CombinedSignatureInternal {

impl ThresholdBip340CombinedSignatureInternal {
/// Serialize in the format BIP340 expects, x coordinate only
pub fn serialize(&self) -> ThresholdEcdsaResult<Vec<u8>> {
pub fn serialize(
&self,
) -> Result<Vec<u8>, ThresholdBip340SignatureShareInternalSerializationError> {
let mut v = vec![];
v.extend_from_slice(&self.r.serialize_bip340()?);
v.extend_from_slice(&self.r.serialize_bip340().map_err(|e| {
ThresholdBip340SignatureShareInternalSerializationError(format!(
"Failed to serialize r: {:?}",
e
))
})?);
v.extend_from_slice(&self.s.serialize());
Ok(v)
}

/// Deserialize in the format BIP340 expects, x coordinate only
pub fn deserialize(
bytes: &[u8],
) -> Result<Self, ThresholdBip340SignatureShareInternalSerializationError> {
const K256: EccCurveType = EccCurveType::K256;

if bytes.len() != K256.scalar_bytes() + K256.point_bytes() {
return Err(ThresholdBip340SignatureShareInternalSerializationError(
"Bad signature length".to_string(),
));
}

let r = EccPoint::deserialize_bip340(K256, &bytes[..K256.scalar_bytes()]).map_err(|e| {
ThresholdBip340SignatureShareInternalSerializationError(format!("Invalid r: {:?}", e))
})?;

let s = EccScalar::deserialize(K256, &bytes[K256.scalar_bytes()..]).map_err(|e| {
ThresholdBip340SignatureShareInternalSerializationError(format!("Invalid s: {:?}", e))
})?;

Ok(Self { r, s })
}

/// Combine shares into a BIP340 Schnorr signature
pub fn new(
derivation_path: &DerivationPath,
Expand Down
Expand Up @@ -1149,7 +1149,7 @@ impl Bip340SignatureProtocolExecution {
&self,
sig: &ThresholdBip340CombinedSignatureInternal,
) -> Result<(), ThresholdBip340VerifySignatureInternalError> {
verify_bip340_threshold_signature(
verify_threshold_bip340_signature(
sig,
&self.derivation_path,
&self.signed_message,
Expand All @@ -1163,7 +1163,9 @@ impl Bip340SignatureProtocolExecution {

assert!(verify_bip340_signature_using_third_party(
&pk,
&sig.serialize()?,
&sig.serialize().map_err(|e| {
ThresholdBip340VerifySignatureInternalError::InternalError(format!("{e:?}"))
})?,
&self.signed_message
));

Expand Down
1 change: 1 addition & 0 deletions rs/crypto/src/sign/canister_threshold_sig.rs
@@ -1,5 +1,6 @@
pub mod ecdsa;
mod idkg;
pub mod schnorr;
#[cfg(test)]
pub(crate) mod test_utils;

Expand Down
260 changes: 260 additions & 0 deletions rs/crypto/src/sign/canister_threshold_sig/schnorr.rs
@@ -0,0 +1,260 @@
//! Implementations of ThresholdSchnorrSigner and ThresholdSchnorrVerifier

use ic_crypto_internal_csp::vault::api::{
CspVault, IDkgTranscriptInternalBytes, ThresholdSchnorrCreateSigShareVaultError,
};
use ic_crypto_internal_threshold_sig_ecdsa::{
combine_bip340_signature_shares, verify_bip340_signature_share,
verify_threshold_bip340_signature, DerivationPath, IDkgTranscriptInternal,
ThresholdBip340CombineSigSharesInternalError, ThresholdBip340CombinedSignatureInternal,
ThresholdBip340SignatureShareInternal, ThresholdBip340VerifySigShareInternalError,
ThresholdBip340VerifySignatureInternalError,
};
use ic_types::{
crypto::canister_threshold_sig::{
error::{
ThresholdSchnorrCombineSigSharesError, ThresholdSchnorrCreateSigShareError,
ThresholdSchnorrVerifyCombinedSigError, ThresholdSchnorrVerifySigShareError,
},
idkg::IDkgReceivers,
ThresholdSchnorrCombinedSignature, ThresholdSchnorrSigInputs, ThresholdSchnorrSigShare,
},
crypto::AlgorithmId,
NodeId, NodeIndex,
};
use std::collections::BTreeMap;

pub fn create_sig_share(
vault: &dyn CspVault,
self_node_id: &NodeId,
inputs: &ThresholdSchnorrSigInputs,
) -> Result<ThresholdSchnorrSigShare, ThresholdSchnorrCreateSigShareError> {
ensure_self_was_receiver(self_node_id, inputs.receivers())?;

let key_raw = inputs.key_transcript().transcript_to_bytes();
let presignature_raw = inputs
.presig_transcript()
.blinder_unmasked()
.transcript_to_bytes();

let sig_share_raw_typed = vault
.create_schnorr_sig_share(
inputs.derivation_path().clone(),
inputs.message().to_vec(),
*inputs.nonce(),
IDkgTranscriptInternalBytes::from(key_raw),
IDkgTranscriptInternalBytes::from(presignature_raw),
inputs.algorithm_id(),
)
.map_err(|e| {
type F = ThresholdSchnorrCreateSigShareVaultError;
type T = ThresholdSchnorrCreateSigShareError;
match e {
F::InvalidArguments(s) => T::InvalidArguments(s),
F::InconsistentCommitments => T::InternalError(format!("{e:?}")),
F::SerializationError(s) => T::SerializationError(s),
F::SecretSharesNotFound { commitment_string } => {
T::SecretSharesNotFound { commitment_string }
}
F::InternalError(s) => T::InternalError(s),
F::TransientInternalError(s) => T::TransientInternalError(s),
}
})?;
let sig_share_raw = sig_share_raw_typed.into_vec();

Ok(ThresholdSchnorrSigShare { sig_share_raw })
}

pub fn verify_sig_share(
signer: NodeId,
inputs: &ThresholdSchnorrSigInputs,
share: &ThresholdSchnorrSigShare,
) -> Result<(), ThresholdSchnorrVerifySigShareError> {
let presig = IDkgTranscriptInternal::try_from(inputs.presig_transcript().blinder_unmasked())
.map_err(|e| ThresholdSchnorrVerifySigShareError::SerializationError(e.0))?;
let key = IDkgTranscriptInternal::try_from(inputs.key_transcript())
.map_err(|e| ThresholdSchnorrVerifySigShareError::SerializationError(e.0))?;

let signer_index = inputs.key_transcript().index_for_signer_id(signer).ok_or(
ThresholdSchnorrVerifySigShareError::InvalidArgumentMissingSignerInTranscript {
signer_id: signer,
},
)?;

match inputs.algorithm_id() {
AlgorithmId::ThresholdSchnorrBip340 => {
let internal_share =
ThresholdBip340SignatureShareInternal::deserialize(&share.sig_share_raw).map_err(
|e| ThresholdSchnorrVerifySigShareError::SerializationError(format!("{e:?}")),
)?;

verify_bip340_signature_share(
&internal_share,
&DerivationPath::from(inputs.derivation_path()),
inputs.message(),
*inputs.nonce(),
signer_index,
&key,
&presig,
)
.map_err(|e| {
type F = ThresholdBip340VerifySigShareInternalError;
type T = ThresholdSchnorrVerifySigShareError;
match e {
F::InvalidArguments(s) => T::InvalidArguments(s),
F::InternalError(internal_error) => T::InternalError(internal_error),
F::InconsistentCommitments => T::InternalError(format!("{e:?}")),
F::InvalidSignatureShare => T::InvalidSignatureShare,
}
})
}
algorithm_id => Err(ThresholdSchnorrVerifySigShareError::InvalidArguments(
format!("invalid algorithm id for threshold Schnorr signature: {algorithm_id}"),
)),
}
}

pub fn combine_sig_shares(
inputs: &ThresholdSchnorrSigInputs,
shares: &BTreeMap<NodeId, ThresholdSchnorrSigShare>,
) -> Result<ThresholdSchnorrCombinedSignature, ThresholdSchnorrCombineSigSharesError> {
ensure_sufficient_sig_shares_collected(inputs, shares)?;

let presig = IDkgTranscriptInternal::try_from(inputs.presig_transcript().blinder_unmasked())
.map_err(|e| ThresholdSchnorrCombineSigSharesError::SerializationError(e.0))?;
let key = IDkgTranscriptInternal::try_from(inputs.key_transcript())
.map_err(|e| ThresholdSchnorrCombineSigSharesError::SerializationError(e.0))?;

match inputs.algorithm_id() {
AlgorithmId::ThresholdSchnorrBip340 => {
let internal_shares =
internal_bip340_sig_shares_by_index_from_sig_shares(shares, inputs)?;

let internal_combined_sig = combine_bip340_signature_shares(
&DerivationPath::from(inputs.derivation_path()),
inputs.message(),
*inputs.nonce(),
&key,
&presig,
inputs.reconstruction_threshold(),
&internal_shares,
)
.map_err(|e| {
type F = ThresholdBip340CombineSigSharesInternalError;
type T = ThresholdSchnorrCombineSigSharesError;

match e {
F::UnsupportedAlgorithm => {
T::InvalidArguments("unsupported algorithm".to_string())
}
F::InconsistentCommitments => {
T::InvalidArguments("inconsistent commitments".to_string())
}
F::InsufficientShares => T::InvalidArguments("insufficient shares".to_string()),
F::InternalError(s) => T::InternalError(s),
}
})?;

Ok(ThresholdSchnorrCombinedSignature {
signature: internal_combined_sig
.serialize()
.map_err(|e| ThresholdSchnorrCombineSigSharesError::SerializationError(e.0))?,
})
}
algorithm_id => Err(ThresholdSchnorrCombineSigSharesError::InternalError(
format!("invalid algorithm id for threshold Schnorr signature: {algorithm_id}"),
)),
}
}

fn ensure_sufficient_sig_shares_collected(
inputs: &ThresholdSchnorrSigInputs,
shares: &BTreeMap<NodeId, ThresholdSchnorrSigShare>,
) -> Result<(), ThresholdSchnorrCombineSigSharesError> {
if shares.len() < inputs.reconstruction_threshold().get() as usize {
Err(
ThresholdSchnorrCombineSigSharesError::UnsatisfiedReconstructionThreshold {
threshold: inputs.reconstruction_threshold().get(),
share_count: shares.len(),
},
)
} else {
Ok(())
}
}

/// Deserialize each raw signature share to the internal format,
/// and map them by signer index (rather than signer Id).
fn internal_bip340_sig_shares_by_index_from_sig_shares(
shares: &BTreeMap<NodeId, ThresholdSchnorrSigShare>,
inputs: &ThresholdSchnorrSigInputs,
) -> Result<
BTreeMap<NodeIndex, ThresholdBip340SignatureShareInternal>,
ThresholdSchnorrCombineSigSharesError,
> {
shares
.iter()
.map(|(&id, share)| {
let index = inputs
.index_for_signer_id(id)
.ok_or(ThresholdSchnorrCombineSigSharesError::SignerNotAllowed { node_id: id })?;
let internal_share = ThresholdBip340SignatureShareInternal::deserialize(
&share.sig_share_raw,
)
.map_err(|e| {
ThresholdSchnorrCombineSigSharesError::SerializationError(format!("{:?}", e))
})?;
Ok((index, internal_share))
})
.collect()
}

pub fn verify_combined_sig(
inputs: &ThresholdSchnorrSigInputs,
signature: &ThresholdSchnorrCombinedSignature,
) -> Result<(), ThresholdSchnorrVerifyCombinedSigError> {
let blinder_unmasked =
IDkgTranscriptInternal::try_from(inputs.presig_transcript().blinder_unmasked())
.map_err(|e| ThresholdSchnorrVerifyCombinedSigError::SerializationError(e.0))?;
let key = IDkgTranscriptInternal::try_from(inputs.key_transcript())
.map_err(|e| ThresholdSchnorrVerifyCombinedSigError::SerializationError(e.0))?;

match inputs.algorithm_id() {
AlgorithmId::ThresholdSchnorrBip340 => {
let signature =
ThresholdBip340CombinedSignatureInternal::deserialize(&signature.signature)
.map_err(|e| ThresholdSchnorrVerifyCombinedSigError::SerializationError(e.0))?;
verify_threshold_bip340_signature(
&signature,
&DerivationPath::from(inputs.derivation_path()),
inputs.message(),
*inputs.nonce(),
&key,
&blinder_unmasked,
)
.map_err(|e| {
type F = ThresholdBip340VerifySignatureInternalError;
type T = ThresholdSchnorrVerifyCombinedSigError;
match e {
F::UnexpectedCommitmentType => T::InvalidArguments(format!("{e:?}")),
F::InternalError(s) => T::InternalError(s),
F::InvalidSignature => T::InvalidSignature,
}
})
}
algorithm_id => Err(ThresholdSchnorrVerifyCombinedSigError::InvalidArguments(
format!("invalid algorithm id for threshold Schnorr signature: {algorithm_id}"),
)),
}
}

fn ensure_self_was_receiver(
self_node_id: &NodeId,
receivers: &IDkgReceivers,
) -> Result<(), ThresholdSchnorrCreateSigShareError> {
if receivers.contains(*self_node_id) {
Ok(())
} else {
Err(ThresholdSchnorrCreateSigShareError::NotAReceiver)
}
}

0 comments on commit e98988c

Please sign in to comment.