Skip to content

Commit

Permalink
tests: Test also with blame=True
Browse files Browse the repository at this point in the history
  • Loading branch information
real-or-random committed Oct 18, 2024
1 parent 4ee666e commit b018e77
Show file tree
Hide file tree
Showing 4 changed files with 124 additions and 81 deletions.
45 changes: 33 additions & 12 deletions python/chilldkg_ref/encpedpop.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Tuple, List, NamedTuple, Optional
from typing import Tuple, List, NamedTuple, Optional, cast

from secp256k1proto.secp256k1 import Scalar, GE
from secp256k1proto.ecdh import ecdh_libsecp256k1
Expand Down Expand Up @@ -209,7 +209,7 @@ def participant_step1(
# case someone derives secnonce differently.
simpl_seed = derive_simpl_seed(seed, pubnonce, enc_context)

simpl_state, simpl_pmsg, shares, _ = simplpedpop.participant_step1(
simpl_state, simpl_pmsg, shares = simplpedpop.participant_step1(
simpl_seed, t, n, idx
)
assert len(shares) == n
Expand Down Expand Up @@ -261,9 +261,19 @@ def participant_step2(
else:
simpl_blame_rec = None

dkg_output, eq_input = simplpedpop.participant_step2(
simpl_state, simpl_cmsg, secshare, simpl_blame_rec
)
try:
dkg_output, eq_input = simplpedpop.participant_step2(
simpl_state, simpl_cmsg, secshare, simpl_blame_rec
)
except simplpedpop.InconsistentSecsharesError as e:
# The secshare is not equal to the sum of the partial secshares in the
# blame records. Since the encryption is additively homomorphic, this
# can only happen if the sum of the *encrypted* secshare is not equal
# to the sum of the encrypted partial sechares.
assert Scalar.sum(*enc_partial_secshares) != enc_secshare
raise FaultyCoordinatorError(
"Sum of encrypted partial secshares not equal to encrypted secshare"
) from e
eq_input += b"".join(enckeys) + b"".join(pubnonces)
return dkg_output, eq_input

Expand All @@ -288,8 +298,8 @@ def coordinator_step(
n = len(enckeys)
if n != len(pmsgs):
raise ValueError
simpl_cmsg, dkg_output, eq_input = simplpedpop.coordinator_step(
[pmsg.simpl_pmsg for pmsg in pmsgs], t, n
simpl_cmsg, dkg_output, eq_input, all_partial_pubshares = (
simplpedpop.coordinator_step([pmsg.simpl_pmsg for pmsg in pmsgs], t, n, blame)
)
pubnonces = [pmsg.pubnonce for pmsg in pmsgs]
for i in range(n):
Expand All @@ -300,17 +310,28 @@ def coordinator_step(
enc_secshares = [
Scalar.sum(*([pmsg.enc_shares[i] for pmsg in pmsgs])) for i in range(n)
]
# enc_secshares[0] += Scalar(n)

blame_recs: List[Optional[BlameRecord]]
if blame:
enc_partial_secshares = [
# We called simplpedpop.coordinator_step(..., blame=True), which is
# supposed to return proper partial_pubshares.
assert all(
[
all_partial_pubshares[i][j] is not None
for i in range(n)
for j in range(n)
]
)
all_enc_partial_secshares = [
[pmsg.enc_shares[i] for pmsg in pmsgs] for i in range(n)
]
partial_pubshares = [
[pmsg.simpl_pmsg.com.pubshare(i) for pmsg in pmsgs] for i in range(n)
]
# for i in range(n):
# all_enc_partial_secshares[0][i] += Scalar(1)
blame_recs = [
BlameRecord(enc_partial_secshares[i], partial_pubshares[i])
BlameRecord(
all_enc_partial_secshares[i], cast(List[GE], all_partial_pubshares[i])
)
for i in range(n)
]
else:
Expand Down
129 changes: 76 additions & 53 deletions python/chilldkg_ref/simplpedpop.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from secrets import token_bytes as random_bytes
from typing import List, NamedTuple, NewType, Tuple, Optional, cast
from typing import List, NamedTuple, NewType, Tuple, Optional, NoReturn, cast

from secp256k1proto.bip340 import schnorr_sign, schnorr_verify
from secp256k1proto.secp256k1 import GE, Scalar
Expand All @@ -13,6 +13,15 @@
from .vss import VSS, VSSCommitment


###
### Exceptions
###


class InconsistentSecsharesError(ValueError):
pass


###
### Proofs of possession (pops)
###
Expand Down Expand Up @@ -131,18 +140,15 @@ def participant_step1(
msg = ParticipantMsg(com, pop)
state = ParticipantState(t, n, idx, com_to_secret)

partial_pubshares_from_me: List[Optional[GE]]
if blame:
partial_pubshares_from_me = [com.pubshare(i) for i in range(n)]
else:
partial_pubshares_from_me = [None for i in range(n)]

return state, msg, partial_secshares_from_me, partial_pubshares_from_me
return state, msg, partial_secshares_from_me


# Helper function to prepare the secret side inputs for participant idx's
# participant_step2() from the partial_secshares returned by all participants'
# participant_step1().
# participant_step2() from
# - the list of all partial_secshares[idx] values from participants'
# participant_step1(), and # FIXME terms are wrong here
# - the partial_pubshares list from the coordinator's coordinator_step()
# (if not blaming, this is a list containing n times None).
#
# This computation cannot be done entirely by the SimplPedPop coordinator
# because it involves secret shares. In a pure run of SimplPedPop where secret
Expand All @@ -153,10 +159,21 @@ def participant_step1(
# take care of this preparation by exploiting the homomorphic property of the
# encryption.
def participant_step2_prepare_secret_side_inputs(
partial_secshares: List[Scalar],
) -> Scalar:
partial_secshares: List[Scalar], partial_pubshares: List[Optional[GE]]
) -> Tuple[Scalar, Optional[BlameRecord]]:
## FIXME take n from state, amend other commit
n = len(partial_secshares)
secshare = Scalar.sum(*partial_secshares)
return secshare
if not len(partial_secshares) == len(partial_pubshares) == n:
raise ValueError
# blame_rec: Optional[BlameRecord]
if partial_pubshares[0] is not None:
if not all([p is not None for p in partial_pubshares]):
raise ValueError
blame_rec = BlameRecord(partial_secshares, cast(List[GE], partial_pubshares))
else:
blame_rec = None
return secshare, blame_rec


def participant_step2(
Expand Down Expand Up @@ -196,44 +213,12 @@ def participant_step2(
pubshares = [sum_coms.pubshare(i) for i in range(n)]

if not VSSCommitment.verify_secshare(secshare, pubshares[idx]):
if blame_rec is None:
raise FaultyParticipantError(None, "Received invalid secshare")
if blame_rec is not None:
_participant_step2_blame(secshare, pubshares, idx, blame_rec)
else:
# TODO Extract function
partial_secshares, partial_pubshares = blame_rec

# TODO Shoud we include these checks? They're superficial but diligent:
# Alternatively, solve the redudancy by sending only n-1
# FIXME Reconsider this check. That's not a faulty coordinator, the
# coordinator is involved the secshares in simplpedpop. Perhaps
# we can move this check to encpedpop (and perform it on the
# encrypted shares). Or additional raise something else here.
# How can this even fail in a run of pure simplpedpop? I think only
# if the prepare function was wrong.
if Scalar.sum(*partial_secshares) != secshare:
raise FaultyCoordinatorError(
"Sum of partial secshares not equal to secshare"
)
# FIXME Similar, but this can fail if either the coordinator was
# wrong, or the prepare function.
if GE.sum(*partial_pubshares) != pubshares[idx]:
raise FaultyCoordinatorError(
"Sum of partial pubshares not equal to pubshare"
)
for i in range(n):
if not VSSCommitment.verify_secshare(
partial_secshares[i], partial_pubshares[i]
):
if i != idx:
raise FaultyParticipantError(
i, "Participant sent invalid partial secshare"
)
else:
# We are not faulty, so it must be the coordinator.
raise FaultyCoordinatorError(
"Would blame myself" # TODO better message
)
assert False
raise FaultyParticipantError(
None, "Received invalid secshare, consider rerunning in blame mode"
)

dkg_output = DKGOutput(
secshare.to_bytes(),
Expand All @@ -244,14 +229,46 @@ def participant_step2(
return dkg_output, eq_input


def _participant_step2_blame(
secshare: Scalar, pubshares: List[GE], idx: int, blame_rec: BlameRecord
) -> NoReturn:
partial_secshares, partial_pubshares = blame_rec
n = len(pubshares)
if Scalar.sum(*partial_secshares) != secshare:
raise InconsistentSecsharesError
# The following check can safely be omitted, because we trust the
# coordinator for computing the partial_pubshares correctly anyway. Or, in
# other words, the coordinator can anyway make us blame some innocent
# participant. We keep it because it may help debugging benign failures.
if GE.sum(*partial_pubshares) != pubshares[idx]:
raise FaultyCoordinatorError("Sum of partial pubshares not equal to pubshare")
for i in range(n):
if not VSSCommitment.verify_secshare(
partial_secshares[i], partial_pubshares[i]
):
if i != idx:
raise FaultyParticipantError(
i, "Participant sent invalid partial secshare"
)
else:
# We are not faulty, so it must be the coordinator.
raise FaultyCoordinatorError(
"Coordinator fiddled with the share from me to myself"
)
assert False, "unreachable"


###
### Coordinator
###


## FIXME document the last return value. Or can we make it an
## Optional[List[List[GE]]] instead? That's more elegant but the type checker
## didn't like me when I had tried this earlier.
def coordinator_step(
pmsgs: List[ParticipantMsg], t: int, n: int
) -> Tuple[CoordinatorMsg, DKGOutput, bytes]:
pmsgs: List[ParticipantMsg], t: int, n: int, blame: bool = True
) -> Tuple[CoordinatorMsg, DKGOutput, bytes, List[List[Optional[GE]]]]:
# Sum the commitments to the i-th coefficients for i > 0 # FIXME
#
# This procedure is introduced by Pedersen in Section 5.1 of
Expand All @@ -273,10 +290,16 @@ def coordinator_step(
threshold_pubkey = sum_coms.commitment_to_secret()
pubshares = [sum_coms.pubshare(i) for i in range(n)]

partial_pubshares: List[List[Optional[GE]]]
if blame:
partial_pubshares = [[pmsg.com.pubshare(i) for pmsg in pmsgs] for i in range(n)]
else:
partial_pubshares = [[None for pmsg in pmsgs] for i in range(n)]

dkg_output = DKGOutput(
None,
threshold_pubkey.to_bytes_compressed(),
[pubshare.to_bytes_compressed() for pubshare in pubshares],
)
eq_input = t.to_bytes(4, byteorder="big") + sum_coms.to_bytes()
return cmsg, dkg_output, eq_input
return cmsg, dkg_output, eq_input, partial_pubshares
5 changes: 3 additions & 2 deletions python/example.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,9 @@ async def coordinator(
#


def simulate_chilldkg_full(hostseckeys, t) -> List[Tuple[DKGOutput, RecoveryData]]:
blame = True # TODO Test also blame=False
def simulate_chilldkg_full(
hostseckeys, t, blame=True
) -> List[Tuple[DKGOutput, RecoveryData]]:
# Generate common inputs for all participants and coordinator
n = len(hostseckeys)
hostpubkeys = []
Expand Down
26 changes: 12 additions & 14 deletions python/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,22 +35,21 @@ def rand_polynomial(t):
)


def simulate_simplpedpop(seeds, t) -> List[Tuple[simplpedpop.DKGOutput, bytes]]:
blame = True # TODO Test also blame=False
def simulate_simplpedpop(seeds, t, blame) -> List[Tuple[simplpedpop.DKGOutput, bytes]]:
n = len(seeds)
prets = []
for i in range(n):
prets += [simplpedpop.participant_step1(seeds[i], t, n, i, blame)]
pmsgs = [ret[1] for ret in prets]

cmsg, cout, ceq = simplpedpop.coordinator_step(pmsgs, t, n)
cmsg, cout, ceq, all_partial_pubshares = simplpedpop.coordinator_step(pmsgs, t, n)
pre_finalize_rets = [(cout, ceq)]
for i in range(n):
partial_secshares = [pret[2][i] for pret in prets]
partial_pubshares = all_partial_pubshares[i]
# TODO Test that the protocol fails when wrong shares are sent.
# if i == n - 1:
# partial_secshares[-1] += Scalar(17)
partial_pubshares = [pret[3][i] for pret in prets]
secshare, blame_rec = simplpedpop.participant_step2_prepare_secret_side_inputs(
partial_secshares, partial_pubshares
)
Expand All @@ -66,8 +65,7 @@ def encpedpop_keys(seed: bytes) -> Tuple[bytes, bytes]:
return deckey, enckey


def simulate_encpedpop(seeds, t) -> List[Tuple[simplpedpop.DKGOutput, bytes]]:
blame = True # TODO Test also blame=False
def simulate_encpedpop(seeds, t, blame) -> List[Tuple[simplpedpop.DKGOutput, bytes]]:
n = len(seeds)
enc_prets0 = []
enc_prets1 = []
Expand Down Expand Up @@ -100,9 +98,8 @@ def simulate_encpedpop(seeds, t) -> List[Tuple[simplpedpop.DKGOutput, bytes]]:


def simulate_chilldkg(
hostseckeys, t
hostseckeys, t, blame
) -> List[Tuple[chilldkg.DKGOutput, chilldkg.RecoveryData]]:
blame = True # TODO Test also blame=False
n = len(hostseckeys)

hostpubkeys = []
Expand Down Expand Up @@ -201,12 +198,12 @@ def test_correctness_dkg_output(t, n, dkg_outputs: List[simplpedpop.DKGOutput]):
assert recovered * G == GE.from_bytes_compressed(threshold_pubkey)


def test_correctness(t, n, simulate_dkg, recovery=False):
def test_correctness(t, n, simulate_dkg, recovery=False, blame=True):
seeds = [None] + [random_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[1:], t)
rets = simulate_dkg(seeds[1:], t, blame=blame)
assert len(rets) == n + 1

dkg_outputs = [ret[0] for ret in rets]
Expand All @@ -229,7 +226,8 @@ def test_correctness(t, n, simulate_dkg, recovery=False):
test_vss_correctness()
test_recover_secret()
for t, n in [(1, 1), (1, 2), (2, 2), (2, 3), (2, 5)]:
test_correctness(t, n, simulate_simplpedpop)
test_correctness(t, n, simulate_encpedpop)
test_correctness(t, n, simulate_chilldkg, recovery=True)
test_correctness(t, n, simulate_chilldkg_full, recovery=True)
for blame in [False, True]:
test_correctness(t, n, simulate_simplpedpop, blame=blame)
test_correctness(t, n, simulate_encpedpop, blame=blame)
test_correctness(t, n, simulate_chilldkg, blame=blame, recovery=True)
test_correctness(t, n, simulate_chilldkg_full, blame=blame, recovery=True)

0 comments on commit b018e77

Please sign in to comment.