Fix member.pubkey ambiguity between dkg and dealer
This commit is contained in:
@@ -41,6 +41,7 @@ pub fn generate_dealer_package(
|
||||
.map(|s| MemberPackage {
|
||||
idx: s.idx,
|
||||
pubkey: get_pubkey(&s.seckey),
|
||||
identity_pk: None,
|
||||
})
|
||||
.collect();
|
||||
|
||||
|
||||
@@ -22,8 +22,8 @@
|
||||
/// all received DkgSharePackages, and all Round 1 DkgCommitPackages.
|
||||
/// Returns DkgOutput containing their aggregate share and the GroupPackage.
|
||||
/// ```
|
||||
use crate::ecc::group::serialize_element;
|
||||
use crate::ecc::util::lift_x;
|
||||
use crate::ecc::group::{scalar_multi, serialize_element};
|
||||
use crate::ecc::util::{lift_x, pow_n};
|
||||
use crate::shares::{combine_set, verify_share};
|
||||
use crate::types::SecretShare;
|
||||
use crate::vss::{create_share_coeffs, get_share_commits, merge_share_commits};
|
||||
@@ -175,15 +175,23 @@ pub fn dkg_finalize(
|
||||
})?
|
||||
.ok_or_else(|| Error::Assertion("no VSS commits to merge".to_string()))?;
|
||||
|
||||
// Build member packages: each member's pubkey is their first VSS commit
|
||||
// (their individual public key = a_i0 * G, the constant term of their polynomial).
|
||||
// Build member packages.
|
||||
// pubkey = aggregate share pubkey, derived from merged VSS commits:
|
||||
// sum_k( merged_commits[k] * idx^k )
|
||||
// This is the same equation used in share verification, but returning
|
||||
// the point rather than comparing it — no secret knowledge required.
|
||||
// identity_pk = each participant's first VSS commit (a_i0 * G).
|
||||
let members: Vec<MemberPackage> = sorted_commits
|
||||
.iter()
|
||||
.map(|c| MemberPackage {
|
||||
idx: c.idx,
|
||||
pubkey: c.vss_commits[0],
|
||||
.map(|c| {
|
||||
let share_pubkey = eval_vss_pubkey(&group_vss_commits, c.idx)?;
|
||||
Ok(MemberPackage {
|
||||
idx: c.idx,
|
||||
pubkey: share_pubkey,
|
||||
identity_pk: Some(c.vss_commits[0]),
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
.collect::<Result<_, Error>>()?;
|
||||
|
||||
let group = GroupPackage {
|
||||
group_pk,
|
||||
@@ -212,3 +220,27 @@ fn sum_points(points: &[[u8; 33]]) -> Result<[u8; 33], Error> {
|
||||
}
|
||||
Ok(serialize_element(&acc))
|
||||
}
|
||||
|
||||
/// Evaluate the VSS commitment polynomial at a participant index to derive
|
||||
/// their aggregate share public key, without knowing the secret.
|
||||
///
|
||||
/// Computes: sum_k( commits[k] * idx^k )
|
||||
///
|
||||
/// This mirrors the share verification equation in `shares::verify_share`,
|
||||
/// but returns the resulting point rather than comparing it.
|
||||
fn eval_vss_pubkey(commits: &[[u8; 33]], idx: u32) -> Result<[u8; 33], Error> {
|
||||
if commits.is_empty() {
|
||||
return Err(Error::Assertion("no VSS commits".to_string()));
|
||||
}
|
||||
let mut acc = None::<k256::ProjectivePoint>;
|
||||
for (k, commit) in commits.iter().enumerate() {
|
||||
let point = lift_x(commit)?;
|
||||
let exp = pow_n(idx as u64, k as u64);
|
||||
let term = scalar_multi(&point, &exp);
|
||||
acc = Some(match acc {
|
||||
None => term,
|
||||
Some(a) => a + term,
|
||||
});
|
||||
}
|
||||
Ok(serialize_element(&acc.unwrap()))
|
||||
}
|
||||
|
||||
@@ -1204,16 +1204,56 @@ mod dkg_tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn finalize_member_pubkeys_are_first_vss_commits() {
|
||||
fn finalize_member_identity_pks_are_first_vss_commits() {
|
||||
let outputs = run_dkg();
|
||||
let (_, commit1) = dkg_round1(1, 2, &seeds_for(SEED1));
|
||||
let (_, commit2) = dkg_round1(2, 2, &seeds_for(SEED2));
|
||||
let (_, commit3) = dkg_round1(3, 2, &seeds_for(SEED3));
|
||||
|
||||
let group = &outputs[0].group;
|
||||
assert_eq!(group.members[0].pubkey, commit1.vss_commits[0]);
|
||||
assert_eq!(group.members[1].pubkey, commit2.vss_commits[0]);
|
||||
assert_eq!(group.members[2].pubkey, commit3.vss_commits[0]);
|
||||
assert_eq!(group.members[0].identity_pk, Some(commit1.vss_commits[0]));
|
||||
assert_eq!(group.members[1].identity_pk, Some(commit2.vss_commits[0]));
|
||||
assert_eq!(group.members[2].identity_pk, Some(commit3.vss_commits[0]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn finalize_member_pubkeys_are_share_pubkeys() {
|
||||
let outputs = run_dkg();
|
||||
// Each member's pubkey must equal their aggregate share seckey * G.
|
||||
// We can verify this for our own slot (where we know the secret),
|
||||
// and for all slots via the VSS equation.
|
||||
for output in &outputs {
|
||||
let expected_pk = crate::helpers::get_pubkey(&output.share.seckey);
|
||||
let member = output
|
||||
.group
|
||||
.members
|
||||
.iter()
|
||||
.find(|m| m.idx == output.share.idx)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
member.pubkey, expected_pk,
|
||||
"member {} pubkey should equal share pubkey",
|
||||
output.share.idx
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn finalize_dealer_members_have_no_identity_pk() {
|
||||
let secrets = [
|
||||
hex::decode("0070ca75929ca1ec4cd70ac34f46079bdfdd87f9d0c0bf4275f3882f7b462d0f")
|
||||
.unwrap()
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
hex::decode("0e0376a7180253cdb8b6020bff0eda529760da65e715663e2152e4be2fc7b443")
|
||||
.unwrap()
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
];
|
||||
let pkg = crate::frost::dealer::generate_dealer_package(2, 3, &secrets).unwrap();
|
||||
for member in &pkg.group.members {
|
||||
assert_eq!(member.identity_pk, None);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -17,8 +17,13 @@ pub struct SharePackage {
|
||||
pub struct MemberPackage {
|
||||
/// Participant index (1-based).
|
||||
pub idx: u32,
|
||||
/// 33-byte compressed public key derived from the secret share.
|
||||
/// 33-byte compressed public key of this member's aggregate secret share (`seckey * G`).
|
||||
/// Used for partial signature verification.
|
||||
pub pubkey: [u8; 33],
|
||||
/// DKG only: the first VSS commitment from this participant's Round 1 broadcast
|
||||
/// (`a_i0 * G`), i.e. their individual identity public key.
|
||||
/// `None` in the trusted dealer model.
|
||||
pub identity_pk: Option<[u8; 33]>,
|
||||
}
|
||||
|
||||
/// The group's public state: group key + member roster + threshold.
|
||||
|
||||
Reference in New Issue
Block a user