Skip to content

Commit

Permalink
CRP-1293+CRP-1294 Add verification of IDKG (Threshold ECDSA) dealings
Browse files Browse the repository at this point in the history
  • Loading branch information
randombit committed Dec 8, 2021
1 parent 8e8069f commit 8f1ba3a
Show file tree
Hide file tree
Showing 5 changed files with 349 additions and 18 deletions.
130 changes: 124 additions & 6 deletions rs/crypto/internal/crypto_lib/threshold_sig/tecdsa/src/dealings.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
use crate::*;
use core::fmt::{self, Debug};
use ic_types::crypto::canister_threshold_sig::idkg::IDkgMultiSignedDealing;
use ic_types::NumberOfNodes;
use serde::{Deserialize, Serialize};
use std::convert::TryFrom;

// TODO(CRP-1158) these should have values
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ZkProof {}
pub enum ZkProof {
ProofOfMaskedResharing,
ProofOfMultiplication,
}

#[derive(Clone)]
pub enum SecretShares {
Expand Down Expand Up @@ -228,9 +233,8 @@ impl IDkgDealingInternal {
mega_seed,
)?;

let proof = None; // TODO(CRP-1158)

(commitment, ciphertext, proof)
// The commitment is unmasked so no ZK equivalence proof is required
(commitment, ciphertext, None)
}
SecretShares::ReshareOfMasked(secret, masking) => {
if secret.curve_type() != curve || masking.curve_type() != curve {
Expand All @@ -251,7 +255,7 @@ impl IDkgDealingInternal {

// Compute zk proof
// Note that `masking` will be used here as part of the witness
let proof = None; // TODO(CRP-1158)
let proof = Some(ZkProof::ProofOfMaskedResharing); // TODO(CRP-1158)

(commitment, ciphertext, proof)
}
Expand Down Expand Up @@ -281,7 +285,7 @@ impl IDkgDealingInternal {

// Compute zk proof
// Note that `right_masking` will be used here as part of the witness
let proof = None; // TODO(CRP-1158)
let proof = Some(ZkProof::ProofOfMultiplication); // TODO(CRP-1158)

(commitment, ciphertext, proof)
}
Expand All @@ -294,6 +298,120 @@ impl IDkgDealingInternal {
})
}

pub fn publicly_verify(
&self,
curve_type: EccCurveType,
transcript_type: &IDkgTranscriptOperationInternal,
reconstruction_threshold: NumberOfNodes,
dealer_index: NodeIndex,
number_of_receivers: NumberOfNodes,
) -> ThresholdEcdsaResult<()> {
if self.commitment.len() != reconstruction_threshold.get() as usize {
return Err(ThresholdEcdsaError::InconsistentCommitments);
}

if self.commitment.curve_type() != curve_type {
return Err(ThresholdEcdsaError::CurveMismatch);
}

if self.ciphertext.recipients() != number_of_receivers.get() as usize {
return Err(ThresholdEcdsaError::InvalidRecipients);
}

type Op = IDkgTranscriptOperationInternal;

// Check that the proof type matches the transcript type, and verify the proof
match (transcript_type, self.proof.as_ref()) {
(Op::Random, None) => {
self.commitment
.verify_is(PolynomialCommitmentType::Pedersen, curve_type)?;
self.ciphertext
.verify_is(MEGaCiphertextType::Pairs, curve_type)?;
// no ZK proof for this transcript type
Ok(())
}
(Op::ReshareOfMasked(previous_commitment), Some(ZkProof::ProofOfMaskedResharing)) => {
self.commitment
.verify_is(PolynomialCommitmentType::Simple, curve_type)?;
previous_commitment.verify_is(PolynomialCommitmentType::Pedersen, curve_type)?;
self.ciphertext
.verify_is(MEGaCiphertextType::Single, curve_type)?;

/* TODO(CRP-1158) verify proof */
Ok(())
}

(Op::ReshareOfUnmasked(previous_commitment), None) => {
self.commitment
.verify_is(PolynomialCommitmentType::Simple, curve_type)?;
previous_commitment.verify_is(PolynomialCommitmentType::Simple, curve_type)?;
self.ciphertext
.verify_is(MEGaCiphertextType::Single, curve_type)?;

match previous_commitment {
PolynomialCommitment::Pedersen(_) => {
return Err(ThresholdEcdsaError::InconsistentCommitments)
}
PolynomialCommitment::Simple(c) => {
let constant_term = self.commitment.constant_term();
let dealer_index =
EccScalar::from_u64(curve_type, (dealer_index + 1) as u64);

if c.evaluate_at(&dealer_index)? != constant_term {
return Err(ThresholdEcdsaError::InconsistentCommitments);
}
}
}

// no ZK proof for this transcript type
Ok(())
}
(Op::UnmaskedTimesMasked(lhs, rhs), Some(ZkProof::ProofOfMultiplication)) => {
self.commitment
.verify_is(PolynomialCommitmentType::Pedersen, curve_type)?;
self.ciphertext
.verify_is(MEGaCiphertextType::Pairs, curve_type)?;
lhs.verify_is(PolynomialCommitmentType::Simple, curve_type)?;
rhs.verify_is(PolynomialCommitmentType::Pedersen, curve_type)?;

/* TODO(CRP-1158) verify proof */
Ok(())
}
(_transcript_type, _proof) => Err(ThresholdEcdsaError::InvalidProof),
}
}

