Skip to content

Commit

Permalink
Reorganize and tidy simplpedpop module
Browse files Browse the repository at this point in the history
  • Loading branch information
real-or-random committed Mar 15, 2024
1 parent c385a7a commit df8694a
Show file tree
Hide file tree
Showing 3 changed files with 128 additions and 113 deletions.
59 changes: 24 additions & 35 deletions reference/reference.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,10 @@
from secp256k1ref.bip340 import schnorr_sign, schnorr_verify
from secp256k1ref.keys import pubkey_gen_plain
from secp256k1ref.util import tagged_hash, int_from_bytes, bytes_from_int

from network import SignerChannel, CoordinatorChannels

from vss import VSSCommitment, GroupInfo
from simplpedpop import (
SimplPedPopR1State,
VSSCommitmentExt,
VSSCommitmentSumExt,
DKGOutput,
simplpedpop_round1,
simplpedpop_pre_finalize,
vss_sum_commitments,
vss_commitments_sum_finalize,
)
import simplpedpop
from util import (
kdf,
tagged_hash_bip_dkg,
Expand Down Expand Up @@ -58,12 +48,12 @@ def decrypt_sum(
return shares_sum


EncPedPopR1State = Tuple[int, bytes, List[bytes], int, Scalar, SimplPedPopR1State]
EncPedPopR1State = Tuple[int, bytes, List[bytes], int, Scalar, simplpedpop.SignerState1]


def encpedpop_round1(
seed: bytes, t: int, n: int, my_deckey: bytes, enckeys: List[bytes], my_idx: int
) -> Tuple[EncPedPopR1State, VSSCommitmentExt, List[Scalar]]:
) -> Tuple[EncPedPopR1State, simplpedpop.Unicast1, List[Scalar]]:
assert t < 2 ** (4 * 8)
n = len(enckeys)

Expand All @@ -72,7 +62,7 @@ def encpedpop_round1(
enc_context = t.to_bytes(4, byteorder="big") + b"".join(enckeys)
seed_ = tagged_hash_bip_dkg("EncPedPop seed", seed + enc_context)

simpl_state, vss_commitment_ext, gen_shares = simplpedpop_round1(
simpl_state, vss_commitment_ext, gen_shares = simplpedpop.signer_round1(
seed_, t, n, my_idx
)
assert len(gen_shares) == n
Expand All @@ -97,9 +87,9 @@ def encpedpop_round1(

def encpedpop_pre_finalize(
state1: EncPedPopR1State,
vss_commitments_sum: VSSCommitmentSumExt,
vss_commitments_sum: simplpedpop.Broadcast1,
enc_shares_sum: Scalar,
) -> Tuple[bytes, DKGOutput]:
) -> Tuple[bytes, simplpedpop.DKGOutput]:
t, my_deckey, enckeys, my_idx, self_share, simpl_state = state1
n = len(enckeys)

Expand All @@ -110,7 +100,7 @@ def encpedpop_pre_finalize(
enc_context = t.to_bytes(4, byteorder="big") + b"".join(enckeys)
shares_sum = decrypt_sum(enc_shares_sum, my_deckey, enckeys, my_idx, enc_context)
shares_sum += self_share
eta, dkg_output = simplpedpop_pre_finalize(
eta, dkg_output = simplpedpop.signer_pre_finalize(
simpl_state, vss_commitments_sum, shares_sum
)
eta += b"".join(enckeys)
Expand Down Expand Up @@ -146,7 +136,7 @@ def chilldkg_session_params(

def chilldkg_round1(
seed: bytes, params: SessionParams
) -> Tuple[ChillDKGStateR1, VSSCommitmentExt, List[Scalar]]:
) -> Tuple[ChillDKGStateR1, simplpedpop.Unicast1, List[Scalar]]:
my_hostseckey, my_hostpubkey = chilldkg_hostkey_gen(seed)
(hostpubkeys, t, params_id) = params
n = len(hostpubkeys)
Expand All @@ -159,13 +149,13 @@ def chilldkg_round1(
return state1, vss_commitment_ext, enc_gen_shares


ChillDKGStateR2 = Tuple[SessionParams, bytes, DKGOutput]
ChillDKGStateR2 = Tuple[SessionParams, bytes, simplpedpop.DKGOutput]


def chilldkg_round2(
seed: bytes,
state1: ChillDKGStateR1,
vss_commitments_sum: VSSCommitmentSumExt,
vss_commitments_sum: simplpedpop.Broadcast1,
all_enc_shares_sum: List[Scalar],
) -> Tuple[ChillDKGStateR2, bytes]:
(my_hostseckey, _) = chilldkg_hostkey_gen(seed)
Expand All @@ -186,7 +176,9 @@ def chilldkg_round2(
return state2, certifying_eq_round1(my_hostseckey, eta)


def chilldkg_finalize(state2: ChillDKGStateR2, cert: bytes) -> Optional[DKGOutput]:
def chilldkg_finalize(
state2: ChillDKGStateR2, cert: bytes
) -> Optional[simplpedpop.DKGOutput]:
"""
A return value of None means that `cert` is not a valid certificate.
Expand All @@ -212,7 +204,7 @@ def chilldkg_backup(state2: ChillDKGStateR2, cert: bytes) -> Any:

async def chilldkg(
chan: SignerChannel, seed: bytes, my_hostseckey: bytes, params: SessionParams
) -> Optional[Tuple[DKGOutput, Any]]:
) -> Optional[Tuple[simplpedpop.DKGOutput, Any]]:
# TODO Top-level error handling
state1, vss_commitment_ext, enc_gen_shares = chilldkg_round1(seed, params)
chan.send((vss_commitment_ext, enc_gen_shares))
Expand Down Expand Up @@ -285,24 +277,21 @@ async def chilldkg_coordinate(
) -> Union[GroupInfo, Literal[False]]:
(hostpubkeys, t, params_id) = params
n = len(hostpubkeys)
vss_commitments_ext = []
simpl_round1_ins = []
all_enc_shares_sum = [Scalar(0)] * n
for i in range(n):
vss_commitment_ext, enc_shares = await chans.receive_from(i)
vss_commitments_ext += [vss_commitment_ext]
simpl_round1_in, enc_shares = await chans.receive_from(i)
simpl_round1_ins += [simpl_round1_in]
all_enc_shares_sum = [all_enc_shares_sum[j] + enc_shares[j] for j in range(n)]
vss_commitments_sum = vss_sum_commitments(vss_commitments_ext, t)
chans.send_all((vss_commitments_sum, all_enc_shares_sum))
eta = serialize_eta(
t,
vss_commitments_sum_finalize(vss_commitments_sum, t, n),
hostpubkeys,
all_enc_shares_sum,
simpl_round1_outs = simplpedpop.coordinator_round1(simpl_round1_ins, t)
chans.send_all((simpl_round1_outs, all_enc_shares_sum))
vss_commitment = simplpedpop.aggregate_vss_commitments(
simpl_round1_outs.first_ges, simpl_round1_outs.remaining_ges, t, n
)
eta = serialize_eta(t, vss_commitment, hostpubkeys, all_enc_shares_sum)
cert = await certifying_eq_coordinate(chans, hostpubkeys)
if not verify_cert(hostpubkeys, eta, cert):
return False
vss_commitment = vss_commitments_sum_finalize(vss_commitments_sum, t, n)
return vss_commitment.group_info(n)


Expand Down Expand Up @@ -345,7 +334,7 @@ def deserialize_eta(b: bytes) -> Any:
# Recovery requires the seed and the public backup
def chilldkg_recover(
seed: bytes, backup: Any, context_string: bytes
) -> Union[Tuple[DKGOutput, SessionParams], Literal[False]]:
) -> Union[Tuple[simplpedpop.DKGOutput, SessionParams], Literal[False]]:
(eta, cert) = backup
try:
(t, vss_commit, hostpubkeys, all_enc_shares_sum) = deserialize_eta(eta)
Expand Down Expand Up @@ -379,6 +368,6 @@ def chilldkg_recover(

# Compute shared & individual pubkeys
(shared_pubkey, signer_pubkeys) = vss_commit.group_info(n)
dkg_output = (shares_sum, shared_pubkey, signer_pubkeys)
dkg_output = simplpedpop.DKGOutput(shares_sum, shared_pubkey, signer_pubkeys)

return dkg_output, params
156 changes: 92 additions & 64 deletions reference/simplpedpop.py
Original file line number Diff line number Diff line change
@@ -1,30 +1,54 @@
from typing import Tuple, List, NamedTuple
from typing import List, NamedTuple, NewType, Tuple

from secp256k1ref.secp256k1 import GE, Scalar
from secp256k1ref.bip340 import schnorr_sign, schnorr_verify


from vss import VSS, VSSCommitment
from secp256k1ref.secp256k1 import GE, Scalar
from util import (
kdf,
BIP_TAG,
InvalidContributionError,
VSSVerifyError,
)
from vss import VSS, VSSCommitment


###
### Proofs of possession (Pops)
###


Pop = NewType("Pop", bytes)

POP_MSG_TAG = (BIP_TAG + "pop message").encode()

Pop = bytes

def pop_msg(idx: int):
return POP_MSG_TAG + idx.to_bytes(4, byteorder="big")


def pop_prove(seckey, my_idx, aux_rand: bytes = 32 * b"\x00"):
# TODO: What to do with aux_rand?
sig = schnorr_sign(pop_msg(my_idx), seckey, aux_rand)
return Pop(sig)


def pop_verify(pop: Pop, pubkey: bytes, idx: int):
return schnorr_verify(pop_msg(idx), pubkey, pop)


###
### Messages
###


class Unicast1(NamedTuple):
"""Round 1 message from signer to coordinator"""

# An extended VSS Commitment is a VSS commitment with a proof of knowledge
# TODO This should be called SimplPedPopRound1SignerToCoordinator or similar
class VSSCommitmentExt(NamedTuple):
com: VSSCommitment
pop: Pop


# A VSS Commitment Sum is the sum of multiple extended VSS Commitments
# TODO This should be called SimplPedPopRound1CoordinatorToSigner or similar
class VSSCommitmentSumExt(NamedTuple):
class Broadcast1(NamedTuple):
"""Round 1 message from coordinator to all signers"""

first_ges: List[GE]
remaining_ges: List[GE]
pops: List[Pop]
Expand All @@ -38,38 +62,39 @@ def to_bytes(self) -> bytes:
) + b"".join(self.pops)


# 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'.
def vss_sum_commitments(coms: List[VSSCommitmentExt], t: int) -> VSSCommitmentSumExt:
first_ges = [com[0].ges[0] for com in coms]
remaining_ges_sum = [GE.sum(*(com[0].ges[j] for com in coms)) for j in range(1, t)]
poks = [com[1] for com in coms]
return VSSCommitmentSumExt(first_ges, remaining_ges_sum, poks)
def aggregate_vss_commitments(
first_ges: List[GE], remaining_ges: List[GE], t: int, n: int
) -> VSSCommitment:
# Sum the commitments to the constant coefficients
return VSSCommitment([GE.sum(*(first_ges[i] for i in range(n)))] + remaining_ges)


def vss_commitments_sum_finalize(
vss_commitments_sum: VSSCommitmentSumExt, t: int, n: int
) -> VSSCommitment:
# Strip the signatures and sum the commitments to the constant coefficients
return VSSCommitment(
[GE.sum(*(vss_commitments_sum.first_ges[i] for i in range(n)))]
+ vss_commitments_sum.remaining_ges
)
###
### Signer
###


class SignerState1(NamedTuple):
t: int
n: int
my_idx: int


SimplPedPopR1State = Tuple[int, int, int]
# TODO This should probably moved somewhere else as its common to all DKGs
class DKGOutput(NamedTuple):
share: Scalar
shared_pubkey: GE
pubkeys: List[GE]

POP_MSG_TAG = (BIP_TAG + "VSS PoK").encode()

# To keep the algorithms of SimplPedPop and EncPedPop purely non-interactive computations,
# we omit explicit invocations of an interactive equality check protocol.
# ChillDKG will take care of invoking the equality check protocol.


def simplpedpop_round1(
def signer_round1(
seed: bytes, t: int, n: int, my_idx: int
) -> Tuple[SimplPedPopR1State, VSSCommitmentExt, List[Scalar]]:
) -> Tuple[SignerState1, Unicast1, List[Scalar]]:
"""
Generate SimplPedPop messages to be sent to the coordinator.
Expand All @@ -84,58 +109,61 @@ def simplpedpop_round1(

vss = VSS.generate(seed, t)
shares = vss.shares(n)
pop = pop_prove(vss.secret().to_bytes(), my_idx)

# TODO: fix aux_rand
sig = schnorr_sign(
POP_MSG_TAG + my_idx.to_bytes(4, byteorder="big"),
vss.secret().to_bytes(),
kdf(seed, "VSS PoK"),
)

vss_commitment_ext = VSSCommitmentExt(vss.commit(), sig)
state = (t, n, my_idx)
return state, vss_commitment_ext, shares

msg = Unicast1(vss.commit(), pop)
state = SignerState1(t, n, my_idx)
return state, msg, shares

DKGOutput = Tuple[Scalar, GE, List[GE]]


def simplpedpop_pre_finalize(
state: SimplPedPopR1State,
vss_commitments_sum: VSSCommitmentSumExt,
def signer_pre_finalize(
state: SignerState1,
msg: Broadcast1,
shares_sum: Scalar,
) -> Tuple[bytes, DKGOutput]:
"""
Take the messages received from the coordinator and return eta to be compared and DKG output
:param SimplPedPopR1State state: the signer's state output by simplpedpop_round1
:param VSSCommitmentSumExt vss_commitments_sum: sum of VSS commitments received from the coordinator
:param SignerState state: the signer's state after round 1 (output by signer_round1)
:param Broadcast1 msgs: round 1 broadcast message received from the coordinator
:param Scalar shares_sum: sum of shares for this participant received from all participants (including this participant)
:return: the data `eta` that must be input to an equality check protocol, the final share, the shared pubkey, the individual participants' pubkeys
"""
t, n, my_idx = state
assert len(vss_commitments_sum.first_ges) == n
assert len(vss_commitments_sum.remaining_ges) == t - 1
assert len(vss_commitments_sum.pops) == n
first_ges, remaining_ges, pops = msg
assert len(first_ges) == n
assert len(remaining_ges) == t - 1
assert len(pops) == n

for i in range(n):
P_i = vss_commitments_sum.first_ges[i]
P_i = first_ges[i]
if P_i.infinity:
# TODO This branch can go away once we add real serializations.
# If the serialized pubkey is infinity, pop_verify will simply fail.
raise InvalidContributionError(i, "Participant sent invalid commitment")
else:
pk_i = P_i.to_bytes_xonly()
if not schnorr_verify(
POP_MSG_TAG + i.to_bytes(4, byteorder="big"),
pk_i,
vss_commitments_sum.pops[i],
):
if not pop_verify(pops[i], P_i.to_bytes_xonly(), i):
raise InvalidContributionError(
i, "Participant sent invalid proof-of-knowledge"
)
# Strip the signatures and sum the commitments to the constant coefficients
vss_commitment = vss_commitments_sum_finalize(vss_commitments_sum, t, n)
vss_commitment = aggregate_vss_commitments(first_ges, remaining_ges, t, n)
if not vss_commitment.verify(my_idx, shares_sum):
raise VSSVerifyError()
eta = t.to_bytes(4, byteorder="big") + vss_commitment.to_bytes()
shared_pubkey, signer_pubkeys = vss_commitment.group_info(n)
return eta, (shares_sum, shared_pubkey, signer_pubkeys)
return eta, DKGOutput(shares_sum, shared_pubkey, signer_pubkeys)


###
### Coordinator
###


# 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'.
def coordinator_round1(coms: List[Unicast1], t: int) -> Broadcast1:
first_ges = [com[0].ges[0] for com in coms]
remaining_ges_sum = [GE.sum(*(com[0].ges[j] for com in coms)) for j in range(1, t)]
pops = [com[1] for com in coms]
return Broadcast1(first_ges, remaining_ges_sum, pops)
Loading

0 comments on commit df8694a

Please sign in to comment.