The primary job of the coordinator is to sequence participants in the ceremony by acting as the point of contact for participants. The coordinator is required to have a DoS-resilient internet connection and sufficiently powerful hardware to verify the transcript rapidly so that they are able to send the transcript to the next participant as soon as possible. Furthermore, the coordinator should be a semi-trusted entity as they have the power to censor participants, although they cannot affect the safety of the setup.
@dataclass
class Witness:
running_products: List[bls.G1Point]
pot_pubkeys: List[bls.G2Point]
@dataclass
class SubTranscript:
num_g1_powers: int
num_g2_powers: int
powers_of_tau: PowersOfTau # as defined in ./README.md
witness: Witness
@dataclass
class Transcript
sub_transcripts: List[SubTranscript]
Upon receiving the transcript back from a participant, the Coordinator MUST perform the following checks.
- Schema Check - Verify that the received
contribution.json
matches thecontributionSchema.json
schema.
def schema_check(contribution_json: str, schema_path: str) -> bool:
with open(schema_path) as schema_file:
schema = json.load(schema_file)
try:
jsonschema.validate(contribution_json, schema)
return True
except Exception:
pass
return False
This check verifies that the number of
def parameter_check(contribution: Contribution, transcript: Transcript) -> bool:
for (sub_contribution, sub_transcript) in zip(contribution.sub_contributions, transcript.sub_transcript):
if sub_contribution.num_g1_powers != sub_transcript.num_g1_powers:
return False
if sub_contribution.num_g2_powers != sub_transcript.num_g2_powers:
return False
if sub_contribution.num_g1_powers != len(sub_contribution.powers_of_tau.g1_powers):
return False
if sub_contribution.num_g2_powers != len(sub_contribution.powers_of_tau.g2_powers):
return False
return True
- Prime Subgroup checks
-
G1 Powers Subgroup check - For each of the
$\mathbb{G}_1$ Powers of Tau (g1_powers
), verify that they are actually elements of the prime-ordered subgroup. -
G2 Powers Subgroup check - For each of the
$\mathbb{G}_2$ Powers of Tau (g2_powers
), verify that they are actually elements of the prime-ordered subgroup. - PoTPubkey Subgroup checks - Check that the PoTPubkey is actually an element of the prime-ordered subgroup.
-
G1 Powers Subgroup check - For each of the
def subgroup_checks(contribution: Contribution) -> bool:
for sub_contribution in contribution.sub_contributions:
if not all(bls.G1.is_in_prime_subgroup(P) for P in sub_contribution.powers_of_tau.g1_powers):
return False
if not all(bls.G2.is_in_prime_subgroup(P) for P in sub_contribution.powers_of_tau.g2_powers):
return False
if not bls.G2.is_in_prime_subgroup(sub_contribution.pot_pubkey):
return False
return True
- Non-zero check - Check that the
pot_pubkey
s are not equal to the point at infinity.
def non_zero_check(contribution: Contribution) -> bool:
for sub_contribution in ceremony.sub_contributions:
if bls.G2.is_inf(sub_contribution.pot_pubkey):
return False
return True
Note: The following pairing checks SHOULD be batched via a random linear combination which would reduce the number of final exponentiations to 2 and decrease the number of Miller loops needed.
-
Tau update construction - Verify that the latest contribution is correctly built on top of the previous ones. Let
$[\tau^1_{k}]_1$ be the first power of tau from the most recent ($k$ -th)contribution
and $[\pi]1^{k-1} := [\tau^1{k-1}]1$ be the transcript after updating from the previous correctcontribution
. Verifying that $\tau$ was updated correctly can then be performed by checking $e([\pi{k-1}]_1, [x_k]2) \stackrel{?}{=}e([\pi{k}]_1, g_2)$
def tau_update_check(contribution: Contribution, transcript: Transcript) -> bool:
for (sub_contribution, sub_transcript) in zip(contribution.sub_contributions, transcript.sub_transcripts):
tau = sub_contribution.powers_of_tau.g1_powers[1]
transcript_product = sub_transcript.witness.running_products
pk = sub_contribution.pot_pubkey
if bls.pairing(transcript_product, pk) != bls.pairing(tau, bls.G2.g2):
return False
return True
-
Correct construction of G1 Powers - Verify that the
$\mathbb{G}_1$ points provided are indeed incremental powers of (the same)$\tau$ . This check is done by asserting that the next$\mathbb{G}_1$ point is the result of multiplying the previous one with$\tau$ :$e([\tau^{i + 1}]_1, g_2) \stackrel{?}{=}e([\tau^i]_1, [\tau]_2)$ . Note that the check that the$\tau$ in$[\tau]_2$ is the same as$\pi_k$ is done via theg2_powers_check()
below which verifies that$e([\tau^i]_1, g_2) \stackrel{?}{=}e(g_2, [\tau^i]_2)$ and$[\tau^i]_1 = [\pi_k]_1$ due to theTranscript
updates rules.
def g1_powers_check(contribution: Contribution) -> bool:
for sub_contribution in contribution.sub_contributions:
powers = sub_contribution.powers_of_tau.g1_powers
pi = sub_contribution.powers_of_tau.g2_powers[1]
for power, next_power in zip(powers[:-1], powers[1:]):
if bls.pairing(bls.G1.g1, next_power) != bls.pairing(power, pi):
return False
return True
-
Correct construction of G2 Powers - Verify that the
$\mathbb{G}_2$ points provided are indeed incremental powers of$\tau$ and that$\tau$ is the same across$\mathbb{G}_1$ and$\mathbb{G}_2$ . This check is done by verifying that$\tau^i$ is the same across$[\tau^i]_1$ and$[\tau^i]_2$ .$e([\tau^i]_1, g_2) \stackrel{?}{=}e(g_2, [\tau^i]_2)$
def g2_powers_check(transcript: Transcript) -> bool:
for sub_contribution in transcript.sub_contributions:
g1_powers = sub_contribution.powers_of_tau.g1_powers
g2_powers = sub_contribution.powers_of_tau.g2_powers
for g1_power, g2_power in zip(g1_powers, g2_powers):
if bls.pairing(bls.G1.g1, g1_power) != bls.pairing(g2_power, bls.G2.g2):
return False
return True
Once the coordinator has performed the above Verification checks, they MUST update the transcript.
def update_transcript(old_transcript: Transcript, contribution: Contribution) -> Transcript:
new_transcript = Transcript(sub_transcripts=[])
for (sub_transcript, sub_ceremony) in zip(old_transcript.sub_transcripts, contribution.sub_ceremonies):
new_sub_transcript = SubTranscript(
num_g1_powers=sub_transcript.num_g1_powers,
num_g2_powers=sub_transcript.num_g2_powers,
powers_of_tau=sub_ceremony.powers_of_tau,
witness=Witness(
running_products=sub_transcript.witness.running_products + [sub_ceremony.powers_of_tau.g1_powers[1]],
pot_pubkeys=sub_transcript.witness.pot_pubkeys + [sub_ceremony.pot_pubkey],
)
)
Once the transcript has been updated, the coordinator MUST get a new Contribution
file to send to the next participant.
def get_new_contribution_file(transcript: Transcript) -> Contribution:
contribution = Contribution(sub_contribution=[])
for sub_transcript in transcript:
contribution.sub_contribution.append(
SubContribution(
num_g1_powers=sub_transcript.num_g1_powers,
num_g2_powers=sub_transcript.num_g2_powers,
powers_of_tau=sub_transcript.powers_of_tau,
pot_pubkey='',
)
)