diff --git a/reference/chilldkg.py b/reference/chilldkg.py index b61f40a..bcd09d9 100644 --- a/reference/chilldkg.py +++ b/reference/chilldkg.py @@ -1,5 +1,5 @@ # Reference implementation of BIP DKG. -from typing import Tuple, List, NamedTuple, NewType +from typing import Tuple, List, NamedTuple, NewType, Optional from secp256k1ref.secp256k1 import Scalar from secp256k1ref.bip340 import schnorr_sign, schnorr_verify @@ -241,48 +241,6 @@ async def participant( return participant_finalize(state2, cmsg2) -# Recovery requires the seed and the public recovery data -def participant_recover( - seed: bytes, recovery: RecoveryData, context_string: bytes -) -> Tuple[DKGOutput, SessionParams]: - try: - (t, sum_vss_commit, hostpubkeys, enc_shares_sums, cert) = ( - deserialize_recovery_data(recovery) - ) - except DeserializationError as e: - raise InvalidRecoveryDataError("Failed to deserialize recovery data") from e - - n = len(hostpubkeys) - (params, params_id) = session_params(hostpubkeys, t, context_string) - - # Verify cert - certifying_eq_verify(hostpubkeys, recovery[: 64 * n], cert) - - # Find our hostpubkey - hostseckey, hostpubkey = hostkey_gen(seed) - try: - idx = hostpubkeys.index(hostpubkey) - except ValueError as e: - raise InvalidRecoveryDataError("Seed and recovery data don't match") from e - - # Decrypt share - seed_, enc_context = encpedpop.session_seed(seed, hostpubkeys, t) - shares_sum = encpedpop.decrypt_sum( - enc_shares_sums[idx], hostseckey, hostpubkeys, idx, enc_context - ) - - # Derive self_share - vss = VSS.generate(seed_, t) - self_share = vss.share_for(idx) - shares_sum += self_share - - # Compute threshold pubkey and individual pubshares - (threshold_pubkey, pubshares) = common_dkg_output(sum_vss_commit, n) - - dkg_output = DKGOutput(shares_sum, threshold_pubkey, pubshares) - return dkg_output, params - - ### ### Coordinator ### @@ -335,3 +293,54 @@ async def coordinator( chans.send_all(cmsg2) return dkg_output, recovery_data + + +### +### Recovery +### + + +# Recovery requires the seed (can be None if recovering the coordinator) and the +# public recovery data +def recover( + seed: Optional[bytes], recovery: RecoveryData, context_string: bytes +) -> Tuple[DKGOutput, SessionParams]: + try: + (t, sum_vss_commit, hostpubkeys, enc_shares_sums, cert) = ( + deserialize_recovery_data(recovery) + ) + except DeserializationError as e: + raise InvalidRecoveryDataError("Failed to deserialize recovery data") from e + + n = len(hostpubkeys) + (params, params_id) = session_params(hostpubkeys, t, context_string) + + # Verify cert + certifying_eq_verify(hostpubkeys, recovery[: 64 * n], cert) + + if seed: + # Find our hostpubkey + hostseckey, hostpubkey = hostkey_gen(seed) + try: + idx = hostpubkeys.index(hostpubkey) + except ValueError as e: + raise InvalidRecoveryDataError("Seed and recovery data don't match") from e + + # Decrypt share + seed_, enc_context = encpedpop.session_seed(seed, hostpubkeys, t) + shares_sum = encpedpop.decrypt_sum( + enc_shares_sums[idx], hostseckey, hostpubkeys, idx, enc_context + ) + + # Derive self_share + vss = VSS.generate(seed_, t) + self_share = vss.share_for(idx) + shares_sum += self_share + else: + shares_sum = None + + # Compute threshold pubkey and individual pubshares + (threshold_pubkey, pubshares) = common_dkg_output(sum_vss_commit, n) + + dkg_output = DKGOutput(shares_sum, threshold_pubkey, pubshares) + return dkg_output, params diff --git a/reference/encpedpop.py b/reference/encpedpop.py index 4aa8375..f3d51a9 100644 --- a/reference/encpedpop.py +++ b/reference/encpedpop.py @@ -68,7 +68,7 @@ class ParticipantState(NamedTuple): simpl_state: simplpedpop.ParticipantState # TODO Move up? -def session_seed(seed, enckeys, t): +def session_seed(seed: bytes, enckeys: List[bytes], t: int) -> Tuple[bytes, bytes]: enc_context = t.to_bytes(4, byteorder="big") + b"".join(enckeys) seed_ = tagged_hash_bip_dkg("EncPedPop seed", seed + enc_context) return seed_, enc_context diff --git a/reference/simplpedpop.py b/reference/simplpedpop.py index 1b8d86e..69a68b3 100644 --- a/reference/simplpedpop.py +++ b/reference/simplpedpop.py @@ -193,6 +193,7 @@ def participant_pre_finalize( # Sum the commitments to the i-th coefficients from the given vss_commitments # for i > 0. This procedure is introduced by Pedersen in section 5.1 of # 'Non-Interactive and Information-Theoretic Secure Verifiable Secret Sharing'. +# TODO Should this be called coordinator_pre_finalize? (same in encpedpop) def coordinator_step( pmsgs: List[ParticipantMsg], t: int, n: int ) -> Tuple[CoordinatorMsg, DKGOutput, bytes]: diff --git a/reference/tests.py b/reference/tests.py index 855f2c5..664aa7e 100644 --- a/reference/tests.py +++ b/reference/tests.py @@ -199,11 +199,11 @@ def test_correctness_dkg_output(t, n, dkg_outputs: List[simplpedpop.DKGOutput]): def test_correctness(t, n, simulate_dkg, recovery=False): - seeds = [secrets.token_bytes(32) for _ in range(n)] + seeds = [None] + [secrets.token_bytes(32) for _ in range(n)] # rets[0] are the return values from the coordinator # rets[1 : n + 1] are from the participants - rets = simulate_dkg(seeds, t) + rets = simulate_dkg(seeds[1:], t) assert len(rets) == n + 1 dkg_outputs = [ret[0] for ret in rets] @@ -215,10 +215,10 @@ def test_correctness(t, n, simulate_dkg, recovery=False): if recovery: rec = eqs_or_recs[0] - # test correctness of chilldkg_recover - for i in range(1, n + 1): - (secshare, threshold_pubkey, pubshares), _ = chilldkg.participant_recover( - seeds[i - 1], rec, b"" + # test correctness of chilldkg.recover + for i in range(0, n + 1): + (secshare, threshold_pubkey, pubshares), _ = chilldkg.recover( + seeds[i], rec, b"" ) assert secshare == dkg_outputs[i][0] assert threshold_pubkey == dkg_outputs[i][1]