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 {
|
.map(|s| MemberPackage {
|
||||||
idx: s.idx,
|
idx: s.idx,
|
||||||
pubkey: get_pubkey(&s.seckey),
|
pubkey: get_pubkey(&s.seckey),
|
||||||
|
identity_pk: None,
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
|||||||
@@ -22,8 +22,8 @@
|
|||||||
/// all received DkgSharePackages, and all Round 1 DkgCommitPackages.
|
/// all received DkgSharePackages, and all Round 1 DkgCommitPackages.
|
||||||
/// Returns DkgOutput containing their aggregate share and the GroupPackage.
|
/// Returns DkgOutput containing their aggregate share and the GroupPackage.
|
||||||
/// ```
|
/// ```
|
||||||
use crate::ecc::group::serialize_element;
|
use crate::ecc::group::{scalar_multi, serialize_element};
|
||||||
use crate::ecc::util::lift_x;
|
use crate::ecc::util::{lift_x, pow_n};
|
||||||
use crate::shares::{combine_set, verify_share};
|
use crate::shares::{combine_set, verify_share};
|
||||||
use crate::types::SecretShare;
|
use crate::types::SecretShare;
|
||||||
use crate::vss::{create_share_coeffs, get_share_commits, merge_share_commits};
|
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()))?;
|
.ok_or_else(|| Error::Assertion("no VSS commits to merge".to_string()))?;
|
||||||
|
|
||||||
// Build member packages: each member's pubkey is their first VSS commit
|
// Build member packages.
|
||||||
// (their individual public key = a_i0 * G, the constant term of their polynomial).
|
// 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
|
let members: Vec<MemberPackage> = sorted_commits
|
||||||
.iter()
|
.iter()
|
||||||
.map(|c| MemberPackage {
|
.map(|c| {
|
||||||
idx: c.idx,
|
let share_pubkey = eval_vss_pubkey(&group_vss_commits, c.idx)?;
|
||||||
pubkey: c.vss_commits[0],
|
Ok(MemberPackage {
|
||||||
|
idx: c.idx,
|
||||||
|
pubkey: share_pubkey,
|
||||||
|
identity_pk: Some(c.vss_commits[0]),
|
||||||
|
})
|
||||||
})
|
})
|
||||||
.collect();
|
.collect::<Result<_, Error>>()?;
|
||||||
|
|
||||||
let group = GroupPackage {
|
let group = GroupPackage {
|
||||||
group_pk,
|
group_pk,
|
||||||
@@ -212,3 +220,27 @@ fn sum_points(points: &[[u8; 33]]) -> Result<[u8; 33], Error> {
|
|||||||
}
|
}
|
||||||
Ok(serialize_element(&acc))
|
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]
|
#[test]
|
||||||
fn finalize_member_pubkeys_are_first_vss_commits() {
|
fn finalize_member_identity_pks_are_first_vss_commits() {
|
||||||
let outputs = run_dkg();
|
let outputs = run_dkg();
|
||||||
let (_, commit1) = dkg_round1(1, 2, &seeds_for(SEED1));
|
let (_, commit1) = dkg_round1(1, 2, &seeds_for(SEED1));
|
||||||
let (_, commit2) = dkg_round1(2, 2, &seeds_for(SEED2));
|
let (_, commit2) = dkg_round1(2, 2, &seeds_for(SEED2));
|
||||||
let (_, commit3) = dkg_round1(3, 2, &seeds_for(SEED3));
|
let (_, commit3) = dkg_round1(3, 2, &seeds_for(SEED3));
|
||||||
|
|
||||||
let group = &outputs[0].group;
|
let group = &outputs[0].group;
|
||||||
assert_eq!(group.members[0].pubkey, commit1.vss_commits[0]);
|
assert_eq!(group.members[0].identity_pk, Some(commit1.vss_commits[0]));
|
||||||
assert_eq!(group.members[1].pubkey, commit2.vss_commits[0]);
|
assert_eq!(group.members[1].identity_pk, Some(commit2.vss_commits[0]));
|
||||||
assert_eq!(group.members[2].pubkey, commit3.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]
|
#[test]
|
||||||
|
|||||||
@@ -17,8 +17,13 @@ pub struct SharePackage {
|
|||||||
pub struct MemberPackage {
|
pub struct MemberPackage {
|
||||||
/// Participant index (1-based).
|
/// Participant index (1-based).
|
||||||
pub idx: u32,
|
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],
|
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.
|
/// The group's public state: group key + member roster + threshold.
|
||||||
|
|||||||
Reference in New Issue
Block a user