Skip to content

Commit

Permalink
feat(crypto): CRP-2339 Add support for unmasked random IDKG transcripts
Browse files Browse the repository at this point in the history
  • Loading branch information
randombit committed Jan 22, 2024
1 parent c8be7bd commit 7374d92
Show file tree
Hide file tree
Showing 16 changed files with 833 additions and 218 deletions.
27 changes: 27 additions & 0 deletions rs/crypto/internal/crypto_lib/threshold_sig/tecdsa/src/dealings.rs
Expand Up @@ -7,6 +7,7 @@ use std::convert::TryFrom;

#[derive(Clone)]
pub enum SecretShares {
RandomUnmasked,
Random,
ReshareOfUnmasked(EccScalar),
ReshareOfMasked(EccScalar, EccScalar),
Expand All @@ -17,6 +18,7 @@ impl Debug for SecretShares {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self {
Self::Random => write!(f, "SecretShares::Random"),
Self::RandomUnmasked => write!(f, "SecretShares::RandomUnmasked"),
Self::ReshareOfUnmasked(EccScalar::K256(_)) => write!(
f,
"SecretShares::ReshareOfUnmasked(EccScalar::K256) - REDACTED"
Expand Down Expand Up @@ -214,6 +216,20 @@ impl IDkgDealingInternal {

(commitment, ciphertext, None)
}
SecretShares::RandomUnmasked => {
let values = Polynomial::random(signature_curve, num_coefficients, &mut poly_rng);

let (ciphertext, commitment) = encrypt_and_commit_single_polynomial(
&values,
num_coefficients,
recipients,
dealer_index,
associated_data,
mega_seed,
)?;

(commitment, ciphertext, None)
}
SecretShares::ReshareOfUnmasked(secret) => {
if secret.curve_type() != signature_curve {
return Err(ThresholdEcdsaError::InvalidSecretShare);
Expand Down Expand Up @@ -345,6 +361,17 @@ impl IDkgDealingInternal {
// no ZK proof for this transcript type
Ok(())
}
(Op::RandomUnmasked, None) => {
self.commitment
.verify_is(PolynomialCommitmentType::Simple, signature_curve)?;
self.ciphertext.verify_is(
MEGaCiphertextType::Single,
key_curve,
signature_curve,
)?;
// no ZK proof for this transcript type
Ok(())
}
(
Op::ReshareOfMasked(previous_commitment),
Some(ZkProof::ProofOfMaskedResharing(proof)),
Expand Down
11 changes: 11 additions & 0 deletions rs/crypto/internal/crypto_lib/threshold_sig/tecdsa/src/group.rs
Expand Up @@ -1009,6 +1009,17 @@ impl EccPoint {
EccFieldElement::from_bytes(curve_type, &z[1 + field_bytes..])
}

/// Return if the affine Y coordinate of this point is even
pub fn is_y_even(&self) -> ThresholdEcdsaResult<bool> {
let compressed = self.serialize();

match compressed.first() {
Some(0x02) => Ok(true),
Some(0x03) => Ok(false),
_ => Err(ThresholdEcdsaError::InvalidPoint),
}
}

/// Return true if this is the point at infinity
pub fn is_infinity(&self) -> ThresholdEcdsaResult<bool> {
match &self.point {
Expand Down
Expand Up @@ -600,7 +600,7 @@ impl PolynomialCommitment {
self.points().len()
}

pub(crate) fn evaluate_at(&self, eval_point: NodeIndex) -> ThresholdEcdsaResult<EccPoint> {
pub fn evaluate_at(&self, eval_point: NodeIndex) -> ThresholdEcdsaResult<EccPoint> {
evaluate_at(self.points(), eval_point)
}

Expand Down
Expand Up @@ -37,7 +37,10 @@ fn derive_rho(
presig_transcript: &IDkgTranscriptInternal,
) -> ThresholdEcdsaResult<(EccScalar, EccScalar, EccScalar, EccPoint)> {
let pre_sig = match &presig_transcript.combined_commitment {
// random + reshare of masked case
CombinedCommitment::ByInterpolation(PolynomialCommitment::Simple(c)) => c.constant_term(),
// random unmasked case
CombinedCommitment::BySummation(PolynomialCommitment::Simple(c)) => c.constant_term(),
_ => return Err(ThresholdEcdsaError::UnexpectedCommitmentType),
};

Expand Down
Expand Up @@ -100,6 +100,7 @@ impl CombinedCommitment {
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum IDkgTranscriptOperationInternal {
Random,
RandomUnmasked,
ReshareOfMasked(PolynomialCommitment),
ReshareOfUnmasked(PolynomialCommitment),
UnmaskedTimesMasked(PolynomialCommitment, PolynomialCommitment),
Expand Down Expand Up @@ -222,6 +223,24 @@ impl IDkgTranscriptInternal {
CombinedCommitment::BySummation(PedersenCommitment::new(combined).into())
}

IDkgTranscriptOperationInternal::RandomUnmasked => {
// Combine commitments via sum
let mut combined = vec![EccPoint::identity(curve); reconstruction_threshold];

for dealing in verified_dealings.values() {
if dealing.commitment.ctype() != PolynomialCommitmentType::Simple {
return Err(ThresholdEcdsaError::UnexpectedCommitmentType);
}

let c = dealing.commitment.points();
for i in 0..reconstruction_threshold {
combined[i] = combined[i].add_points(&c[i])?;
}
}

CombinedCommitment::BySummation(SimpleCommitment::new(combined).into())
}

IDkgTranscriptOperationInternal::ReshareOfMasked(reshared_commitment) => {
// Verify that the old commitment is actually masked
if reshared_commitment.ctype() != PolynomialCommitmentType::Pedersen {
Expand Down Expand Up @@ -535,26 +554,44 @@ impl CommitmentOpening {
match transcript_commitment {
CombinedCommitment::BySummation(commitment) => {
// Recombine secret by summation
let mut combined_value = EccScalar::zero(curve);
let mut combined_mask = EccScalar::zero(curve);

for (_dealer_index, opening) in openings {
if let Self::Pedersen(value, mask) = opening {
combined_value = combined_value.add(value)?;
combined_mask = combined_mask.add(mask)?;
} else {
return Err(ThresholdEcdsaError::UnexpectedCommitmentType);

let combined_opening = match commitment {
PolynomialCommitment::Simple(_) => {
let mut combined_value = EccScalar::zero(curve);

for (_dealer_index, opening) in openings {
if let Self::Simple(value) = opening {
combined_value = combined_value.add(value)?;
} else {
return Err(ThresholdEcdsaError::UnexpectedCommitmentType);
}
}

Self::Simple(combined_value)
}
}
PolynomialCommitment::Pedersen(_) => {
let mut combined_value = EccScalar::zero(curve);
let mut combined_mask = EccScalar::zero(curve);

let combined_opening = Self::Pedersen(combined_value, combined_mask);
for (_dealer_index, opening) in openings {
if let Self::Pedersen(value, mask) = opening {
combined_value = combined_value.add(value)?;
combined_mask = combined_mask.add(mask)?;
} else {
return Err(ThresholdEcdsaError::UnexpectedCommitmentType);
}
}

Self::Pedersen(combined_value, combined_mask)
}
};

// Check reconstructed opening matches the commitment
commitment.return_opening_if_consistent(receiver_index, &combined_opening)
}

CombinedCommitment::ByInterpolation(commitment) => {
let opening = match commitment {
let combined_opening = match commitment {
PolynomialCommitment::Simple(_) => {
let mut x_values = Vec::with_capacity(openings.len());
let mut values = Vec::with_capacity(openings.len());
Expand Down Expand Up @@ -597,7 +634,7 @@ impl CommitmentOpening {
};

// Check reconstructed opening matches the commitment
commitment.return_opening_if_consistent(receiver_index, &opening)
commitment.return_opening_if_consistent(receiver_index, &combined_opening)
}
}
}
Expand Down
@@ -0,0 +1 @@
a36a63697068657274657874a16653696e676c65a46d657068656d6572616c5f6b657958220102f134d58fff9311f54657ec06e68165bff4f4ec2b1ed83761c465b8038f6a60a36e706f705f7075626c69635f6b65795822010356833e82d11e17d612376693904eb4d8fbfb2c7d144a1cf9a861548aa13ad64869706f705f70726f6f66a2696368616c6c656e6765582101a7f9519ff311387e60ec9a6e4c987282dbe8b4aad939b6689543a6339b07e47768726573706f6e736558210130de1eaeddc0d236843fc71ea2e25c799b45d98c2cb3e677daa50af3276fa9c56663746578747384582102a3b95224700fc7345deeb671253c0c5a346fa37559d5837ce1da7005161ba619582102bcbf47f803a77e6b54a165e25ce45e3beb65eb540c30066f676ebf69b28bf7b95821025e2bc9c1a268b2ce22a84557f86fdb118517b86a2f7a9cec5d75a5eaa6cb928058210242866ecd96e6441cb05e5865155173fffebc4611f0f21f0114874c3cdec41c366a636f6d6d69746d656e74a16653696d706c65a166706f696e74738258220203f507919949326b7933114d56e910b2728f9e046475518347f3cb462c18c8bf0e58220203de650592a3f093ab387775c4461aad0d26fcafe238ab0cafeb45e0b56090fb686570726f6f66f6
@@ -0,0 +1 @@
a173636f6d62696e65645f636f6d6d69746d656e74a16b427953756d6d6174696f6ea16653696d706c65a166706f696e7473825822020243985a6f698bf5009c5444490516c899f91d875badac1754f731e3a77d4a5eed582202034d6f953116c1d2b66f73d5c42bde7c8a7a934e0b38e7980351384771332e73a4
Expand Up @@ -77,6 +77,48 @@ fn create_random_dealing() -> Result<(), IdkgCreateDealingInternalError> {
Ok(())
}

#[test]
fn create_random_unmasked_dealing() -> Result<(), IdkgCreateDealingInternalError> {
let rng = &mut reproducible_rng();

for curve in EccCurveType::all() {
let associated_data = vec![1, 2, 3];
let (private_keys, public_keys) = gen_private_keys(rng, curve, 5);
let threshold = 2;
let dealer_index = 0;

let shares = SecretShares::RandomUnmasked;

let dealing = create_dealing(
alg_for_curve(curve),
&associated_data,
dealer_index,
NumberOfNodes::from(threshold as u32),
&public_keys,
&shares,
Seed::from_rng(rng),
)?;

match dealing.commitment {
PolynomialCommitment::Simple(c) => {
assert_eq!(c.points.len(), threshold);
}
_ => panic!("Unexpected commitment type for random unmasked dealing"),
}

match dealing.ciphertext {
MEGaCiphertext::Single(p) => {
assert_eq!(p.ctexts.len(), private_keys.len())
}
_ => panic!("Unexpected ciphertext type for random unmasked dealing"),
}

assert!(dealing.proof.is_none()); // random dealings have no associated proof
}

Ok(())
}

#[test]
fn create_reshare_unmasked_dealing() -> Result<(), IdkgCreateDealingInternalError> {
let rng = &mut reproducible_rng();
Expand Down Expand Up @@ -254,6 +296,12 @@ fn secret_shares_should_redact_logs() -> Result<(), ThresholdEcdsaError> {
assert_eq!("SecretShares::Random", log);
}

{
let shares = SecretShares::RandomUnmasked;
let log = format!("{:?}", shares);
assert_eq!("SecretShares::RandomUnmasked", log);
}

{
let secret = EccScalar::random(curve, rng);
let shares = SecretShares::ReshareOfUnmasked(secret);
Expand Down
29 changes: 29 additions & 0 deletions rs/crypto/internal/crypto_lib/threshold_sig/tecdsa/tests/group.rs
Expand Up @@ -262,6 +262,35 @@ fn test_point_negate() -> ThresholdEcdsaResult<()> {
Ok(())
}

#[test]
fn test_y_is_even() -> ThresholdEcdsaResult<()> {
let rng = &mut reproducible_rng();

for curve_type in EccCurveType::all() {
let g = EccPoint::generator_g(curve_type);

for _trial in 0..100 {
let s = EccScalar::random(curve_type, rng);
let p = g.scalar_mul(&s)?;
let np = p.negate();

match (p.is_y_even()?, np.is_y_even()?) {
(true, true) => panic!("Both point and its negation have even y"),
(false, false) => panic!("Neither point nor its negation have even y"),
(true, false) => {
assert_eq!(p.affine_y()?.sign(), 0x00);
assert_eq!(np.affine_y()?.sign(), 0x01);
}
(false, true) => {
assert_eq!(p.affine_y()?.sign(), 0x01);
assert_eq!(np.affine_y()?.sign(), 0x00)
}
}
}
}
Ok(())
}

#[test]
fn test_mul_2_is_correct() -> ThresholdEcdsaResult<()> {
let rng = &mut reproducible_rng();
Expand Down
Expand Up @@ -22,6 +22,7 @@ fn verify_bip32_extended_key_derivation_max_length_enforced() -> Result<(), Thre
threshold,
threshold,
seed,
true,
)?;

for i in 0..=255 {
Expand Down Expand Up @@ -280,6 +281,7 @@ fn verify_bip32_secp256k1_extended_key_derivation() -> Result<(), ThresholdEcdsa
threshold,
threshold,
seed,
true,
)?;

let master_key = setup.public_key(&DerivationPath::new(vec![]))?;
Expand Down Expand Up @@ -342,6 +344,7 @@ fn should_secp256k1_derivation_match_external_bip32_lib() -> Result<(), Threshol
threshold,
threshold,
random_seed,
true,
)?;

// zeros the high bit to avoid requesting hardened derivation, which we do not support
Expand Down

0 comments on commit 7374d92

Please sign in to comment.