frost-core 3.0.0

Types and traits to support implementing Flexible Round-Optimized Schnorr Threshold signature schemes (FROST).
Documentation
//! Test for Repairable Threshold Scheme

use alloc::collections::BTreeMap;

use debugless_unwrap::DebuglessUnwrapExt;
use rand_core::{CryptoRng, RngCore};
use serde_json::Value;

use crate as frost;
use crate::keys::repairable::{Delta, Sigma};
use crate::keys::KeyPackage;
use crate::{
    compute_lagrange_coefficient,
    keys::{
        repairable::{repair_share_part1, repair_share_part2, repair_share_part3},
        PublicKeyPackage, SecretShare,
    },
    Ciphersuite, Error, Field, Group, Identifier,
};

/// We want to test that recovered share matches the original share
pub fn check_rts<C: Ciphersuite, R: RngCore + CryptoRng>(mut rng: R) {
    // Compute shares

    ////////////////////////////////////////////////////////////////////////////
    // Key generation
    ////////////////////////////////////////////////////////////////////////////

    let max_signers = 5;
    let min_signers = 3;
    let (shares, public_key_package): (
        BTreeMap<Identifier<C>, SecretShare<C>>,
        PublicKeyPackage<C>,
    ) = frost::keys::generate_with_dealer(
        max_signers,
        min_signers,
        frost::keys::IdentifierList::Default,
        &mut rng,
    )
    .unwrap();
    let key_packages = shares
        .into_iter()
        .map(|(id, share)| (id, share.try_into().unwrap()))
        .collect::<BTreeMap<Identifier<C>, KeyPackage<C>>>();

    // Try to recover a share

    // Signer 2 will lose their share
    // Signer 1, 4 and 5 will help signer 2 to recover their share

    let helper_1 = &key_packages[&Identifier::try_from(1).unwrap()];
    let helper_4 = &key_packages[&Identifier::try_from(4).unwrap()];
    let helper_5 = &key_packages[&Identifier::try_from(5).unwrap()];
    let participant = &key_packages[&Identifier::try_from(2).unwrap()];

    let helpers: [Identifier<C>; 3] = [
        helper_1.identifier,
        helper_4.identifier,
        helper_5.identifier,
    ];

    // Each helper generates random values for each helper

    let helper_1_deltas =
        repair_share_part1(&helpers, helper_1, &mut rng, participant.identifier).unwrap();
    let helper_4_deltas =
        repair_share_part1(&helpers, helper_4, &mut rng, participant.identifier).unwrap();
    let helper_5_deltas =
        repair_share_part1(&helpers, helper_5, &mut rng, participant.identifier).unwrap();

    // Each helper calculates their sigma from the random values received from the other helpers

    let helper_1_sigma: Sigma<C> = repair_share_part2::<C>(&[
        helper_1_deltas[&helpers[0]],
        helper_4_deltas[&helpers[0]],
        helper_5_deltas[&helpers[0]],
    ]);
    let helper_4_sigma: Sigma<C> = repair_share_part2::<C>(&[
        helper_1_deltas[&helpers[1]],
        helper_4_deltas[&helpers[1]],
        helper_5_deltas[&helpers[1]],
    ]);
    let helper_5_sigma: Sigma<C> = repair_share_part2::<C>(&[
        helper_1_deltas[&helpers[2]],
        helper_4_deltas[&helpers[2]],
        helper_5_deltas[&helpers[2]],
    ]);

    // The participant wishing to recover their share sums the sigmas sent from all helpers

    let participant_recovered_share = repair_share_part3(
        &[helper_1_sigma, helper_4_sigma, helper_5_sigma],
        participant.identifier,
        &public_key_package,
    )
    .unwrap();

    assert!(participant.signing_share() == participant_recovered_share.signing_share())
}

fn generate_scalar_from_byte_string<C: Ciphersuite>(
    bs: &str,
) -> <<C::Group as Group>::Field as Field>::Scalar {
    let decoded = hex::decode(bs).unwrap();
    let out = <<C::Group as Group>::Field>::deserialize(
        &decoded.as_slice().try_into().debugless_unwrap(),
    );
    out.unwrap()
}

