Add unit tests
This commit is contained in:
@@ -2,3 +2,6 @@ pub mod group;
|
||||
pub mod hash;
|
||||
pub mod state;
|
||||
pub mod util;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
@@ -0,0 +1,457 @@
|
||||
// Unit tests for the ecc module.
|
||||
|
||||
#[cfg(test)]
|
||||
mod util_tests {
|
||||
use crate::ecc::util::*;
|
||||
use k256::{ProjectivePoint, Scalar, U256};
|
||||
|
||||
fn s(hex: &str) -> [u8; 32] {
|
||||
let b = hex::decode(hex).unwrap();
|
||||
b.try_into().unwrap()
|
||||
}
|
||||
|
||||
fn p(hex: &str) -> [u8; 33] {
|
||||
let b = hex::decode(hex).unwrap();
|
||||
b.try_into().unwrap()
|
||||
}
|
||||
|
||||
// ── mod_n ────────────────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn mod_n_zero_is_zero() {
|
||||
assert_eq!(mod_n(U256::ZERO), Scalar::ZERO);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mod_n_one_is_one() {
|
||||
assert_eq!(mod_n(U256::ONE), Scalar::ONE);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mod_n_reduces_n_to_zero() {
|
||||
// N mod N = 0
|
||||
assert_eq!(mod_n(N), Scalar::ZERO);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mod_n_reduces_n_plus_one_to_one() {
|
||||
let n_plus_one = N.wrapping_add(&U256::ONE);
|
||||
assert_eq!(mod_n(n_plus_one), Scalar::ONE);
|
||||
}
|
||||
|
||||
// ── scalar_from_bytes / scalar_to_bytes ──────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn scalar_roundtrip() {
|
||||
let bytes = s("0e74411caa9ef5ba058d0ccf4e54e1ee773e625fb7d6258097466cedab0de152");
|
||||
let scalar = scalar_from_bytes(&bytes);
|
||||
assert_eq!(scalar_to_bytes(&scalar), bytes);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scalar_from_zero_bytes() {
|
||||
let bytes = [0u8; 32];
|
||||
assert_eq!(scalar_from_bytes(&bytes), Scalar::ZERO);
|
||||
}
|
||||
|
||||
// ── pow_n ────────────────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn pow_n_exp_zero_is_one() {
|
||||
assert_eq!(pow_n(5, 0), Scalar::ONE);
|
||||
assert_eq!(pow_n(0, 0), Scalar::ONE);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pow_n_exp_one_is_base() {
|
||||
let three = scalar_from_bytes(&{
|
||||
let mut b = [0u8; 32];
|
||||
b[31] = 3;
|
||||
b
|
||||
});
|
||||
assert_eq!(pow_n(3, 1), three);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pow_n_three_to_four() {
|
||||
// 3^4 = 81 = 0x51
|
||||
let expected = scalar_from_bytes(&{
|
||||
let mut b = [0u8; 32];
|
||||
b[31] = 81;
|
||||
b
|
||||
});
|
||||
assert_eq!(pow_n(3, 4), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pow_n_two_to_eight() {
|
||||
// 2^8 = 256 = 0x0100
|
||||
let expected = scalar_from_bytes(&{
|
||||
let mut b = [0u8; 32];
|
||||
b[30] = 1;
|
||||
b[31] = 0;
|
||||
b
|
||||
});
|
||||
assert_eq!(pow_n(2, 8), expected);
|
||||
}
|
||||
|
||||
// ── lift_x ───────────────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn lift_x_32_bytes_gives_even_y_point() {
|
||||
// x-only: strip prefix from known compressed point
|
||||
let x_only =
|
||||
hex::decode("1ae63bc9ddaffe52d44c3018e83115bfb22195bd8112fcad112310714e6fd5ec")
|
||||
.unwrap();
|
||||
let pt = lift_x(&x_only).unwrap();
|
||||
assert!(has_even_y(&pt));
|
||||
let serialized = serialize_point(&pt);
|
||||
assert_eq!(serialized[0], 0x02);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lift_x_33_bytes_compressed() {
|
||||
let compressed = p("021ae63bc9ddaffe52d44c3018e83115bfb22195bd8112fcad112310714e6fd5ec");
|
||||
let pt = lift_x(&compressed).unwrap();
|
||||
assert!(has_even_y(&pt));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lift_x_odd_prefix_still_works() {
|
||||
// 0x03 prefix = odd Y; lift_x should still decode it
|
||||
let odd = p("034bc9f2ef5cc5eb741cc00d763e1077e8bc624df82d198781c71a0757617d8d44");
|
||||
let pt = lift_x(&odd).unwrap();
|
||||
// The point itself has odd Y
|
||||
assert!(!has_even_y(&pt));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lift_x_invalid_bytes_errors() {
|
||||
assert!(lift_x(&[0u8; 31]).is_err());
|
||||
assert!(lift_x(&[0u8; 34]).is_err());
|
||||
// All-zero 32 bytes is not a valid x-coordinate
|
||||
assert!(lift_x(&[0u8; 32]).is_err());
|
||||
}
|
||||
|
||||
// ── serialize / deserialize ──────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn serialize_deserialize_roundtrip() {
|
||||
let pt = ProjectivePoint::GENERATOR;
|
||||
let bytes = serialize_point(&pt);
|
||||
let recovered = deserialize_point(&bytes).unwrap();
|
||||
assert_eq!(pt, recovered);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generator_serializes_correctly() {
|
||||
let pt = ProjectivePoint::GENERATOR;
|
||||
let bytes = serialize_point(&pt);
|
||||
assert_eq!(
|
||||
hex::encode(bytes),
|
||||
"0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"
|
||||
);
|
||||
}
|
||||
|
||||
// ── has_even_y / negate_point ────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn generator_has_even_y() {
|
||||
assert!(has_even_y(&ProjectivePoint::GENERATOR));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn negate_flips_parity() {
|
||||
let pt = ProjectivePoint::GENERATOR;
|
||||
let neg = negate_point(&pt);
|
||||
assert!(!has_even_y(&neg));
|
||||
// Double negation is identity
|
||||
assert_eq!(negate_point(&neg), pt);
|
||||
}
|
||||
|
||||
// ── scalar_invert ────────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn scalar_invert_of_one_is_one() {
|
||||
let inv = scalar_invert(&Scalar::ONE).unwrap();
|
||||
assert_eq!(inv, Scalar::ONE);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scalar_invert_of_zero_errors() {
|
||||
assert!(scalar_invert(&Scalar::ZERO).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scalar_invert_roundtrip() {
|
||||
let a = scalar_from_bytes(&s(
|
||||
"0e74411caa9ef5ba058d0ccf4e54e1ee773e625fb7d6258097466cedab0de152",
|
||||
));
|
||||
let inv = scalar_invert(&a).unwrap();
|
||||
assert_eq!(a * inv, Scalar::ONE);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod group_tests {
|
||||
use crate::ecc::group::*;
|
||||
use k256::Scalar;
|
||||
|
||||
fn p(hex: &str) -> [u8; 33] {
|
||||
let b = hex::decode(hex).unwrap();
|
||||
b.try_into().unwrap()
|
||||
}
|
||||
|
||||
// ── scalar_base_multi ────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn scalar_base_multi_one_is_generator() {
|
||||
let g = scalar_base_multi(&Scalar::ONE);
|
||||
assert_eq!(
|
||||
hex::encode(serialize_element(&g)),
|
||||
"0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scalar_base_multi_known_value() {
|
||||
// 7*G
|
||||
let seven = crate::ecc::util::scalar_from_bytes(&{
|
||||
let mut b = [0u8; 32];
|
||||
b[31] = 7;
|
||||
b
|
||||
});
|
||||
let pt = scalar_base_multi(&seven);
|
||||
assert_eq!(
|
||||
hex::encode(serialize_element(&pt)),
|
||||
"025cbdf0646e5db4eaa398f365f2ea7a0e3d419b7e0330e39ce92bddedcac4f9bc"
|
||||
);
|
||||
}
|
||||
|
||||
// ── element_add ──────────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn element_add_2g_plus_3g_equals_5g() {
|
||||
let two = crate::ecc::util::scalar_from_bytes(&{
|
||||
let mut b = [0u8; 32];
|
||||
b[31] = 2;
|
||||
b
|
||||
});
|
||||
let three = crate::ecc::util::scalar_from_bytes(&{
|
||||
let mut b = [0u8; 32];
|
||||
b[31] = 3;
|
||||
b
|
||||
});
|
||||
let five = crate::ecc::util::scalar_from_bytes(&{
|
||||
let mut b = [0u8; 32];
|
||||
b[31] = 5;
|
||||
b
|
||||
});
|
||||
let a = scalar_base_multi(&two);
|
||||
let b = scalar_base_multi(&three);
|
||||
let c = element_add(Some(a), Some(b)).unwrap();
|
||||
let expected = scalar_base_multi(&five);
|
||||
assert_eq!(
|
||||
hex::encode(serialize_element(&c)),
|
||||
hex::encode(serialize_element(&expected))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn element_add_none_left_returns_right() {
|
||||
let g = scalar_base_multi(&Scalar::ONE);
|
||||
let result = element_add(None, Some(g)).unwrap();
|
||||
assert_eq!(serialize_element(&result), serialize_element(&g));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn element_add_none_right_returns_left() {
|
||||
let g = scalar_base_multi(&Scalar::ONE);
|
||||
let result = element_add(Some(g), None).unwrap();
|
||||
assert_eq!(serialize_element(&result), serialize_element(&g));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn element_add_both_none_errors() {
|
||||
assert!(element_add(None, None).is_err());
|
||||
}
|
||||
|
||||
// ── scalar_multi ─────────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn scalar_multi_matches_base_multi() {
|
||||
let seven = crate::ecc::util::scalar_from_bytes(&{
|
||||
let mut b = [0u8; 32];
|
||||
b[31] = 7;
|
||||
b
|
||||
});
|
||||
let g = scalar_base_multi(&Scalar::ONE);
|
||||
let result = scalar_multi(&g, &seven);
|
||||
let expected = scalar_base_multi(&seven);
|
||||
assert_eq!(serialize_element(&result), serialize_element(&expected));
|
||||
}
|
||||
|
||||
// ── serialize_scalar_u32 ─────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn serialize_scalar_u32_zero() {
|
||||
assert_eq!(serialize_scalar_u32(0), [0u8; 32]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize_scalar_u32_one() {
|
||||
let mut expected = [0u8; 32];
|
||||
expected[31] = 1;
|
||||
assert_eq!(serialize_scalar_u32(1), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize_scalar_u32_large() {
|
||||
let mut expected = [0u8; 32];
|
||||
expected[28..].copy_from_slice(&0x01020304u32.to_be_bytes());
|
||||
assert_eq!(serialize_scalar_u32(0x01020304), expected);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod hash_tests {
|
||||
use crate::ecc::hash::*;
|
||||
|
||||
// All expected values verified against the cmdruid/frost TypeScript implementation.
|
||||
|
||||
#[test]
|
||||
fn h1_empty_input() {
|
||||
let result = h1(&[]);
|
||||
assert_eq!(
|
||||
hex::encode(result),
|
||||
"28d6cedb3fba18f85dbb373d8b01328464bf020f6ad651d877998f70f2980fd0"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn h2_empty_input() {
|
||||
let result = h2(&[]);
|
||||
assert_eq!(
|
||||
hex::encode(result),
|
||||
"ac6482b13a7f3ae0cbb38157c557f1857bf480c77c63e19e73e8070d98c86fc3"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn h3_empty_input() {
|
||||
let result = h3(&[]);
|
||||
assert_eq!(
|
||||
hex::encode(result),
|
||||
"2da9f94fe1dde8b15546272af0cad7c8ba7c4a58e8ec7087521055161c8fbc03"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn h3_known_input() {
|
||||
// generate_nonce(share[1].seckey, hidden_seed) from the fixture
|
||||
let input = hex::decode(
|
||||
"0070ca75929ca1ec4cd70ac34f46079bdfdd87f9d0c0bf4275f3882f7b462d0f\
|
||||
0e74411caa9ef5ba058d0ccf4e54e1ee773e625fb7d6258097466cedab0de152",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
hex::encode(h3(&input)),
|
||||
"189aeb1bf3a453673cb144a459f0b644183ff02808cad807b672067da4f33357"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn h4_empty_input() {
|
||||
let result = h4(&[]);
|
||||
assert_eq!(
|
||||
hex::encode(result),
|
||||
"578967b1c52aeeb9d8c64aa02fbe2cf7f171b58f2547dbeb349be5eff9ba1549"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn h4_known_input() {
|
||||
// "test" in hex = 74657374
|
||||
let input = hex::decode("74657374").unwrap();
|
||||
assert_eq!(
|
||||
hex::encode(h4(&input)),
|
||||
"ff9b5210ffbb3c07a73a7c8935be4a8c62cf015f6cf7ade6efac09a6513540fc"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn h5_known_input() {
|
||||
// Vector from hash.json
|
||||
let input = hex::decode(
|
||||
"000000000000000000000000000000000000000000000000000000000000000103c699af97d26bb4d3f05232ec5e1938c12f1e6ae97643c8f8f11c9820303f190402fa2aaccd51b948c9dc1a325d77226e98a5a3fe65fe9ba213761a60123040a45e000000000000000000000000000000000000000000000000000000000000000303077507ba327fc074d2793955ef3410ee3f03b82b4cdc2370f71d865beb926ef602ad53031ddfbbacfc5fbda3d3b0c2445c8e3e99cbc4ca2db2aa283fa68525b135",
|
||||
).unwrap();
|
||||
assert_eq!(
|
||||
hex::encode(h5(&input)),
|
||||
"3f5a816aaebc2114a811a415d7a55db7c5cbc1cf27183e79dd9def941b5d4801"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn h1_h2_h3_differ_for_same_input() {
|
||||
// Different DSTs must produce different outputs
|
||||
let input = b"test";
|
||||
let r1 = h1(input);
|
||||
let r2 = h2(input);
|
||||
let r3 = h3(input);
|
||||
assert_ne!(r1, r2);
|
||||
assert_ne!(r2, r3);
|
||||
assert_ne!(r1, r3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn h4_h5_differ_for_same_input() {
|
||||
let input = b"test";
|
||||
assert_ne!(h4(input), h5(input));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod state_tests {
|
||||
use crate::ecc::{group::serialize_element, state::get_point_state, util::lift_x};
|
||||
|
||||
fn h32(hex: &str) -> [u8; 32] {
|
||||
hex::decode(hex).unwrap().try_into().unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_tweaks_returns_identity_state() {
|
||||
let pt = lift_x(
|
||||
&hex::decode("1ae63bc9ddaffe52d44c3018e83115bfb22195bd8112fcad112310714e6fd5ec")
|
||||
.unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
let state = get_point_state(pt, &[]).unwrap();
|
||||
// No tweaks: point unchanged, parity = +1 (even Y), state = +1, tweak = 0
|
||||
use k256::Scalar;
|
||||
assert_eq!(state.parity, Scalar::ONE);
|
||||
assert_eq!(state.state, Scalar::ONE);
|
||||
assert_eq!(state.tweak, Scalar::ZERO);
|
||||
assert_eq!(
|
||||
hex::encode(serialize_element(&state.point)),
|
||||
"021ae63bc9ddaffe52d44c3018e83115bfb22195bd8112fcad112310714e6fd5ec"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tweaked_group_pk_matches_fixture() {
|
||||
// From the integration fixture: group_pk tweaked with aa...aa and bb...bb
|
||||
let group_pk =
|
||||
hex::decode("021ae63bc9ddaffe52d44c3018e83115bfb22195bd8112fcad112310714e6fd5ec")
|
||||
.unwrap();
|
||||
let pt = lift_x(&group_pk).unwrap();
|
||||
let tweaks = [
|
||||
h32("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"),
|
||||
h32("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"),
|
||||
];
|
||||
let state = get_point_state(pt, &tweaks).unwrap();
|
||||
assert_eq!(
|
||||
hex::encode(serialize_element(&state.point)),
|
||||
"025731d4d57552d12877e3db13061d7f6ca09198963e003d1d6e0960d6651e42d3"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
#[cfg(test)]
|
||||
mod ecdh_tests {
|
||||
use crate::ecdh::*;
|
||||
use crate::group::create_dealer_set;
|
||||
use crate::helpers::{generate_seckey, get_pubkey, tweak_pubkey};
|
||||
|
||||
fn s32(hex: &str) -> [u8; 32] {
|
||||
hex::decode(hex).unwrap().try_into().unwrap()
|
||||
}
|
||||
|
||||
fn p33(hex: &str) -> [u8; 33] {
|
||||
hex::decode(hex).unwrap().try_into().unwrap()
|
||||
}
|
||||
|
||||
const S0: &str = "0070ca75929ca1ec4cd70ac34f46079bdfdd87f9d0c0bf4275f3882f7b462d0f";
|
||||
const S1: &str = "0e0376a7180253cdb8b6020bff0eda529760da65e715663e2152e4be2fc7b443";
|
||||
|
||||
// ── create_ecdh_share ────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn create_ecdh_share_matches_fixture() {
|
||||
let secrets = [s32(S0), s32(S1)];
|
||||
let group = create_dealer_set(2, 3, &secrets).unwrap();
|
||||
|
||||
// demo_seckey = generate_seckey(Some(aa...aa))
|
||||
let demo_aux = s32("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
|
||||
let demo_seckey = generate_seckey(Some(&demo_aux));
|
||||
let demo_pubkey = get_pubkey(&demo_seckey);
|
||||
|
||||
let members = [1u32, 3u32];
|
||||
let ecdh1 = create_ecdh_share(&members, &group.shares[0], &demo_pubkey).unwrap();
|
||||
let ecdh3 = create_ecdh_share(&members, &group.shares[2], &demo_pubkey).unwrap();
|
||||
|
||||
assert_eq!(ecdh1.idx, 1);
|
||||
assert_eq!(ecdh3.idx, 3);
|
||||
assert_eq!(
|
||||
hex::encode(ecdh1.pubkey),
|
||||
"0386c5b0f4bace78ef17d02b09e339b5a39f659dbbf1f3f531b9825df6836cfea9"
|
||||
);
|
||||
assert_eq!(
|
||||
hex::encode(ecdh3.pubkey),
|
||||
"023edaf055945d35006e1c52dd7a388e0c10b36eb55aa9d117853af87903cb54c0"
|
||||
);
|
||||
}
|
||||
|
||||
// ── derive_ecdh_secret ───────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn derive_ecdh_secret_matches_master_secret() {
|
||||
// The FROST ECDH shared secret must equal secret_key * demo_pubkey
|
||||
// = demo_seckey * group_pubkey (commutativity of scalar mult).
|
||||
let secrets = [s32(S0), s32(S1)];
|
||||
let group = create_dealer_set(2, 3, &secrets).unwrap();
|
||||
|
||||
let demo_aux = s32("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
|
||||
let demo_seckey = generate_seckey(Some(&demo_aux));
|
||||
let demo_pubkey = get_pubkey(&demo_seckey);
|
||||
|
||||
let members = [1u32, 3u32];
|
||||
let ecdh1 = create_ecdh_share(&members, &group.shares[0], &demo_pubkey).unwrap();
|
||||
let ecdh3 = create_ecdh_share(&members, &group.shares[2], &demo_pubkey).unwrap();
|
||||
|
||||
let frost_secret = derive_ecdh_secret(&[ecdh1, ecdh3]).unwrap();
|
||||
|
||||
// master_shared_secret = group_pk * demo_seckey = tweak_pubkey(demo_pubkey, s0)
|
||||
let master_secret = tweak_pubkey(&demo_pubkey, &s32(S0)).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
hex::encode(frost_secret),
|
||||
hex::encode(master_secret),
|
||||
"FROST ECDH secret must match master shared secret"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn derive_ecdh_secret_matches_fixture() {
|
||||
let frost_secret = derive_ecdh_secret(&[
|
||||
crate::types::PublicShare {
|
||||
idx: 1,
|
||||
pubkey: p33("0386c5b0f4bace78ef17d02b09e339b5a39f659dbbf1f3f531b9825df6836cfea9"),
|
||||
},
|
||||
crate::types::PublicShare {
|
||||
idx: 3,
|
||||
pubkey: p33("023edaf055945d35006e1c52dd7a388e0c10b36eb55aa9d117853af87903cb54c0"),
|
||||
},
|
||||
])
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
hex::encode(frost_secret),
|
||||
"020b6417cef5530ed4b82681945d4565ea7027f423a97b60247d07386ca3619585"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn derive_ecdh_secret_empty_errors() {
|
||||
assert!(derive_ecdh_secret(&[]).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create_ecdh_share_invalid_pubkey_errors() {
|
||||
let share = crate::types::SecretShare {
|
||||
idx: 1,
|
||||
seckey: s32("0e74411caa9ef5ba058d0ccf4e54e1ee773e625fb7d6258097466cedab0de152"),
|
||||
};
|
||||
// All-zero pubkey is invalid
|
||||
assert!(create_ecdh_share(&[1u32, 2u32], &share, &[0u8; 33]).is_err());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
#[cfg(test)]
|
||||
mod group_tests {
|
||||
use crate::group::*;
|
||||
use crate::shares::verify_share;
|
||||
|
||||
fn s32(hex: &str) -> [u8; 32] {
|
||||
hex::decode(hex).unwrap().try_into().unwrap()
|
||||
}
|
||||
|
||||
const S0: &str = "0070ca75929ca1ec4cd70ac34f46079bdfdd87f9d0c0bf4275f3882f7b462d0f";
|
||||
const S1: &str = "0e0376a7180253cdb8b6020bff0eda529760da65e715663e2152e4be2fc7b443";
|
||||
|
||||
// ── create_share_set ─────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn create_share_set_correct_counts() {
|
||||
let secrets = [s32(S0), s32(S1)];
|
||||
let set = create_share_set(2, 3, &secrets).unwrap();
|
||||
assert_eq!(set.shares.len(), 3);
|
||||
assert_eq!(set.vss_commits.len(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create_share_set_shares_are_valid() {
|
||||
let secrets = [s32(S0), s32(S1)];
|
||||
let set = create_share_set(2, 3, &secrets).unwrap();
|
||||
for share in &set.shares {
|
||||
assert!(
|
||||
verify_share(&set.vss_commits, share, 2).unwrap(),
|
||||
"share {} should verify",
|
||||
share.idx
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create_share_set_vss_commits_match_fixture() {
|
||||
let secrets = [s32(S0), s32(S1)];
|
||||
let set = create_share_set(2, 3, &secrets).unwrap();
|
||||
assert_eq!(
|
||||
hex::encode(set.vss_commits[0]),
|
||||
"021ae63bc9ddaffe52d44c3018e83115bfb22195bd8112fcad112310714e6fd5ec"
|
||||
);
|
||||
assert_eq!(
|
||||
hex::encode(set.vss_commits[1]),
|
||||
"024f75a5478deda1102eba931e19425e59c1750533a54218ce215ced343fbfb6cf"
|
||||
);
|
||||
}
|
||||
|
||||
// ── create_dealer_set ────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn create_dealer_set_group_pk_is_first_commit() {
|
||||
let secrets = [s32(S0), s32(S1)];
|
||||
let set = create_dealer_set(2, 3, &secrets).unwrap();
|
||||
assert_eq!(set.group_pk, set.vss_commits[0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create_dealer_set_group_pk_matches_fixture() {
|
||||
let secrets = [s32(S0), s32(S1)];
|
||||
let set = create_dealer_set(2, 3, &secrets).unwrap();
|
||||
assert_eq!(
|
||||
hex::encode(set.group_pk),
|
||||
"021ae63bc9ddaffe52d44c3018e83115bfb22195bd8112fcad112310714e6fd5ec"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create_dealer_set_shares_match_fixture() {
|
||||
let secrets = [s32(S0), s32(S1)];
|
||||
let set = create_dealer_set(2, 3, &secrets).unwrap();
|
||||
assert_eq!(
|
||||
hex::encode(set.shares[0].seckey),
|
||||
"0e74411caa9ef5ba058d0ccf4e54e1ee773e625fb7d6258097466cedab0de152"
|
||||
);
|
||||
assert_eq!(
|
||||
hex::encode(set.shares[1].seckey),
|
||||
"1c77b7c3c2a14987be430edb4d63bc410e9f3cc59eeb8bbeb89951abdad59595"
|
||||
);
|
||||
assert_eq!(
|
||||
hex::encode(set.shares[2].seckey),
|
||||
"2a7b2e6adaa39d5576f910e74c729693a600172b8600f1fcd9ec366a0a9d49d8"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create_dealer_set_no_secrets_is_random() {
|
||||
let a = create_dealer_set(2, 3, &[]).unwrap();
|
||||
let b = create_dealer_set(2, 3, &[]).unwrap();
|
||||
// Random secrets → different group keys
|
||||
assert_ne!(a.group_pk, b.group_pk);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
#[cfg(test)]
|
||||
mod helpers_tests {
|
||||
use crate::helpers::*;
|
||||
|
||||
fn s32(hex: &str) -> [u8; 32] {
|
||||
hex::decode(hex).unwrap().try_into().unwrap()
|
||||
}
|
||||
|
||||
// ── generate_seckey ──────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn generate_seckey_deterministic_with_aux() {
|
||||
let aux = s32("0070ca75929ca1ec4cd70ac34f46079bdfdd87f9d0c0bf4275f3882f7b462d0f");
|
||||
let a = generate_seckey(Some(&aux));
|
||||
let b = generate_seckey(Some(&aux));
|
||||
assert_eq!(a, b);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generate_seckey_random_without_aux() {
|
||||
// Two calls without aux should (overwhelmingly) differ
|
||||
let a = generate_seckey(None);
|
||||
let b = generate_seckey(None);
|
||||
assert_ne!(a, b);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generate_seckey_is_h3_of_aux() {
|
||||
let aux = s32("0070ca75929ca1ec4cd70ac34f46079bdfdd87f9d0c0bf4275f3882f7b462d0f");
|
||||
let result = generate_seckey(Some(&aux));
|
||||
let expected = crate::ecc::hash::h3(&aux);
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
// ── generate_nonce ───────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn generate_nonce_deterministic_with_seed() {
|
||||
let secret = s32("0e74411caa9ef5ba058d0ccf4e54e1ee773e625fb7d6258097466cedab0de152");
|
||||
let seed = s32("0070ca75929ca1ec4cd70ac34f46079bdfdd87f9d0c0bf4275f3882f7b462d0f");
|
||||
let a = generate_nonce(&secret, Some(&seed));
|
||||
let b = generate_nonce(&secret, Some(&seed));
|
||||
assert_eq!(a, b);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generate_nonce_matches_fixture() {
|
||||
// hidden_sn for share[1]: generate_nonce(share1.seckey, hidden_seed)
|
||||
let secret = s32("0e74411caa9ef5ba058d0ccf4e54e1ee773e625fb7d6258097466cedab0de152");
|
||||
let seed = s32("0070ca75929ca1ec4cd70ac34f46079bdfdd87f9d0c0bf4275f3882f7b462d0f");
|
||||
let nonce = generate_nonce(&secret, Some(&seed));
|
||||
assert_eq!(
|
||||
hex::encode(nonce),
|
||||
"189aeb1bf3a453673cb144a459f0b644183ff02808cad807b672067da4f33357"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generate_nonce_random_without_seed() {
|
||||
let secret = s32("0e74411caa9ef5ba058d0ccf4e54e1ee773e625fb7d6258097466cedab0de152");
|
||||
let a = generate_nonce(&secret, None);
|
||||
let b = generate_nonce(&secret, None);
|
||||
assert_ne!(a, b);
|
||||
}
|
||||
|
||||
// ── get_pubkey ───────────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn get_pubkey_matches_fixture() {
|
||||
let seckey = s32("0e74411caa9ef5ba058d0ccf4e54e1ee773e625fb7d6258097466cedab0de152");
|
||||
let pubkey = get_pubkey(&seckey);
|
||||
assert_eq!(
|
||||
hex::encode(pubkey),
|
||||
"0278f55809a11a1016d13ec4f54674810abe4a6fec8b586e14f90d0c1f80de33eb"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_pubkey_is_compressed_33_bytes() {
|
||||
let seckey = s32("0e74411caa9ef5ba058d0ccf4e54e1ee773e625fb7d6258097466cedab0de152");
|
||||
let pubkey = get_pubkey(&seckey);
|
||||
assert!(pubkey[0] == 0x02 || pubkey[0] == 0x03);
|
||||
}
|
||||
|
||||
// ── tweak_seckey ─────────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn tweak_seckey_by_one_is_identity() {
|
||||
let seckey = s32("0e74411caa9ef5ba058d0ccf4e54e1ee773e625fb7d6258097466cedab0de152");
|
||||
let one = s32("0000000000000000000000000000000000000000000000000000000000000001");
|
||||
assert_eq!(tweak_seckey(&seckey, &one), seckey);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tweak_seckey_doubles_with_two() {
|
||||
use crate::ecc::util::{scalar_from_bytes, scalar_to_bytes};
|
||||
let seckey = s32("0e74411caa9ef5ba058d0ccf4e54e1ee773e625fb7d6258097466cedab0de152");
|
||||
let two = s32("0000000000000000000000000000000000000000000000000000000000000002");
|
||||
let tweaked = tweak_seckey(&seckey, &two);
|
||||
let expected = scalar_to_bytes(&(scalar_from_bytes(&seckey) + scalar_from_bytes(&seckey)));
|
||||
assert_eq!(tweaked, expected);
|
||||
}
|
||||
|
||||
// ── tweak_pubkey ─────────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn tweak_pubkey_by_one_is_identity() {
|
||||
let pubkey =
|
||||
hex::decode("021ae63bc9ddaffe52d44c3018e83115bfb22195bd8112fcad112310714e6fd5ec")
|
||||
.unwrap();
|
||||
let one = s32("0000000000000000000000000000000000000000000000000000000000000001");
|
||||
let tweaked = tweak_pubkey(&pubkey, &one).unwrap();
|
||||
assert_eq!(hex::encode(tweaked), hex::encode(&pubkey));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tweak_pubkey_invalid_input_errors() {
|
||||
let one = s32("0000000000000000000000000000000000000000000000000000000000000001");
|
||||
assert!(tweak_pubkey(&[0u8; 31], &one).is_err());
|
||||
}
|
||||
|
||||
// ── get_challenge ────────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn get_challenge_matches_fixture() {
|
||||
use crate::ecc::util::scalar_to_bytes;
|
||||
// From the fixture signing context
|
||||
let group_pn =
|
||||
hex::decode("03e76328e49c27c12392a117d39ef9f5def368590d5e72438907fb63c1006fd589")
|
||||
.unwrap();
|
||||
let group_pk =
|
||||
hex::decode("025731d4d57552d12877e3db13061d7f6ca09198963e003d1d6e0960d6651e42d3")
|
||||
.unwrap();
|
||||
let message = hex::decode("68656c6c6f20776f726c6421").unwrap();
|
||||
let challenge = get_challenge(&group_pn, &group_pk, &message).unwrap();
|
||||
assert_eq!(
|
||||
hex::encode(scalar_to_bytes(&challenge)),
|
||||
"99e6637f68e223b0f6b4caa36b48cc277bf036ece4f14bab657200b43ecb0d55"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_challenge_invalid_pubkey_errors() {
|
||||
assert!(get_challenge(&[0u8; 31], &[0u8; 33], &[]).is_err());
|
||||
assert!(get_challenge(&[0u8; 33], &[0u8; 31], &[]).is_err());
|
||||
}
|
||||
|
||||
// ── convert_pubkey_to_bip340 / convert_pubkey_to_ecdsa ──────────────────
|
||||
|
||||
#[test]
|
||||
fn convert_to_bip340_strips_prefix() {
|
||||
let compressed =
|
||||
hex::decode("021ae63bc9ddaffe52d44c3018e83115bfb22195bd8112fcad112310714e6fd5ec")
|
||||
.unwrap();
|
||||
let bip340 = convert_pubkey_to_bip340(&compressed).unwrap();
|
||||
assert_eq!(bip340.len(), 32);
|
||||
assert_eq!(bip340, compressed[1..]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn convert_to_bip340_passthrough_32() {
|
||||
let x_only =
|
||||
hex::decode("1ae63bc9ddaffe52d44c3018e83115bfb22195bd8112fcad112310714e6fd5ec")
|
||||
.unwrap();
|
||||
let bip340 = convert_pubkey_to_bip340(&x_only).unwrap();
|
||||
assert_eq!(bip340, x_only);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn convert_to_bip340_invalid_length_errors() {
|
||||
assert!(convert_pubkey_to_bip340(&[0u8; 31]).is_err());
|
||||
assert!(convert_pubkey_to_bip340(&[0u8; 34]).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn convert_to_ecdsa_prepends_prefix() {
|
||||
let x_only =
|
||||
hex::decode("1ae63bc9ddaffe52d44c3018e83115bfb22195bd8112fcad112310714e6fd5ec")
|
||||
.unwrap();
|
||||
let ecdsa = convert_pubkey_to_ecdsa(&x_only).unwrap();
|
||||
assert_eq!(ecdsa.len(), 33);
|
||||
assert_eq!(ecdsa[0], 0x02);
|
||||
assert_eq!(&ecdsa[1..], x_only.as_slice());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn convert_to_ecdsa_passthrough_33() {
|
||||
let compressed =
|
||||
hex::decode("021ae63bc9ddaffe52d44c3018e83115bfb22195bd8112fcad112310714e6fd5ec")
|
||||
.unwrap();
|
||||
let ecdsa = convert_pubkey_to_ecdsa(&compressed).unwrap();
|
||||
assert_eq!(ecdsa, compressed);
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,23 @@ pub mod shares;
|
||||
pub mod sign;
|
||||
pub mod vss;
|
||||
|
||||
#[cfg(test)]
|
||||
mod ecdh_tests;
|
||||
#[cfg(test)]
|
||||
mod group_tests;
|
||||
#[cfg(test)]
|
||||
mod helpers_tests;
|
||||
#[cfg(test)]
|
||||
mod poly_tests;
|
||||
#[cfg(test)]
|
||||
mod recover_tests;
|
||||
#[cfg(test)]
|
||||
mod refresh_tests;
|
||||
#[cfg(test)]
|
||||
mod shares_tests;
|
||||
#[cfg(test)]
|
||||
mod vss_tests;
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
#[cfg(test)]
|
||||
mod poly_tests {
|
||||
use crate::ecc::util::scalar_from_bytes;
|
||||
use crate::poly::*;
|
||||
use k256::Scalar;
|
||||
|
||||
fn sc(n: u8) -> Scalar {
|
||||
scalar_from_bytes(&{
|
||||
let mut b = [0u8; 32];
|
||||
b[31] = n;
|
||||
b
|
||||
})
|
||||
}
|
||||
|
||||
fn sc32(hex: &str) -> Scalar {
|
||||
scalar_from_bytes(&hex::decode(hex).unwrap().try_into().unwrap())
|
||||
}
|
||||
|
||||
// ── evaluate_x ───────────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn evaluate_x_constant_polynomial() {
|
||||
// f(x) = 7 → f(3) = 7
|
||||
let coeffs = [sc(7)];
|
||||
assert_eq!(evaluate_x(&coeffs, sc(3)).unwrap(), sc(7));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn evaluate_x_linear() {
|
||||
// f(x) = 3 + 2x → f(1) = 5, f(2) = 7
|
||||
let coeffs = [sc(3), sc(2)];
|
||||
assert_eq!(evaluate_x(&coeffs, sc(1)).unwrap(), sc(5));
|
||||
assert_eq!(evaluate_x(&coeffs, sc(2)).unwrap(), sc(7));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn evaluate_x_quadratic() {
|
||||
// f(x) = 3 + 2x + x^2 → f(1) = 6, f(2) = 11
|
||||
let coeffs = [sc(3), sc(2), sc(1)];
|
||||
assert_eq!(evaluate_x(&coeffs, sc(1)).unwrap(), sc(6));
|
||||
assert_eq!(evaluate_x(&coeffs, sc(2)).unwrap(), sc(11));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn evaluate_x_at_zero_errors() {
|
||||
let coeffs = [sc(1), sc(2)];
|
||||
assert!(evaluate_x(&coeffs, Scalar::ZERO).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn evaluate_x_matches_fixture_shares() {
|
||||
// From the fixture: coeffs = [s0, s1], share[1].seckey and share[2].seckey
|
||||
let s0 = sc32("0070ca75929ca1ec4cd70ac34f46079bdfdd87f9d0c0bf4275f3882f7b462d0f");
|
||||
let s1 = sc32("0e0376a7180253cdb8b6020bff0eda529760da65e715663e2152e4be2fc7b443");
|
||||
let coeffs = [s0, s1];
|
||||
|
||||
let share1 = evaluate_x(&coeffs, index_to_scalar(1)).unwrap();
|
||||
let share2 = evaluate_x(&coeffs, index_to_scalar(2)).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
hex::encode(share1.to_bytes()),
|
||||
"0e74411caa9ef5ba058d0ccf4e54e1ee773e625fb7d6258097466cedab0de152"
|
||||
);
|
||||
assert_eq!(
|
||||
hex::encode(share2.to_bytes()),
|
||||
"1c77b7c3c2a14987be430edb4d63bc410e9f3cc59eeb8bbeb89951abdad59595"
|
||||
);
|
||||
}
|
||||
|
||||
// ── interpolate_root ─────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn interpolate_root_recovers_secret() {
|
||||
// secret=7, f(x) = 7 + 3x → f(1)=10, f(2)=13
|
||||
let points = [(sc(1), sc(10)), (sc(2), sc(13))];
|
||||
let secret = interpolate_root(&points).unwrap();
|
||||
assert_eq!(secret, sc(7));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn interpolate_root_single_point() {
|
||||
// f(x) = 5 (constant) → f(1) = 5
|
||||
let points = [(sc(1), sc(5))];
|
||||
let secret = interpolate_root(&points).unwrap();
|
||||
assert_eq!(secret, sc(5));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn interpolate_root_recovers_fixture_secret() {
|
||||
// From the fixture: shares[0] and shares[1] should recover s0
|
||||
let s0 = sc32("0070ca75929ca1ec4cd70ac34f46079bdfdd87f9d0c0bf4275f3882f7b462d0f");
|
||||
let share1 = sc32("0e74411caa9ef5ba058d0ccf4e54e1ee773e625fb7d6258097466cedab0de152");
|
||||
let share2 = sc32("1c77b7c3c2a14987be430edb4d63bc410e9f3cc59eeb8bbeb89951abdad59595");
|
||||
let points = [(index_to_scalar(1), share1), (index_to_scalar(2), share2)];
|
||||
assert_eq!(interpolate_root(&points).unwrap(), s0);
|
||||
}
|
||||
|
||||
// ── interpolate_x ────────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn interpolate_x_not_in_set_errors() {
|
||||
let l = [sc(1), sc(2)];
|
||||
assert!(interpolate_x(&l, sc(3)).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn interpolate_x_duplicate_errors() {
|
||||
let l = [sc(1), sc(1)];
|
||||
assert!(interpolate_x(&l, sc(1)).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn interpolate_x_single_element() {
|
||||
// L = {1}, x = 1: numerator = 1 (empty product), denominator = 1 → result = 1
|
||||
let l = [sc(1)];
|
||||
assert_eq!(interpolate_x(&l, sc(1)).unwrap(), Scalar::ONE);
|
||||
}
|
||||
|
||||
// ── calc_lagrange_coeff ──────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn calc_lagrange_coeff_duplicate_errors() {
|
||||
let l = [sc(1), sc(1)];
|
||||
assert!(calc_lagrange_coeff(&l, sc(1), Scalar::ZERO).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn calc_lagrange_coeff_two_party_at_zero() {
|
||||
// L = {1, 2}, P = 1, x = 0
|
||||
// numerator = (0 - 2) = -2
|
||||
// denominator = (1 - 2) = -1
|
||||
// result = (-2)/(-1) = 2
|
||||
let l = [sc(1), sc(2)];
|
||||
let coeff = calc_lagrange_coeff(&l, sc(1), Scalar::ZERO).unwrap();
|
||||
assert_eq!(coeff, sc(2));
|
||||
}
|
||||
|
||||
// ── index_to_scalar ──────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn index_to_scalar_one() {
|
||||
assert_eq!(index_to_scalar(1), Scalar::ONE);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn index_to_scalar_zero() {
|
||||
assert_eq!(index_to_scalar(0), Scalar::ZERO);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn index_to_scalar_large() {
|
||||
let expected = scalar_from_bytes(&{
|
||||
let mut b = [0u8; 32];
|
||||
b[28..].copy_from_slice(&255u32.to_be_bytes());
|
||||
b
|
||||
});
|
||||
assert_eq!(index_to_scalar(255), expected);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
#[cfg(test)]
|
||||
mod recover_tests {
|
||||
use crate::group::create_dealer_set;
|
||||
use crate::recover::*;
|
||||
use crate::types::SecretShare;
|
||||
|
||||
fn s32(hex: &str) -> [u8; 32] {
|
||||
hex::decode(hex).unwrap().try_into().unwrap()
|
||||
}
|
||||
|
||||
const S0: &str = "0070ca75929ca1ec4cd70ac34f46079bdfdd87f9d0c0bf4275f3882f7b462d0f";
|
||||
const S1: &str = "0e0376a7180253cdb8b6020bff0eda529760da65e715663e2152e4be2fc7b443";
|
||||
|
||||
// ── gen_recovery_shares ──────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn gen_recovery_shares_correct_structure() {
|
||||
let secrets = [s32(S0), s32(S1)];
|
||||
let group = create_dealer_set(2, 3, &secrets).unwrap();
|
||||
let members = [2u32, 3u32];
|
||||
let pkg = gen_recovery_shares(&members, &group.shares[1], 1, 2, &[]).unwrap();
|
||||
assert_eq!(pkg.idx, 2); // originating share's idx
|
||||
assert_eq!(pkg.shares.len(), 2); // one per member
|
||||
// vss_commits = rand_coeffs (threshold-1) + repair_coeff = threshold total
|
||||
assert_eq!(pkg.vss_commits.len(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gen_recovery_shares_not_enough_members_errors() {
|
||||
let secrets = [s32(S0), s32(S1)];
|
||||
let group = create_dealer_set(2, 3, &secrets).unwrap();
|
||||
// Only 1 member but threshold=2
|
||||
assert!(gen_recovery_shares(&[2u32], &group.shares[1], 1, 2, &[]).is_err());
|
||||
}
|
||||
|
||||
// ── recover_share ────────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn recover_share_reconstructs_lost_share() {
|
||||
// Members 2 and 3 help recover share for target=1.
|
||||
// Protocol: each helper generates recovery shares, then target sums
|
||||
// the aggregated shares it receives.
|
||||
let secrets = [s32(S0), s32(S1)];
|
||||
let group = create_dealer_set(2, 3, &secrets).unwrap();
|
||||
let members = [2u32, 3u32];
|
||||
|
||||
let pkg2 = gen_recovery_shares(&members, &group.shares[1], 1, 2, &[s32(S0)]).unwrap();
|
||||
let pkg3 = gen_recovery_shares(&members, &group.shares[2], 1, 2, &[s32(S1)]).unwrap();
|
||||
|
||||
// Each member aggregates the shares they received from others at their own index.
|
||||
// Member 2 receives: pkg2.shares[idx=2] + pkg3.shares[idx=2]
|
||||
// Member 3 receives: pkg2.shares[idx=3] + pkg3.shares[idx=3]
|
||||
use crate::ecc::util::{scalar_from_bytes, scalar_to_bytes};
|
||||
|
||||
let agg2 = {
|
||||
let a = scalar_from_bytes(&pkg2.shares.iter().find(|s| s.idx == 2).unwrap().seckey);
|
||||
let b = scalar_from_bytes(&pkg3.shares.iter().find(|s| s.idx == 2).unwrap().seckey);
|
||||
SecretShare {
|
||||
idx: 2,
|
||||
seckey: scalar_to_bytes(&(a + b)),
|
||||
}
|
||||
};
|
||||
let agg3 = {
|
||||
let a = scalar_from_bytes(&pkg2.shares.iter().find(|s| s.idx == 3).unwrap().seckey);
|
||||
let b = scalar_from_bytes(&pkg3.shares.iter().find(|s| s.idx == 3).unwrap().seckey);
|
||||
SecretShare {
|
||||
idx: 3,
|
||||
seckey: scalar_to_bytes(&(a + b)),
|
||||
}
|
||||
};
|
||||
|
||||
let repaired = recover_share(&[agg2, agg3], 1);
|
||||
assert_eq!(repaired.idx, 1);
|
||||
assert_eq!(
|
||||
hex::encode(repaired.seckey),
|
||||
hex::encode(group.shares[0].seckey),
|
||||
"recovered share must match original"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn recover_share_sums_scalars() {
|
||||
// recover_share simply sums the provided shares.
|
||||
let a = SecretShare {
|
||||
idx: 1,
|
||||
seckey: s32("0000000000000000000000000000000000000000000000000000000000000003"),
|
||||
};
|
||||
let b = SecretShare {
|
||||
idx: 1,
|
||||
seckey: s32("0000000000000000000000000000000000000000000000000000000000000004"),
|
||||
};
|
||||
let result = recover_share(&[a, b], 5);
|
||||
assert_eq!(result.idx, 5);
|
||||
assert_eq!(result.seckey[31], 7);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn recover_share_single_input() {
|
||||
let a = SecretShare {
|
||||
idx: 2,
|
||||
seckey: s32("0e74411caa9ef5ba058d0ccf4e54e1ee773e625fb7d6258097466cedab0de152"),
|
||||
};
|
||||
let result = recover_share(&[a.clone()], 2);
|
||||
assert_eq!(result.seckey, a.seckey);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
#[cfg(test)]
|
||||
mod refresh_tests {
|
||||
use crate::group::create_dealer_set;
|
||||
use crate::refresh::*;
|
||||
use crate::shares::derive_shares_secret;
|
||||
use crate::types::SecretShare;
|
||||
|
||||
fn s32(hex: &str) -> [u8; 32] {
|
||||
hex::decode(hex).unwrap().try_into().unwrap()
|
||||
}
|
||||
|
||||
const S0: &str = "0070ca75929ca1ec4cd70ac34f46079bdfdd87f9d0c0bf4275f3882f7b462d0f";
|
||||
const S1: &str = "0e0376a7180253cdb8b6020bff0eda529760da65e715663e2152e4be2fc7b443";
|
||||
const R0: &str = "1111111111111111111111111111111111111111111111111111111111111111";
|
||||
const R1: &str = "2222222222222222222222222222222222222222222222222222222222222222";
|
||||
const R2: &str = "3333333333333333333333333333333333333333333333333333333333333333";
|
||||
|
||||
// ── gen_refresh_shares ───────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn gen_refresh_shares_correct_counts() {
|
||||
let pkg = gen_refresh_shares(1, 2, 3, &[s32(R0)]).unwrap();
|
||||
assert_eq!(pkg.shares.len(), 3);
|
||||
assert_eq!(pkg.vss_commits.len(), 1); // threshold - 1 = 1
|
||||
assert_eq!(pkg.idx, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gen_refresh_shares_polynomial_has_zero_constant_term() {
|
||||
// The refresh polynomial f has f(0) = 0.
|
||||
// Verify: sum of all refresh shares from one participant, interpolated at 0, equals 0.
|
||||
use crate::ecc::util::scalar_from_bytes;
|
||||
use crate::poly::index_to_scalar;
|
||||
use crate::poly::interpolate_root;
|
||||
|
||||
let pkg = gen_refresh_shares(1, 2, 3, &[s32(R0)]).unwrap();
|
||||
let points: Vec<_> = pkg
|
||||
.shares
|
||||
.iter()
|
||||
.map(|s| (index_to_scalar(s.idx), scalar_from_bytes(&s.seckey)))
|
||||
.collect();
|
||||
let root = interpolate_root(&points).unwrap();
|
||||
assert_eq!(
|
||||
root,
|
||||
k256::Scalar::ZERO,
|
||||
"refresh polynomial must have f(0) = 0"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gen_refresh_shares_deterministic_with_secrets() {
|
||||
let a = gen_refresh_shares(1, 2, 3, &[s32(R0)]).unwrap();
|
||||
let b = gen_refresh_shares(1, 2, 3, &[s32(R0)]).unwrap();
|
||||
for (sa, sb) in a.shares.iter().zip(b.shares.iter()) {
|
||||
assert_eq!(sa.seckey, sb.seckey);
|
||||
}
|
||||
}
|
||||
|
||||
// ── refresh_share ────────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn refresh_share_preserves_secret() {
|
||||
// Full 3-participant refresh: secret must be unchanged after refresh.
|
||||
let secrets = [s32(S0), s32(S1)];
|
||||
let group = create_dealer_set(2, 3, &secrets).unwrap();
|
||||
|
||||
let rp1 = gen_refresh_shares(1, 2, 3, &[s32(R0)]).unwrap();
|
||||
let rp2 = gen_refresh_shares(2, 2, 3, &[s32(R1)]).unwrap();
|
||||
let rp3 = gen_refresh_shares(3, 2, 3, &[s32(R2)]).unwrap();
|
||||
|
||||
// Each participant collects all refresh shares addressed to them (including own).
|
||||
let agg1: Vec<SecretShare> = [&rp1, &rp2, &rp3]
|
||||
.iter()
|
||||
.map(|pkg| pkg.shares.iter().find(|s| s.idx == 1).unwrap().clone())
|
||||
.collect();
|
||||
let agg2: Vec<SecretShare> = [&rp1, &rp2, &rp3]
|
||||
.iter()
|
||||
.map(|pkg| pkg.shares.iter().find(|s| s.idx == 2).unwrap().clone())
|
||||
.collect();
|
||||
|
||||
let new1 = refresh_share(&agg1, &group.shares[0]).unwrap();
|
||||
let new2 = refresh_share(&agg2, &group.shares[1]).unwrap();
|
||||
|
||||
let recovered = derive_shares_secret(&[new1, new2]).unwrap();
|
||||
assert_eq!(recovered, s32(S0), "secret must be unchanged after refresh");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn refresh_share_changes_share_values() {
|
||||
let secrets = [s32(S0), s32(S1)];
|
||||
let group = create_dealer_set(2, 3, &secrets).unwrap();
|
||||
let rp1 = gen_refresh_shares(1, 2, 3, &[s32(R0)]).unwrap();
|
||||
let rp2 = gen_refresh_shares(2, 2, 3, &[s32(R1)]).unwrap();
|
||||
let rp3 = gen_refresh_shares(3, 2, 3, &[s32(R2)]).unwrap();
|
||||
|
||||
let agg1: Vec<SecretShare> = [&rp1, &rp2, &rp3]
|
||||
.iter()
|
||||
.map(|pkg| pkg.shares.iter().find(|s| s.idx == 1).unwrap().clone())
|
||||
.collect();
|
||||
|
||||
let new1 = refresh_share(&agg1, &group.shares[0]).unwrap();
|
||||
// The refreshed share should differ from the original
|
||||
assert_ne!(new1.seckey, group.shares[0].seckey);
|
||||
assert_eq!(new1.idx, group.shares[0].idx);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn refresh_share_mismatched_idx_errors() {
|
||||
let current = SecretShare {
|
||||
idx: 1,
|
||||
seckey: s32("0e74411caa9ef5ba058d0ccf4e54e1ee773e625fb7d6258097466cedab0de152"),
|
||||
};
|
||||
let wrong_idx = SecretShare {
|
||||
idx: 2, // different idx
|
||||
seckey: s32("0000000000000000000000000000000000000000000000000000000000000001"),
|
||||
};
|
||||
// combine_set will fail because indices differ
|
||||
assert!(refresh_share(&[wrong_idx], ¤t).is_err());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
#[cfg(test)]
|
||||
mod shares_tests {
|
||||
use crate::ecc::util::scalar_from_bytes;
|
||||
use crate::shares::*;
|
||||
use crate::types::SecretShare;
|
||||
use crate::vss::create_share_coeffs;
|
||||
|
||||
fn s32(hex: &str) -> [u8; 32] {
|
||||
hex::decode(hex).unwrap().try_into().unwrap()
|
||||
}
|
||||
|
||||
fn share(idx: u32, seckey_hex: &str) -> SecretShare {
|
||||
SecretShare {
|
||||
idx,
|
||||
seckey: s32(seckey_hex),
|
||||
}
|
||||
}
|
||||
|
||||
// ── create_shares ────────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn create_shares_count_and_indices() {
|
||||
let s0 = s32("0070ca75929ca1ec4cd70ac34f46079bdfdd87f9d0c0bf4275f3882f7b462d0f");
|
||||
let s1 = s32("0e0376a7180253cdb8b6020bff0eda529760da65e715663e2152e4be2fc7b443");
|
||||
let coeffs = create_share_coeffs(&[s0, s1], 2);
|
||||
let shares = create_shares(&coeffs, 3).unwrap();
|
||||
assert_eq!(shares.len(), 3);
|
||||
assert_eq!(shares[0].idx, 1);
|
||||
assert_eq!(shares[1].idx, 2);
|
||||
assert_eq!(shares[2].idx, 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create_shares_matches_fixture() {
|
||||
let s0 = s32("0070ca75929ca1ec4cd70ac34f46079bdfdd87f9d0c0bf4275f3882f7b462d0f");
|
||||
let s1 = s32("0e0376a7180253cdb8b6020bff0eda529760da65e715663e2152e4be2fc7b443");
|
||||
let coeffs = create_share_coeffs(&[s0, s1], 2);
|
||||
let shares = create_shares(&coeffs, 3).unwrap();
|
||||
assert_eq!(
|
||||
hex::encode(shares[0].seckey),
|
||||
"0e74411caa9ef5ba058d0ccf4e54e1ee773e625fb7d6258097466cedab0de152"
|
||||
);
|
||||
assert_eq!(
|
||||
hex::encode(shares[1].seckey),
|
||||
"1c77b7c3c2a14987be430edb4d63bc410e9f3cc59eeb8bbeb89951abdad59595"
|
||||
);
|
||||
assert_eq!(
|
||||
hex::encode(shares[2].seckey),
|
||||
"2a7b2e6adaa39d5576f910e74c729693a600172b8600f1fcd9ec366a0a9d49d8"
|
||||
);
|
||||
}
|
||||
|
||||
// ── combine_shares ───────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn combine_shares_sums_scalars() {
|
||||
let a = share(
|
||||
1,
|
||||
"0000000000000000000000000000000000000000000000000000000000000003",
|
||||
);
|
||||
let b = share(
|
||||
2,
|
||||
"0000000000000000000000000000000000000000000000000000000000000004",
|
||||
);
|
||||
let result = combine_shares(&[a, b]);
|
||||
let expected = scalar_from_bytes(&{
|
||||
let mut b = [0u8; 32];
|
||||
b[31] = 7;
|
||||
b
|
||||
});
|
||||
assert_eq!(scalar_from_bytes(&result), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn combine_shares_single() {
|
||||
let a = share(
|
||||
1,
|
||||
"0e74411caa9ef5ba058d0ccf4e54e1ee773e625fb7d6258097466cedab0de152",
|
||||
);
|
||||
let result = combine_shares(&[a.clone()]);
|
||||
assert_eq!(result, a.seckey);
|
||||
}
|
||||
|
||||
// ── combine_set ──────────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn combine_set_same_idx() {
|
||||
let a = share(
|
||||
1,
|
||||
"0000000000000000000000000000000000000000000000000000000000000003",
|
||||
);
|
||||
let b = share(
|
||||
1,
|
||||
"0000000000000000000000000000000000000000000000000000000000000004",
|
||||
);
|
||||
let result = combine_set(&[a, b]).unwrap();
|
||||
assert_eq!(result.idx, 1);
|
||||
assert_eq!(result.seckey[31], 7);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn combine_set_mismatched_idx_errors() {
|
||||
let a = share(
|
||||
1,
|
||||
"0000000000000000000000000000000000000000000000000000000000000003",
|
||||
);
|
||||
let b = share(
|
||||
2,
|
||||
"0000000000000000000000000000000000000000000000000000000000000004",
|
||||
);
|
||||
assert!(combine_set(&[a, b]).is_err());
|
||||
}
|
||||
|
||||
// ── merge_shares ─────────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn merge_shares_combines_matching_indices() {
|
||||
let a1 = share(
|
||||
1,
|
||||
"0000000000000000000000000000000000000000000000000000000000000003",
|
||||
);
|
||||
let a2 = share(
|
||||
2,
|
||||
"0000000000000000000000000000000000000000000000000000000000000005",
|
||||
);
|
||||
let b1 = share(
|
||||
1,
|
||||
"0000000000000000000000000000000000000000000000000000000000000004",
|
||||
);
|
||||
let b2 = share(
|
||||
2,
|
||||
"0000000000000000000000000000000000000000000000000000000000000006",
|
||||
);
|
||||
let merged = merge_shares(&[a1, a2], &[b1, b2]).unwrap();
|
||||
assert_eq!(merged[0].idx, 1);
|
||||
assert_eq!(merged[0].seckey[31], 7);
|
||||
assert_eq!(merged[1].idx, 2);
|
||||
assert_eq!(merged[1].seckey[31], 11);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn merge_shares_mismatched_lengths_errors() {
|
||||
let a = vec![share(
|
||||
1,
|
||||
"0000000000000000000000000000000000000000000000000000000000000001",
|
||||
)];
|
||||
let b: Vec<SecretShare> = vec![];
|
||||
assert!(merge_shares(&a, &b).is_err());
|
||||
}
|
||||
|
||||
// ── verify_share ─────────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn verify_share_valid_shares() {
|
||||
let s0 = s32("0070ca75929ca1ec4cd70ac34f46079bdfdd87f9d0c0bf4275f3882f7b462d0f");
|
||||
let s1 = s32("0e0376a7180253cdb8b6020bff0eda529760da65e715663e2152e4be2fc7b443");
|
||||
let coeffs = create_share_coeffs(&[s0, s1], 2);
|
||||
let shares = create_shares(&coeffs, 3).unwrap();
|
||||
let commits: Vec<[u8; 33]> = crate::vss::get_share_commits(&coeffs);
|
||||
for s in &shares {
|
||||
assert!(
|
||||
verify_share(&commits, s, 2).unwrap(),
|
||||
"share {} should be valid",
|
||||
s.idx
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verify_share_tampered_share_fails() {
|
||||
let s0 = s32("0070ca75929ca1ec4cd70ac34f46079bdfdd87f9d0c0bf4275f3882f7b462d0f");
|
||||
let s1 = s32("0e0376a7180253cdb8b6020bff0eda529760da65e715663e2152e4be2fc7b443");
|
||||
let coeffs = create_share_coeffs(&[s0, s1], 2);
|
||||
let mut shares = create_shares(&coeffs, 3).unwrap();
|
||||
let commits = crate::vss::get_share_commits(&coeffs);
|
||||
// Corrupt share[0]
|
||||
shares[0].seckey[0] ^= 0xff;
|
||||
assert!(!verify_share(&commits, &shares[0], 2).unwrap());
|
||||
}
|
||||
|
||||
// ── derive_shares_secret ─────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn derive_shares_secret_recovers_root() {
|
||||
let s0 = s32("0070ca75929ca1ec4cd70ac34f46079bdfdd87f9d0c0bf4275f3882f7b462d0f");
|
||||
let s1 = s32("0e0376a7180253cdb8b6020bff0eda529760da65e715663e2152e4be2fc7b443");
|
||||
let coeffs = create_share_coeffs(&[s0, s1], 2);
|
||||
let shares = create_shares(&coeffs, 3).unwrap();
|
||||
// Any 2-of-3 subset should recover s0
|
||||
let secret_12 = derive_shares_secret(&shares[..2]).unwrap();
|
||||
let secret_13 = derive_shares_secret(&[shares[0].clone(), shares[2].clone()]).unwrap();
|
||||
let secret_23 = derive_shares_secret(&shares[1..]).unwrap();
|
||||
assert_eq!(secret_12, s0);
|
||||
assert_eq!(secret_13, s0);
|
||||
assert_eq!(secret_23, s0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn derive_shares_secret_all_three_also_recovers() {
|
||||
let s0 = s32("0070ca75929ca1ec4cd70ac34f46079bdfdd87f9d0c0bf4275f3882f7b462d0f");
|
||||
let s1 = s32("0e0376a7180253cdb8b6020bff0eda529760da65e715663e2152e4be2fc7b443");
|
||||
let coeffs = create_share_coeffs(&[s0, s1], 2);
|
||||
let shares = create_shares(&coeffs, 3).unwrap();
|
||||
let secret = derive_shares_secret(&shares).unwrap();
|
||||
assert_eq!(secret, s0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
#[cfg(test)]
|
||||
mod vss_tests {
|
||||
use crate::ecc::group::{scalar_base_multi, serialize_element};
|
||||
use crate::ecc::util::scalar_from_bytes;
|
||||
use crate::vss::*;
|
||||
use k256::Scalar;
|
||||
|
||||
fn s32(hex: &str) -> [u8; 32] {
|
||||
hex::decode(hex).unwrap().try_into().unwrap()
|
||||
}
|
||||
|
||||
fn p33(hex: &str) -> [u8; 33] {
|
||||
hex::decode(hex).unwrap().try_into().unwrap()
|
||||
}
|
||||
|
||||
// ── create_share_coeffs ──────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn create_share_coeffs_uses_provided_secrets() {
|
||||
let s0 = s32("0070ca75929ca1ec4cd70ac34f46079bdfdd87f9d0c0bf4275f3882f7b462d0f");
|
||||
let s1 = s32("0e0376a7180253cdb8b6020bff0eda529760da65e715663e2152e4be2fc7b443");
|
||||
let coeffs = create_share_coeffs(&[s0, s1], 2);
|
||||
assert_eq!(coeffs.len(), 2);
|
||||
assert_eq!(coeffs[0], scalar_from_bytes(&s0));
|
||||
assert_eq!(coeffs[1], scalar_from_bytes(&s1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create_share_coeffs_fills_random_when_short() {
|
||||
let s0 = s32("0070ca75929ca1ec4cd70ac34f46079bdfdd87f9d0c0bf4275f3882f7b462d0f");
|
||||
let coeffs = create_share_coeffs(&[s0], 3);
|
||||
assert_eq!(coeffs.len(), 3);
|
||||
// First coeff is deterministic
|
||||
assert_eq!(coeffs[0], scalar_from_bytes(&s0));
|
||||
// Remaining two are random (non-zero with overwhelming probability)
|
||||
// We can't assert their values, but we can check they're not zero
|
||||
assert_ne!(coeffs[1], Scalar::ZERO);
|
||||
assert_ne!(coeffs[2], Scalar::ZERO);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create_share_coeffs_empty_secrets_all_random() {
|
||||
let coeffs = create_share_coeffs(&[], 3);
|
||||
assert_eq!(coeffs.len(), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create_share_coeffs_threshold_zero_is_empty() {
|
||||
let coeffs = create_share_coeffs(&[], 0);
|
||||
assert!(coeffs.is_empty());
|
||||
}
|
||||
|
||||
// ── get_share_commits ────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn get_share_commits_matches_fixture() {
|
||||
let s0 = s32("0070ca75929ca1ec4cd70ac34f46079bdfdd87f9d0c0bf4275f3882f7b462d0f");
|
||||
let s1 = s32("0e0376a7180253cdb8b6020bff0eda529760da65e715663e2152e4be2fc7b443");
|
||||
let coeffs = create_share_coeffs(&[s0, s1], 2);
|
||||
let commits = get_share_commits(&coeffs);
|
||||
assert_eq!(commits.len(), 2);
|
||||
assert_eq!(
|
||||
hex::encode(commits[0]),
|
||||
"021ae63bc9ddaffe52d44c3018e83115bfb22195bd8112fcad112310714e6fd5ec"
|
||||
);
|
||||
assert_eq!(
|
||||
hex::encode(commits[1]),
|
||||
"024f75a5478deda1102eba931e19425e59c1750533a54218ce215ced343fbfb6cf"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_share_commits_commit_is_scalar_times_generator() {
|
||||
let s0 = s32("0070ca75929ca1ec4cd70ac34f46079bdfdd87f9d0c0bf4275f3882f7b462d0f");
|
||||
let coeffs = create_share_coeffs(&[s0], 1);
|
||||
let commits = get_share_commits(&coeffs);
|
||||
let expected = serialize_element(&scalar_base_multi(&coeffs[0]));
|
||||
assert_eq!(commits[0], expected);
|
||||
}
|
||||
|
||||
// ── merge_share_commits ──────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn merge_share_commits_adds_points() {
|
||||
let s0 = s32("0070ca75929ca1ec4cd70ac34f46079bdfdd87f9d0c0bf4275f3882f7b462d0f");
|
||||
let s1 = s32("0e0376a7180253cdb8b6020bff0eda529760da65e715663e2152e4be2fc7b443");
|
||||
let coeffs_a = create_share_coeffs(&[s0], 1);
|
||||
let coeffs_b = create_share_coeffs(&[s1], 1);
|
||||
let commits_a = get_share_commits(&coeffs_a);
|
||||
let commits_b = get_share_commits(&coeffs_b);
|
||||
let merged = merge_share_commits(&commits_a, &commits_b).unwrap();
|
||||
// merged[0] should be (s0 + s1) * G
|
||||
let combined = scalar_from_bytes(&s0) + scalar_from_bytes(&s1);
|
||||
let expected = serialize_element(&scalar_base_multi(&combined));
|
||||
assert_eq!(merged[0], expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn merge_share_commits_mismatched_lengths_errors() {
|
||||
let a = vec![p33(
|
||||
"021ae63bc9ddaffe52d44c3018e83115bfb22195bd8112fcad112310714e6fd5ec",
|
||||
)];
|
||||
let b: Vec<[u8; 33]> = vec![];
|
||||
assert!(merge_share_commits(&a, &b).is_err());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user