#[allow(clippy::too_many_arguments)]
pub fn privately_verify(
&self,
curve_type: EccCurveType,
private_key: &MEGaPrivateKey,
public_key: &MEGaPublicKey,
associated_data: &[u8],
dealer_index: NodeIndex,
recipient_index: NodeIndex,
) -> ThresholdEcdsaResult<()> {
if private_key.curve_type() != curve_type || public_key.curve_type() != curve_type {
return Err(ThresholdEcdsaError::CurveMismatch);
}

if self.commitment.constant_term().curve_type() != curve_type {
return Err(ThresholdEcdsaError::CurveMismatch);
}

let _opening = mega::decrypt_and_check(
&self.ciphertext,
&self.commitment,
associated_data,
dealer_index as usize,
recipient_index as usize,
private_key,
public_key,
)?;

Ok(())
}

pub fn serialize(&self) -> ThresholdEcdsaResult<Vec<u8>> {
serde_cbor::to_vec(self)
.map_err(|e| ThresholdEcdsaError::SerializationError(format!("{}", e)))
Expand Down
85 changes: 85 additions & 0 deletions rs/crypto/internal/crypto_lib/threshold_sig/tecdsa/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@ use std::collections::BTreeMap;
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub enum ThresholdEcdsaError {
CurveMismatch,
InconsistentCiphertext,
InconsistentCommitments,
InsufficientDealings,
InterpolationError,
InvalidArguments(String),
InvalidFieldElement,
InvalidOpening,
InvalidPoint,
InvalidProof,
InvalidRecipients,
InvalidScalar,
InvalidSecretShare,
Expand Down Expand Up @@ -183,3 +185,86 @@ pub fn compute_secret_shares(
)
.map_err(|e| e.into())
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub enum IDkgVerifyDealingInternalError {
UnsupportedAlgorithm,
InvalidCommitment,
InvalidProof,
InvalidRecipients,
InternalError(String),
}

impl From<ThresholdEcdsaError> for IDkgVerifyDealingInternalError {
fn from(e: ThresholdEcdsaError) -> Self {
match e {
ThresholdEcdsaError::InvalidProof => Self::InvalidProof,
ThresholdEcdsaError::InconsistentCommitments => Self::InvalidCommitment,
ThresholdEcdsaError::InvalidRecipients => Self::InvalidRecipients,
x => Self::InternalError(format!("{:?}", x)),
}
}
}

/// Verify a dealing using public information
///
/// Verify that the dealing has the expected type of ciphertext
/// and commitment (depending on the type of dealing)
///
/// When CRP-1158 is completed this will also verify the zero
/// knowledge proofs
pub fn publicly_verify_dealing(
algorithm_id: AlgorithmId,
dealing: &IDkgDealingInternal,
transcript_type: &IDkgTranscriptOperationInternal,
reconstruction_threshold: NumberOfNodes,
dealer_index: NodeIndex,
number_of_receivers: NumberOfNodes,
) -> Result<(), IDkgVerifyDealingInternalError> {
let curve = match algorithm_id {
AlgorithmId::ThresholdEcdsaSecp256k1 => Ok(EccCurveType::K256),
_ => Err(IDkgVerifyDealingInternalError::UnsupportedAlgorithm),
}?;

dealing
.publicly_verify(
curve,
transcript_type,
reconstruction_threshold,
dealer_index,
number_of_receivers,
)
.map_err(|e| e.into())
}

/// Verify a dealing using private information
///
/// This private verification must be done after the dealing has been publically
/// verified. This operation decrypts the dealing and verifies that the
/// decrypted value is consistent with the commitment in the dealing.
#[allow(clippy::too_many_arguments)]
pub fn privately_verify_dealing(
algorithm_id: AlgorithmId,
dealing: &IDkgDealingInternal,
private_key: &MEGaPrivateKey,
public_key: &MEGaPublicKey,
associated_data: &[u8],
dealer_index: NodeIndex,
recipient_index: NodeIndex,
) -> Result<(), IDkgVerifyDealingInternalError> {
let curve = match algorithm_id {
AlgorithmId::ThresholdEcdsaSecp256k1 => Ok(EccCurveType::K256),
_ => Err(IDkgVerifyDealingInternalError::UnsupportedAlgorithm),
}?;

dealing
.privately_verify(
curve,
private_key,
public_key,
associated_data,
dealer_index,
recipient_index,
)
.map_err(|e| e.into())
}
47 changes: 47 additions & 0 deletions rs/crypto/internal/crypto_lib/threshold_sig/tecdsa/src/mega.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,13 +123,60 @@ impl MEGaCiphertextPair {
}
}

/// The type of MEGa ciphertext
#[derive(Debug, Copy, Clone, Serialize, Deserialize, Eq, PartialEq)]
pub enum MEGaCiphertextType {
Single,
Pairs,
}

/// Some type of MEGa ciphertext
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum MEGaCiphertext {
Single(MEGaCiphertextSingle),
Pairs(MEGaCiphertextPair),
}

impl MEGaCiphertext {
pub fn recipients(&self) -> usize {
match self {
MEGaCiphertext::Single(c) => c.ctexts.len(),
MEGaCiphertext::Pairs(c) => c.ctexts.len(),
}
}

pub fn ctype(&self) -> MEGaCiphertextType {
match self {
MEGaCiphertext::Single(_) => MEGaCiphertextType::Single,
MEGaCiphertext::Pairs(_) => MEGaCiphertextType::Pairs,
}
}

pub fn verify_is(
&self,
ctype: MEGaCiphertextType,
curve: EccCurveType,
) -> ThresholdEcdsaResult<()> {
let curves_ok = match self {
MEGaCiphertext::Single(c) => c.ctexts.iter().all(|x| x.curve_type() == curve),
MEGaCiphertext::Pairs(c) => c
.ctexts
.iter()
.all(|(x, y)| x.curve_type() == curve && y.curve_type() == curve),
};

if !curves_ok {
return Err(ThresholdEcdsaError::CurveMismatch);
}

if self.ctype() != ctype {
return Err(ThresholdEcdsaError::InconsistentCiphertext);
}

Ok(())
}
}

impl From<MEGaCiphertextSingle> for MEGaCiphertext {
fn from(c: MEGaCiphertextSingle) -> Self {
Self::Single(c)
Expand Down
Loading

0 comments on commit 8f1ba3a

Please sign in to comment.