/// Test repair_share_part1
pub fn check_repair_share_part1<C: Ciphersuite, R: RngCore + CryptoRng>(mut rng: R) {
    // Compute shares

    let max_signers = 5;
    let min_signers = 3;
    let (shares, _pubkeys): (BTreeMap<Identifier<C>, SecretShare<C>>, PublicKeyPackage<C>) =
        frost::keys::generate_with_dealer(
            max_signers,
            min_signers,
            frost::keys::IdentifierList::Default,
            &mut rng,
        )
        .unwrap();
    let key_packages = shares
        .into_iter()
        .map(|(id, share)| (id, share.try_into().unwrap()))
        .collect::<BTreeMap<Identifier<C>, KeyPackage<C>>>();

    // Signer 2 will lose their share
    // Signers (helpers) 1, 4 and 5 will help signer 2 (participant) to recover their share

    let helper_1 = &key_packages[&Identifier::try_from(1).unwrap()];
    let helper_4 = &key_packages[&Identifier::try_from(4).unwrap()];
    let helper_5 = &key_packages[&Identifier::try_from(5).unwrap()];
    let participant = &key_packages[&Identifier::try_from(2).unwrap()];

    let helpers: [Identifier<C>; 3] = [
        helper_1.identifier,
        helper_4.identifier,
        helper_5.identifier,
    ];

    // Generate deltas for helper 4
    let deltas = repair_share_part1(&helpers, helper_4, &mut rng, participant.identifier).unwrap();

    let lagrange_coefficient = compute_lagrange_coefficient(
        &helpers.iter().cloned().collect(),
        Some(participant.identifier),
        helpers[1],
    )
    .unwrap();

    let mut rhs = <<C::Group as Group>::Field>::zero();
    for (_k, v) in deltas {
        rhs = rhs + v.to_scalar();
    }

    let lhs = lagrange_coefficient * helper_4.signing_share.to_scalar();

    assert!(lhs == rhs)
}

/// Test repair_share_part2
pub fn check_repair_share_part2<C: Ciphersuite>(repair_share_helpers: &Value) {
    let values = &repair_share_helpers["scalar_generation"];

    let value_1 = Delta::new(generate_scalar_from_byte_string::<C>(
        values["random_scalar_1"].as_str().unwrap(),
    ));
    let value_2 = Delta::new(generate_scalar_from_byte_string::<C>(
        values["random_scalar_2"].as_str().unwrap(),
    ));
    let value_3 = Delta::new(generate_scalar_from_byte_string::<C>(
        values["random_scalar_3"].as_str().unwrap(),
    ));
    let expected = repair_share_part2::<C>(&[value_1, value_2, value_3]);

    let actual = Sigma::new(generate_scalar_from_byte_string::<C>(
        values["random_scalar_sum"].as_str().unwrap(),
    ));

    assert!(actual == expected);
}

/// Test repair_share_part3
pub fn check_repair_share_part3<C: Ciphersuite, R: RngCore + CryptoRng>(
    mut rng: R,
    repair_share_helpers: &Value,
) {
    // We need a dummy public key package to call the function
    let max_signers = 5;
    let min_signers = 3;
    let (_shares, public_key_package): (
        BTreeMap<Identifier<C>, SecretShare<C>>,
        PublicKeyPackage<C>,
    ) = frost::keys::generate_with_dealer(
        max_signers,
        min_signers,
        frost::keys::IdentifierList::Default,
        &mut rng,
    )
    .unwrap();

    let sigmas: &Value = &repair_share_helpers["sigma_generation"];

    let sigma_1 = Sigma::new(generate_scalar_from_byte_string::<C>(
        sigmas["sigma_1"].as_str().unwrap(),
    ));
    let sigma_2 = Sigma::new(generate_scalar_from_byte_string::<C>(
        sigmas["sigma_2"].as_str().unwrap(),
    ));
    let sigma_3 = Sigma::new(generate_scalar_from_byte_string::<C>(
        sigmas["sigma_3"].as_str().unwrap(),
    ));
    let sigma_4 = Sigma::new(generate_scalar_from_byte_string::<C>(
        sigmas["sigma_4"].as_str().unwrap(),
    ));

    let actual = repair_share_part3::<C>(
        &[sigma_1, sigma_2, sigma_3, sigma_4],
        Identifier::try_from(2).unwrap(),
        &public_key_package,
    )
    .unwrap();

    let expected: <<C::Group as Group>::Field as Field>::Scalar =
        generate_scalar_from_byte_string::<C>(sigmas["sigma_sum"].as_str().unwrap());

    assert!(expected == actual.signing_share().to_scalar());
}

/// Test repair share part 1 fails with invalid numbers of signers.
pub fn check_repair_share_part1_fails_with_invalid_min_signers<
    C: Ciphersuite,
    R: RngCore + CryptoRng,
>(
    mut rng: R,
) {
    // Generate shares
    let max_signers = 3;
    let min_signers = 2; // This is to make sure this test fails at the right point
    let (shares, _pubkeys): (BTreeMap<Identifier<C>, SecretShare<C>>, PublicKeyPackage<C>) =
        frost::keys::generate_with_dealer(
            max_signers,
            min_signers,
            frost::keys::IdentifierList::Default,
            &mut rng,
        )
        .unwrap();
    let key_packages = shares
        .into_iter()
        .map(|(id, share)| (id, share.try_into().unwrap()))
        .collect::<BTreeMap<Identifier<C>, KeyPackage<C>>>();

    let helper = Identifier::try_from(3).unwrap();

    let out = repair_share_part1(
        &[helper],
        &key_packages[&helper],
        &mut rng,
        Identifier::try_from(2).unwrap(),
    );

    assert!(out.is_err());
    assert!(out == Err(Error::IncorrectNumberOfIdentifiers))
}