From 2b9b0b1875ae47357449ea70f43d3aa73f7f3b90 Mon Sep 17 00:00:00 2001 From: mllwchrry Date: Thu, 30 Jul 2026 19:25:16 +0300 Subject: [PATCH] Add a BIP for ChillDKG: Distributed Key Generation for FROST --- .typos.toml | 1 + bip-chilldkg.md | 1510 +++++++++++++++++ bip-chilldkg/COPYING | 21 + bip-chilldkg/all.sh | 9 + bip-chilldkg/images/Makefile | 4 + bip-chilldkg/images/chilldkg-sequence.png | Bin 0 -> 14398 bytes bip-chilldkg/images/chilldkg-sequence.txt | 22 + bip-chilldkg/python/.ruff.toml | 12 + bip-chilldkg/python/chilldkg_ref/__init__.py | 7 + bip-chilldkg/python/chilldkg_ref/chilldkg.py | 1256 ++++++++++++++ bip-chilldkg/python/chilldkg_ref/encpedpop.py | 470 +++++ .../python/chilldkg_ref/simplpedpop.py | 429 +++++ bip-chilldkg/python/chilldkg_ref/util.py | 106 ++ bip-chilldkg/python/chilldkg_ref/vss.py | 153 ++ bip-chilldkg/python/example.py | 307 ++++ .../python/gen_vector_utils/__init__.py | 0 .../python/gen_vector_utils/coordinator.py | 459 +++++ .../python/gen_vector_utils/fixtures.py | 28 + .../python/gen_vector_utils/participant.py | 992 +++++++++++ .../python/gen_vector_utils/session.py | 437 +++++ bip-chilldkg/python/gen_vector_utils/util.py | 121 ++ bip-chilldkg/python/gen_vectors.py | 64 + bip-chilldkg/python/mypy.ini | 4 + .../secp256k1lab/.github/workflows/main.yml | 34 + bip-chilldkg/python/secp256k1lab/.gitignore | 10 + .../python/secp256k1lab/.python-version | 1 + bip-chilldkg/python/secp256k1lab/CHANGELOG.md | 25 + bip-chilldkg/python/secp256k1lab/COPYING | 23 + bip-chilldkg/python/secp256k1lab/README.md | 13 + .../python/secp256k1lab/pyproject.toml | 34 + .../secp256k1lab/src/secp256k1lab/__init__.py | 0 .../secp256k1lab/src/secp256k1lab/bip340.py | 73 + .../secp256k1lab/src/secp256k1lab/ecdh.py | 16 + .../secp256k1lab/src/secp256k1lab/keys.py | 15 + .../secp256k1lab/src/secp256k1lab/py.typed | 0 .../src/secp256k1lab/secp256k1.py | 483 ++++++ .../secp256k1lab/src/secp256k1lab/util.py | 24 + .../python/secp256k1lab/test/__init__.py | 5 + .../python/secp256k1lab/test/test_bip340.py | 51 + .../python/secp256k1lab/test/test_ecdh.py | 18 + .../secp256k1lab/test/test_secp256k1.py | 180 ++ .../secp256k1lab/test/vectors/bip340.csv | 20 + bip-chilldkg/python/tests.py | 914 ++++++++++ bip-chilldkg/python/tests.sh | 36 + bip-chilldkg/vectors/COPYING | 9 + .../vectors/coordinator_finalize_vectors.json | 429 +++++ .../coordinator_investigate_vectors.json | 125 ++ .../vectors/coordinator_step1_vectors.json | 989 +++++++++++ .../vectors/hostpubkey_gen_vectors.json | 48 + bip-chilldkg/vectors/params_hash_vectors.json | 108 ++ .../vectors/participant_finalize_vectors.json | 274 +++ .../participant_investigate_vectors.json | 266 +++ .../vectors/participant_step1_vectors.json | 956 +++++++++++ .../vectors/participant_step2_vectors.json | 772 +++++++++ bip-chilldkg/vectors/recover_vectors.json | 181 ++ 55 files changed, 12544 insertions(+) create mode 100644 bip-chilldkg.md create mode 100644 bip-chilldkg/COPYING create mode 100755 bip-chilldkg/all.sh create mode 100644 bip-chilldkg/images/Makefile create mode 100644 bip-chilldkg/images/chilldkg-sequence.png create mode 100644 bip-chilldkg/images/chilldkg-sequence.txt create mode 100644 bip-chilldkg/python/.ruff.toml create mode 100644 bip-chilldkg/python/chilldkg_ref/__init__.py create mode 100644 bip-chilldkg/python/chilldkg_ref/chilldkg.py create mode 100644 bip-chilldkg/python/chilldkg_ref/encpedpop.py create mode 100644 bip-chilldkg/python/chilldkg_ref/simplpedpop.py create mode 100644 bip-chilldkg/python/chilldkg_ref/util.py create mode 100644 bip-chilldkg/python/chilldkg_ref/vss.py create mode 100755 bip-chilldkg/python/example.py create mode 100644 bip-chilldkg/python/gen_vector_utils/__init__.py create mode 100644 bip-chilldkg/python/gen_vector_utils/coordinator.py create mode 100644 bip-chilldkg/python/gen_vector_utils/fixtures.py create mode 100644 bip-chilldkg/python/gen_vector_utils/participant.py create mode 100644 bip-chilldkg/python/gen_vector_utils/session.py create mode 100644 bip-chilldkg/python/gen_vector_utils/util.py create mode 100755 bip-chilldkg/python/gen_vectors.py create mode 100644 bip-chilldkg/python/mypy.ini create mode 100644 bip-chilldkg/python/secp256k1lab/.github/workflows/main.yml create mode 100644 bip-chilldkg/python/secp256k1lab/.gitignore create mode 100644 bip-chilldkg/python/secp256k1lab/.python-version create mode 100644 bip-chilldkg/python/secp256k1lab/CHANGELOG.md create mode 100644 bip-chilldkg/python/secp256k1lab/COPYING create mode 100644 bip-chilldkg/python/secp256k1lab/README.md create mode 100644 bip-chilldkg/python/secp256k1lab/pyproject.toml create mode 100644 bip-chilldkg/python/secp256k1lab/src/secp256k1lab/__init__.py create mode 100644 bip-chilldkg/python/secp256k1lab/src/secp256k1lab/bip340.py create mode 100644 bip-chilldkg/python/secp256k1lab/src/secp256k1lab/ecdh.py create mode 100644 bip-chilldkg/python/secp256k1lab/src/secp256k1lab/keys.py create mode 100644 bip-chilldkg/python/secp256k1lab/src/secp256k1lab/py.typed create mode 100644 bip-chilldkg/python/secp256k1lab/src/secp256k1lab/secp256k1.py create mode 100644 bip-chilldkg/python/secp256k1lab/src/secp256k1lab/util.py create mode 100644 bip-chilldkg/python/secp256k1lab/test/__init__.py create mode 100644 bip-chilldkg/python/secp256k1lab/test/test_bip340.py create mode 100644 bip-chilldkg/python/secp256k1lab/test/test_ecdh.py create mode 100644 bip-chilldkg/python/secp256k1lab/test/test_secp256k1.py create mode 100644 bip-chilldkg/python/secp256k1lab/test/vectors/bip340.csv create mode 100755 bip-chilldkg/python/tests.py create mode 100755 bip-chilldkg/python/tests.sh create mode 100644 bip-chilldkg/vectors/COPYING create mode 100644 bip-chilldkg/vectors/coordinator_finalize_vectors.json create mode 100644 bip-chilldkg/vectors/coordinator_investigate_vectors.json create mode 100644 bip-chilldkg/vectors/coordinator_step1_vectors.json create mode 100644 bip-chilldkg/vectors/hostpubkey_gen_vectors.json create mode 100644 bip-chilldkg/vectors/params_hash_vectors.json create mode 100644 bip-chilldkg/vectors/participant_finalize_vectors.json create mode 100644 bip-chilldkg/vectors/participant_investigate_vectors.json create mode 100644 bip-chilldkg/vectors/participant_step1_vectors.json create mode 100644 bip-chilldkg/vectors/participant_step2_vectors.json create mode 100644 bip-chilldkg/vectors/recover_vectors.json diff --git a/.typos.toml b/.typos.toml index eb6e77858d..0b851c9ee2 100644 --- a/.typos.toml +++ b/.typos.toml @@ -22,6 +22,7 @@ extend-ignore-re = [ [default.extend-words] # NOTE: use here for false-positives anc = "anc" +ges = "ges" PSBT = "PSBT" ser = "ser" # Names diff --git a/bip-chilldkg.md b/bip-chilldkg.md new file mode 100644 index 0000000000..fea450da8e --- /dev/null +++ b/bip-chilldkg.md @@ -0,0 +1,1510 @@ +``` +BIP: ? +Title: ChillDKG: Distributed Key Generation for FROST +Layer: Applications +Authors: Tim Ruffing + Jonas Nick + Illia Melnyk + Mariia Zhvanko + Sivaram Dhakshinamoorthy +Status: Draft +Type: Specification +Assigned: ? +License: CC0-1.0 +License-Code: MIT +Discussion: 2024-07-08: https://groups.google.com/g/bitcoindev/c/HE3HSnGTpoQ/m/euZvPxKeAQAJ +Version: 0.3.0 +Requires: 445 +``` + +# ChillDKG: Distributed Key Generation for FROST + +### Abstract + +This Bitcoin Improvement Proposal proposes ChillDKG, a distributed key generation protocol (DKG) for use with the FROST Schnorr threshold signature scheme. + +### Copyright + +This document is made available under [CC0 1.0 Universal](https://creativecommons.org/publicdomain/zero/1.0/). +The accompanying auxiliary files are licensed under the [MIT License](https://opensource.org/license/mit). +The collection of test vectors therein (subdirectory `vectors/`) is, in addition to the MIT License, available under CC0 1.0 Universal. + +## Introduction + +### Motivation + +The FROST threshold signature scheme [[KG20](https://eprint.iacr.org/2020/852), [CKM21](https://eprint.iacr.org/2021/1375), [BTZ22](https://eprint.iacr.org/2022/833), [CGRS23](https://eprint.iacr.org/2023/899)] enables `t`-of-`n` Schnorr signatures, +in which some threshold `t` of a group of `n` participants is required to produce a signature. +FROST guarantees unforgeability as long as at most `t - 1` participants are compromised +and remains functional as long as `t` honest participants do not lose their secret key material, +where `t` and `n` can be chosen arbitrarily (as long as `1 <= t <= n`).[^t-edge-cases] +As a result, threshold signatures increase both security and availability, +enabling users to escape the inherent dilemma between the contradicting goals of protecting a single secret key against theft and data loss simultaneously. + +[^t-edge-cases]: While `t = n` and `t = 1` are in principle supported, simpler alternatives are available in these cases. +In the case of `t = n`, using a dedicated `n`-of-`n` multi-signature scheme such as MuSig2 [[BIP 327](bip-0327.mediawiki)] instead of FROST avoids the need for an interactive DKG. +The case `t = 1` can be realized by letting one participant generate an ordinary [BIP 340](bip-0340.mediawiki) key pair and transmitting the key pair to every other participant, who can check its consistency and then simply use the ordinary [BIP 340](bip-0340.mediawiki) signing algorithm. +Participants still need to ensure that they agree on a key pair. A detailed specification is not in the scope of this document. + +[BIP 445](bip-0445.md) provides a specification of the FROST signing protocol tailored to [BIP 340](bip-0340.mediawiki) Schnorr signatures as deployed in Bitcoin. +However, in order to use the specified protocol, the participants need to generate a shared *threshold public key* (representing the entire group with its `t`-of-`n` policy), +together with `n` corresponding *secret shares* (held by the `n` participants) that allow to sign under the threshold public key. +This key generation can, in principle, be performed by a trusted dealer who takes care of generating the threshold public key as well as all `n` secret shares, +which are then distributed to the `n` participants via secure channels. +However, the trusted dealer constitutes a single point of failure: +a compromised dealer can forge signatures arbitrarily. + +An interactive *distributed key generation* (DKG) protocol session by all participants avoids the need for a trusted dealer. +There exist a number of DKG protocols with different requirements and guarantees in the cryptographic literature. +Most suitable for the use with FROST is the PedPop DKG protocol [[KG20](https://eprint.iacr.org/2020/852), [CKM21](https://eprint.iacr.org/2021/1375), [CGRS23](https://eprint.iacr.org/2023/899)] ("Pedersen DKG [[Ped92](https://doi.org/10.1007/3-540-46766-1_9), [GJKR07](https://doi.org/10.1007/s00145-006-0347-3)] with proofs of possession"), +which, like FROST, does not impose restrictions on the choice of `t` and `n`. + +But similar to most DKG protocols in the literature, PedPop has strong requirements on the communication channels between participants, +which make it difficult to deploy in practice: +First, it assumes that participants have secure (i.e., authenticated and encrypted) channels between each other, +which is necessary to avoid man-in-the-middle attacks and to ensure confidentiality of secret shares when delivering them to individual participants. +Second, PedPop assumes that all participants have access to some external consensus or reliable broadcast mechanism +that ensures they have an identical view of the protocol messages exchanged during DKG. +This will, in turn, ensure that all participants eventually reach agreement over the results of the DKG, +which include not only parameters such as the generated threshold public key +but also whether the DKG has succeeded at all. + +To understand the necessity of reaching agreement, +consider the example of a DKG to set up a 2-of-3 Bitcoin wallet +in which two participants are honest but the third participant is malicious. +The malicious participant sends invalid secret shares to the first honest participant, but valid shares to the second honest participant. +While the first honest participant cannot finish the DKG, +the second honest participant will believe that the DKG has finished successfully +and thus may be willing to send funds to the resulting threshold public key. +But this constitutes a catastrophic failure: +Those funds will be lost irrevocably because the single remaining secret share of the second participant will not be sufficient to produce a signature (without the help of the malicious participant).[^resharing-attack] + +[^resharing-attack]: A very similar attack has been observed in the implementation of a resharing scheme [[AS20](https://eprint.iacr.org/2020/1052), Section 3]. + +To sum up, there is currently no description of PedPop that +does not assume the availability of external secure channels and consensus +and thus can be turned into a standalone implementation. +To overcome these issues, we propose ChillDKG in this BIP. +ChillDKG is a variant of PedPop with "batteries included", +i.e., it incorporates minimal but sufficient implementations of secure channels and consensus +and thus does not have external dependencies. +This makes it easy to implement and deploy, and +we provide detailed algorithmic specifications in the form of Python code. + +### Design + +We assume a network setup in which participants have point-to-point connections to an untrusted coordinator. +This will enable bandwidth optimizations and is common also in implementations of the signing stage of FROST. +Participants are identified and authenticated via long-term public keys. + +The basic building block of ChillDKG is the SimplPedPop protocol (a simplified variant of PedPop), +which has been designed specifically for FROST. +SimplPedPop is proven to be secure when combined with FROST [[CGRS23](https://eprint.iacr.org/2023/899)], +and its output contains, in addition to the threshold public key, separate per-participant public shares thereof, +which allow for partial verification of contributions in a FROST signing session. + +Besides external secure channels, SimplPedPop depends on an external *equality check protocol*. +The equality check protocol serves as an abstraction of a consensus mechanism: +Its only purpose is to check that, at the end of SimplPedPop, all participants have received identical protocol messages. + +Our goal is to turn SimplPedPop into a standalone DKG protocol without external dependencies. +We then follow a modular approach that removes one dependency at a time. +First, we take care of secure channels by wrapping SimplPedPop in a protocol EncPedPop, +which relies on pairwise ECDH key exchanges between the participants to encrypt secret shares. +Finally, we add a concrete equality check protocol CertEq to EncPedPop to obtain a standalone DKG protocol ChillDKG. + +Our equality check protocol CertEq consists of every participant simply collecting a list of valid signatures on the session transcript from all `n` participants +before finalizing the DKG session with some threshold public key as output. +The list of signatures, also called a *success certificate*, can convince any other honest participant +(ultimately at the time of a signing request) +that the DKG session has indeed been successful. +This is sufficient to exclude the catastrophic failure described in the previous section. + +As an additional feature of ChillDKG, the DKG outputs for any signing device can be fully recovered from +a backup of a single *host secret key* specific to the device, +(the essential parts of) the public transcripts of the DKG sessions, +and the corresponding success certificates. +To simplify the interface, we combine the transcript data and the session certificate into a single byte string called the *recovery data*, +which is common to all participants and does not need to be kept confidential. +Recovering a device that has participated in a DKG session then requires just the device's host secret key and the recovery data, +the latter of which can be obtained from any cooperative participant (or the coordinator) or from an untrusted backup provider. + +ChillDKG outputs a threshold public key that can be safely used in Taproot outputs [[BIP 341](bip-0341.mediawiki)]. +In contrast, a standard PedPop implementation would allow a malicious participant to secretly embed a Taproot commitment to a script path within the threshold public key. +If such a key was used directly in a Taproot output, the malicious participant could spend the output through their hidden script path, bypassing the requirement for `t - 1` additional signatures. +While [BIP 341](bip-0341.mediawiki) outlines special precautions for using threshold public keys generated by standard PedPop, ChillDKG eliminates this vulnerability entirely, providing built-in protection against accidental misuse. + +If a ChillDKG session fails due to the participants or the coordinator deviating from the protocol, +any aborting party will be able to identify and blame a single party responsible for the failure +(assuming the network, and, depending on the circumstances, the coordinator, are reliable). + +These features make ChillDKG usable in a wide range of applications. +As a consequence of this broad applicability, there will necessarily be scenarios in which specialized protocols need less communication overhead and fewer rounds, +e.g., when setting up multiple signing devices in a single location. + +In summary, we aim for the following design goals: + + - **Standalone**: ChillDKG is fully specified, requiring no external secure channels or consensus mechanism. + - **Conditional agreement**: If a ChillDKG session succeeds for one honest participant, this participant will be able to convince every other honest participant that the session has succeeded. + - **No restriction on threshold**: Like the FROST signing protocol, ChillDKG supports any threshold `t <= n`, including `t > n/2` (also called "dishonest majority"). + - **Broad applicability**: ChillDKG supports a wide range of scenarios, from those where the signing devices are owned and connected by a single individual to those where multiple owners manage the devices from distinct locations. + - **Simple backups**: ChillDKG allows recovering the DKG output using the host secret key and common recovery data shared among all participants and the coordinator. This eliminates the need for session-specific backups, simplifying user experience. + - **Untrusted coordinator**: Like FROST, ChillDKG uses a coordinator that relays messages between the participants. This simplifies the network topology, and the coordinator additionally reduces communication overhead by aggregating some of the messages. A faulty coordinator can force the DKG to fail but cannot negatively affect the security of the DKG. + - **Per-participant public shares**: ChillDKG supports partial signature verification in FROST signing sessions. + - **Taproot-safe threshold public key**: ChillDKG prevents malicious participants from embedding a hidden Taproot commitment to a script path in the threshold public key. + - **Blame functionality**: If a ChillDKG session aborts, it is possible to identify and blame a single party responsible for the failure (assuming the network, and, depending on the circumstances, the coordinator, are reliable). + +In summary, ChillDKG incorporates solutions for both secure channels and consensus and simplifies backups in practice. +As a result, it fits a wide range of application scenarios, +and due to its low overhead, we recommend ChillDKG even if secure communication channels or a consensus mechanism (e.g., a BFT protocol or a reliable broadcast mechanism) are readily available. + +#### Robustness is Not a Goal + +Despite the blame functionality, ChillDKG does not provide robustness, i.e., the protocol is not designed to succeed in the presence of faulty participants. +In fact, a single participant can cause the protocol to fail, either due to malicious intent, software bugs, or unreliable communication links. +In such cases, users must investigate and resolve the issue before the DKG can output key material. + +Adding robustness to ChillDKG would require the coordinator to exclude participants that appear unresponsive or faulty, which degrades the setup already from the beginning from `t`-of-`n` to `(t-1)`-of-`(n-1)`. +This approach is undesirable in most scenarios, as a faulty coordinator would have the power to exclude participants at will, +and even if ChillDKG's design did not include a coordinator and participants had direct communication links to each other, it would be unclear how to achieve robustness in a dishonest majority setting. + +Moreover, we believe that it is preferable to err on the side of caution even in the case of benign failures. +For example, consider a key generation ceremony for a threshold cold wallet intended to store large amounts of Bitcoin. +If it turns out that one of the devices participating appears non-responsive, e.g., due to a loss of network or a software bug, +users will typically prefer security to progress, and abort the protocol instead of forcing successful termination of the ceremony by excluding the device from the DKG session. +While warnings can be presented to users in this case, users tend to misunderstand and ignore them. + +Even in distributed systems with strict liveness requirements, e.g., a system run by a large federation of nodes of which a majority is trusted, what is typically necessary for the liveness of the system is the continued ability to *produce signatures*. +However, the setup of keys is typically performed in a one-time ceremony at the inception of the system (and possibly repeated in large time intervals, e.g., every few months). +In other words, what is primarily required to ensure liveness in these applications is a robust signing protocol +(and a solution for FROST exists [[RRJSS22](https://eprint.iacr.org/2022/550)]), and not a robust DKG protocol. + +### Structure of this Document + +This BIP includes a normative reference implementation in Python 3.12 +(see [`python/chilldkg_ref/chilldkg.py`](./bip-chilldkg/python/chilldkg_ref/chilldkg.py)). +Due to the complexity of ChillDKG, we refrain from providing an additional pseudocode description. + +To ease understanding of the design and reference code, +we provide a technical overview of the internals of ChillDKG in [Section "Internals of ChillDKG"](#internals-of-chilldkg). +For those who would like to use a ChillDKG implementation in their applications and systems, +we explain the external interface and usage considerations of ChillDKG in [Section "Usage of ChillDKG"](#usage-of-chilldkg). + +## Internals of ChillDKG + +This section provides a detailed technical overview of the internals of ChillDKG, +which includes as building blocks the DKG protocols SimplPedPop and EncPedPop, and the equality check protocol CertEq. +The contents of this section are purely informational and not strictly required to implement or use ChillDKG, +and some details present in the normative Python reference implementation are omitted. + +We stress that **this document does not endorse the direct use of SimplPedPop or EncPedPop as DKG protocols**. +While SimplPedPop and EncPedPop may in principle serve as building blocks of other DKG protocols (e.g., for applications that already incorporate a consensus mechanism), +this requires careful further consideration, which is not in the scope of this document. +Consequently, implementations **should not** expose the algorithms of the building blocks as part of a high-level API, which is intended to be safe to use. + +### DKG Protocol SimplPedPop + +(See [`python/chilldkg_ref/simplpedpop.py`](./bip-chilldkg/python/chilldkg_ref/simplpedpop.py).) + +The SimplPedPop protocol has been proposed by Chu, Gerhart, Ruffing, and Schröder [Section 4, [CGRS23](https://eprint.iacr.org/2023/899)]. +We make the following modifications as compared to the original SimplPedPop proposal: + + - Every participant holds a secret seed, from which all required random values are derived deterministically using a pseudorandom function (based on tagged hashes [[BIP 340](bip-0340.mediawiki)]). + - Individual participants' public shares are added to the output of the DKG. This allows partial signature verification. + - The participants send VSS commitments to an untrusted coordinator instead of directly to each other. This lets the coordinator aggregate VSS commitments, which reduces communication costs. Nevertheless, if a session fails, participants are able to investigate who provided invalid secret shares by asking the coordinator for the other participants' individual contributions to their public share. + - To prevent a malicious participant from embedding a Taproot script path in the threshold public key, the participants tweak the VSS commitment such that the corresponding threshold public key has an unspendable script path. + - The proofs of possession are omitted from the data for the equality check.[^pop-eq] This reduces the size of the backups in ChillDKG. + +[^pop-eq]: An inspection of the security proof [[CGRS23](https://eprint.iacr.org/2023/899)] shows that this modification does not affect security. + +Our variant of the SimplPedPop protocol then works as follows: + +1. Every participant `i` (where `i` is an integer `0 <= i < n`) creates a `t`-of-`n` sharing of a random secret scalar using Feldman Verifiable Secret Sharing (VSS), a variant of Shamir Secret Sharing. + This involves generating random coefficients `a_i[0], ..., a_i[t-1]` of a polynomial `f_i` of degree `t-1` in the scalar group: + + ``` + f_i(Z) = a_i[0] + a_i[1] * Z + ... + a_i[t-1] * Z^(t-1) + ``` + + Here, `f_i(0) = a_i[0]` acts as the secret scalar to be shared. + Participant `i` computes a VSS share `shares[j] = f_i(j+1)` for every participant `j` (including `j = i`), + which is supposed to be sent to participant `j` in private. + (This will be realized in EncPedPop using encryption.) + + Participant `i` then sends a VSS commitment, + which is a vector `com = (com[0], ..., com[t-1]) = (a_i[0] * G, ..., a_i[t-1] * G)` of group elements, + where `G` is the base point of the secp256k1 elliptic curve, + and a BIP 340 Schnorr signature `pop` on message "`i`" with secret key `a_i[0]` to the coordinator. + (The Schnorr signature acts as a *proof of possession*, + i.e., it proves knowledge of the discrete logarithm of `com[0] = a_i[0] * G`. + This avoids rogue-key attacks, also known as key cancellation attacks.) + +2. Upon receiving `coms[j] = (coms[j][0], ..., coms[j][t-1])` and `pops[j]` from every participant `j`, + the coordinator aggregates the commitments + by computing the component-wise sum of all `coms[j]` vectors except for their first components `coms[j][0]`, + which are simply concatenated (because the participants will need them to verify the proofs of possession): + + ``` + sum_coms_to_nonconst_terms = (coms[0][1] + ... + coms[n-1][1], ..., coms[0][t-1] + ... + coms[n-1][t-1]) + coms_to_secrets = (coms[0][0], ..., coms[n-1][0]) + ``` + + The coordinator sends the vectors `coms_to_secrets`, `sum_coms_to_nonconst_terms`, and `pops` to every participant. + +3. Upon receiving `coms_to_secrets`, `sum_coms_to_nonconst_terms`, and `pops` from the coordinator, + every participant `i` verifies every signature `pops[j]` using message `j` and public key `coms_to_secrets[j]`. + If any signature, say the one from participant `j`, is invalid, participant `i` aborts and blames participant `j` for the failure of the session. + + Otherwise, i.e., if all signatures are valid, participant `i` sums the components of `coms_to_secrets`, + and prepends the sum to the `sum_coms_to_nonconst_terms` vector, resulting in a vector `sum_coms`. + (Assuming the coordinator is honest, + the vector `sum_coms` is now the complete component-wise sum of the `coms[j]` vectors from every participant `j`. + It acts as a VSS commitment to the sum `f = f_0 + ... + f_{n-1}` of the polynomials of all participants.) + + Participant `i` computes its public share `pubshare` as: + ``` + pubshare = (i+1)^0 * sum_coms[0] + ... + (i+1)^(t-1) * sum_coms[t-1] + ``` + + Let `partial_secshares` be the vector of the VSS shares that participant `i` has privately obtained from each participant, + and let `secshare = partial_secshares[0] + ... + partial_secshares[n-1]` be the sum of the vector components. + Participant `i` checks the validity of `secshare` against `sum_coms` + by checking if the equation `secshare * G = pubshare` holds. + (`secshare` is supposed to be equal to `f(i+1)`.) + + If the check fails, participant `i` aborts. + Assuming the coordinator is honest and has sent the correct data to derive a `sum_coms` vector, + participant `i` knows that some participant contributed a wrong summand to `secshare`, + but participant `i` does not have sufficient information to single out and blame the faulty participant. + In this case, participant `i` can optionally investigate the error by asking the coordinator for the vector `partial_pubshares` defined as: + ``` + partial_pubshares[j] = (i+1)^0 * coms[j][0] + ... + (i+1)^(t-1) * coms[j][t-1] + ``` + With this vector at hand, participant `i` verifies each component of `partial_secshares` individually + by checking for which participant `j` the equation `partial_secshares[j] * G = partial_pubshares[j]` does not hold. + Participant `i` blames this participant `j`. + + Otherwise, i.e., in the successful case that the equation `secshare * G = pubshare` holds, participant `i` proceeds as follows. + In order to obtain a threshold public key with an unspendable Taproot script path [[BIP 341](bip-0341.mediawiki)], + participant `i` computes a Taproot tweak `tweak` for an unspendable script path, + and adds the point `tweak * G` to `sum_coms[0]`, resulting in a new VSS commitment called `sum_coms_tweaked`. + Participant `i` computes the public share of every participant as + ``` + pubshares[j] = (j+1)^0 * sum_coms_tweaked[0] + ... + (j+1)^(t-1) * sum_coms_tweaked[t-1] + ``` + Correspondingly, participant `i` computes `secshare_tweaked = secshare + tweak`. + + Then, participant `i` sets the DKG output consisting of + this participant's secret share `secshare_tweaked`, + the threshold public key `thresh_pk = sum_coms_tweaked[0]`, and + all participants' public shares `pubshares`. + + As a final step, participant `i` enters a session of an external equality check protocol + to verify that all participants agree on the *transcript*, i.e., common data produced during the session, + and that none of them has aborted the session due to an invalid VSS share or an invalid proof of possession. + The transcript of SimplPedPop, constructed in a variable `eq_input`, + is simply the concatenation (of serializations) of `t` and the `sum_coms` vector. + Upon the equality protocol returning successfully, + participant `i` returns successfully with the DKG outputs as computed above. + Details of the interface of the equality check protocol will be described further below in + [Subsection "Background on Equality Checks"](#background-on-equality-checks). + +### DKG Protocol EncPedPop + +(See [`python/chilldkg_ref/encpedpop.py`](./bip-chilldkg/python/chilldkg_ref/encpedpop.py).) + +EncPedPop is a thin wrapper around SimplPedPop that takes care of encrypting the VSS shares +so that they can be sent over an insecure communication channel. + +As in SimplPedPop, every EncPedPop participant holds a long-term secret seed. +Every participant derives from this seed a static, long-term ECDH key pair consisting of a secret decryption key and a public encryption key. +It is assumed that every participant has an authentic copy of every other participant's encryption key. + +The encryption relies on ephemeral-static ECDH key exchange. +Every participant derives, from their long-term seed, fresh randomness, and the session context (the threshold `t` and the static encryption keys of all participants), an ephemeral *session seed*, which is passed down to SimplPedPop. +From this session seed, the participant further derives an ephemeral encryption nonce pair consisting of a secret nonce and the corresponding public nonce. +Deriving the nonce pair from the session seed, rather than directly and independently from fresh randomness, ensures that different sets of participants will have different SimplPedPop sessions with distinct nonces, even in the case that the randomness is accidentally reused. +This will enable every pair of sending participant `i` and recipient participant `j != i` +to perform an ECDH key exchange between the ephemeral encryption nonce pair of participant `i` and the static encryption key pair of participant `j` +in order to establish a shared secret pad `pad_ij` only known to participants `i` and `j`. +The derivation of `pad_ij` from the raw ECDH output uses a tagged hash and includes +additional context, namely the identifier `j` of the recipient, the threshold `t`, and the static encryption keys of all `n` participants.[^mr-kem] + +[^mr-kem]: This implements a multi-recipient multi-key key encapsulation mechanism (MR-MK-KEM) secure under the static Diffie-Hellman assumption [[Theorem 2, PPS14](https://doi.org/10.1145/2590296.2590329)]. + +When `j = i` (i.e., when a participant encrypts a VSS share for themselves), the computationally expensive ECDH key exchange is unnecessary. +Instead, the participant repurposes the secret decryption key as a symmetric key, such that `pad_ii` is computed as the tagged hash of the decryption key, public encryption nonce, and context. + +EncPedPop then works like SimplPedPop with the following differences: +Participant `i` will additionally transmit their public encryption nonce and, for every participant `j` (including themselves, i.e., `j = i`), an encrypted VSS share `shares[j] + pad_ij` +as part of the first message to the coordinator. +The coordinator collects all encrypted VSS shares, +and computes the sum `enc_secshare[i]` of all shares intended for every participant `i`. +The coordinator sends all public encryption nonces along with the sum `enc_secshare[i]` to participant `i`. +Participant `i` stores the sum as `enc_secshare`, +derives the pads `pad_0i`, ..., `pad_(n-1)i` as described above, +obtains the value `secshare = enc_secshare - (pad_0i + ... + pad_(n-1)i)`, +and passes it down to SimplPedPop. + +If SimplPedPop raises an error because this `secshare` value fails VSS verification, +then participant `i` can optionally investigate the error +by asking the coordinator for the vector `enc_partial_secshares` of the individual contributions of all participants to `enc_secshare`. +Participant `i` obtains the vector `partial_secshares`, which SimplPedPop requires for investigating the error, +by decrypting the components of `enc_partial_secshares` via `partial_secshares[j] = enc_partial_secshares[j] - pad_ji` for every participant `j`. +Then, participant `i` can pass `partial_secshares` down to SimplPedPop, +which, after additionally obtaining the vector `partial_pubshares` from the coordinator, +has all the information required to determine and blame a faulty participant. + +Otherwise, i.e., if SimplPedPop does not raise an error, +EncPedPop appends to the transcript `eq_input` of SimplPedPop all the `n` static encryption keys and also the `n` public encryption nonces. +The inclusion of the static encryption keys ensures that participants agree on their identities +and excludes man-in-the-middle attacks if Eq authenticates participants, +e.g., if the Eq protocol messages are signed under long-term public keys of the participants. + +### Background on Equality Checks + +As explained in the "Motivation" section, it is crucial for security that participants reach agreement over the results of a DKG session. +SimplPedPop, and consequently also EncPedPop, ensure agreement during the final step of the DKG session by running an external *equality check protocol* Eq. +The purpose of Eq is to verify that all participants have received an identical *transcript*, which is a byte string constructed by the respective DKG protocol. + +Eq is assumed to be an interactive protocol between the `n` participants with the following abstract interface: +Every participant can invoke a session of Eq with an input value `eq_input`. +Eq may not return at all to the calling participant, +but if it returns successfully to some participant, then all honest participants agree on the value `eq_input`. +(However, it may be the case that not all honest participants have established this fact yet.) +This means that the DKG session was successful, and the resulting threshold public key can be returned to the participant, +who can use it, e.g., by sending funds to some Bitcoin address derived from it. + +More formally, Eq must fulfill the following properties [[CGRS23](https://eprint.iacr.org/2023/899)]: + - **Integrity:** If Eq returns successfully to some honest participant, then for every pair of input values `eq_input` and `eq_input'` provided by two honest participants, we have `eq_input = eq_input'`. + - **Conditional Agreement:** Assuming all messages are delivered eventually, if Eq returns successfully to some honest participant, then Eq will eventually return successfully to all honest participants. + +Depending on the application scenario, different approaches may be suitable to implement Eq, +such as a consensus protocol already available as part of a federated system +or out-of-band communication. +For example, in a scenario where a single user employs multiple signing devices to set up a threshold wallet, +every device could display its value `eq_input` (or a hash of `eq_input` under a collision-resistant hash function) to the user. +The user could manually verify the equality of the values by comparing the values shown on all displays, +and confirm their equality by providing explicit confirmation to every device, e.g., by pressing a button on every device. +Similarly, if signing devices are controlled by different organizations in different geographic locations, +agents of these organizations could meet and compare the values. +A detailed treatment of these out-of-band methods is out of scope of this document. + +### DKG Protocol ChillDKG + +(See [`python/chilldkg_ref/chilldkg.py`](./bip-chilldkg/python/chilldkg_ref/chilldkg.py).) + +Instead of performing an out-of-band check as the last step of the DKG, +ChillDKG relies on a more direct approach: +It is a wrapper around EncPedPop, +which instantiates the required equality check protocol with a concrete in-band protocol CertEq. +CertEq assumes that each participant holds a long-term key pair of a signature scheme, called the *host key pair*. +ChillDKG repurposes the host key pairs as the ECDH key pairs required by EncPedPop,[^joint-security] +and it repurposes the host secret key as the seed required by EncPedPop. + +[^joint-security]: Schnorr signatures and ECDH-based KEMs are known to be jointly secure [Theorem 2, [DLPSS11](https://eprint.iacr.org/2011/615)] +under the combination of the gap-DH and gap-DL assumptions, and this result can be adapted to the MR-KEM used in EncPedPop. + +#### Equality Check Protocol CertEq + +The CertEq protocol is straightforward:[^certeq-literature] +Every participant sends a signature on their input value `eq_input` to every other participant (via the untrusted coordinator), +and expects to receive valid signatures on `eq_input` from the other participants. +A participant terminates successfully as soon as the participant has collected what we call a *success certificate*, +i.e., a full list of valid signatures from all `n` participants (including themselves).[^multisig-cert] + +[^multisig-cert]: Abstractly, the required primitive is a multi-signature scheme, i.e., `n` participants signing the same message `eq_input`. +We have chosen the naive scheme of collecting a list of `n` individual signatures for simplicity. +Other multi-signatures schemes, +e.g., MuSig2 [[BIP 327](bip-0327.mediawiki)] or a scheme based on Schnorr signature half aggregation [[Halfagg-BIP-Draft](https://github.com/BlockstreamResearch/cross-input-aggregation/blob/master/half-aggregation.mediawiki), [CGKN21](https://eprint.iacr.org/2021/350), [CZ22](https://eprint.iacr.org/2022/222)], +could be used instead to reduce the size of the success certificate. +These methods are out of scope of this document. + +[^certeq-literature]: CertEq can be viewed as a signed variant of the Goldwasser-Lindell echo broadcast protocol [[GL05](https://eprint.iacr.org/2002/040), Protocol 1], or alternatively, as a unanimous variant of Signed Echo Broadcast [[Rei94](https://doi.org/10.1145/191177.191194), Section 4], [[CGR11](https://doi.org/10.1007/978-3-642-15260-3), Algorithm 3.17]. + +This termination rule immediately implies the integrity property: +Unless a signature has been forged, if some honest participant with input `eq_input` terminates successfully, +then by construction, all other honest participants have sent a signature on `eq_input` and thus received `eq_input` as input. + +The key insight to ensuring conditional agreement is that any participant terminating successfully +obtains a *success certificate* `cert` consisting of the collected list of all `n` signatures on `eq_input`. +This certificate will, by the above termination rule, convince every other honest participant (who, by integrity, has received `eq_input` as input) to terminate successfully. +Crucially, this other honest participant will be convinced even after having received invalid or no signatures during the actual run of CertEq, +due to unreliable communication links, a faulty coordinator, or faulty participants signing more than one value. + +Thus, the certificate does not need to be sent during a normal run of CertEq, +but can instead be presented to other participants later, +e.g., during a request to participate in a FROST signing session. + +#### Facilitating Backup and Recovery + +ChillDKG constructs a transcript `eq_input` by appending to the transcript of EncPedPop the vector `enc_secshare`. +Since every participant needs to include the *entire* vector `enc_secshare` in their own `eq_input`, +unlike plain EncPedPop (where the coordinator sends each participant only their own entry `enc_secshare[i]`), +the ChillDKG coordinator broadcasts the complete vector to every participant. +This ensures that all participants agree on all encrypted shares, +and as a consequence, +the entire DKG output of a successful ChillDKG participant can be deterministically reproduced from a per-participant *host secret key* and the transcript. + +This property is leveraged to offer a backup and recovery functionality: +ChillDKG outputs a string called *recovery data* which is the concatenation of the transcript `eq_input` and the success certificate `cert`. +The recovery data, which is the same for every participant, can be used by any participant together with the host secret key to recover the full output of the DKG session. + +Crucially, the recovery data carries proof that the DKG session took place: +any recovering participant can extract their own valid signature on the transcript from the success certificate. +This valid signature proves that the participant, or more precisely, their former instance, +had successfully reached the state at which this signature is sent to the coordinator. +In particular, this implies that the proofs of possession from all participants, +which are omitted in recovery data for succinctness, +had been checked successfully. + +In fact, the recovery procedure subsumes the handling of a valid success certificate +which is presented to the participant only after the session +(in case an invalid or no certificate was received during the session). +As a result, ChillDKG does not provide a dedicated method for providing a success certificate after the session, +and callers can simply use the recovery functionality instead. + +## Usage of ChillDKG + +The purpose of this section is to provide a high-level overview of the interface and usage of ChillDKG, +aimed at developers who would like to use a ChillDKG implementation in their applications and systems. + +Detailed API documentation of the reference implementation is provided in [Subsection "API Documentation"](#api-documentation). +Developers who would like to implement ChillDKG or understand ChillDKG's internals and reference implementation +**should** also read [Section "Internals of ChillDKG"](#internals-of-chilldkg). + +### Use ChillDKG only for FROST + +ChillDKG is designed for usage with the FROST signing protocol as specified in [BIP 445](bip-0445.md), +and its security depends on the specifics of FROST. +We stress that ChillDKG is not a general-purpose DKG protocol,[^no-simulatable-dkg] +and **must not** be combined with other threshold cryptographic schemes, +e.g., FROST specifications other than [BIP 445](bip-0445.md), threshold signature schemes other than FROST, or threshold decryption schemes, +without careful further consideration, which is not in the scope of this document. + +[^no-simulatable-dkg]: As a variant of Pedersen DKG, ChillDKG does not provide simulation-based security [[GJKR07](https://doi.org/10.1007/s00145-006-0347-3)]. Roughly speaking, if ChillDKG is combined with some threshold cryptographic scheme, the security of the combination is not automatically implied by the security of the two components. Instead, the security of every combination must be analyzed separately. The security of the specific combination of SimplPedPop (as the core building block of ChillDKG) and FROST has been analyzed [[CGRS23](https://eprint.iacr.org/2023/899)]. + +### DKG Parties and Inputs + +A DKG session comprises `n >= 1` *participants*, +some number `t <= n` of which will be required to produce a signature. +Additionally, each session requires a helper party called the *coordinator*. +If there is no dedicated coordinator, one of the participants can act as the coordinator. + +Each participant holds a long-term *host key pair* consisting of a *host public key* and a *host secret key*. +The `n` participants in a DKG session are represented by an ordered list of their host public keys. +The list must not contain duplicate entries, +and thus the list assigns each participant a unique identifier in the range of `0` to `n - 1` according to the position of their host public key in the list. + +The list of host public keys and the signing threshold `t` together +form the public *session parameters*, the common input to all participants and the coordinator. + +Each participant **must** ensure to have authentic copies of all other participants' host public keys before the start of the session.[^trust-anchor] +A participant can verify authenticity by comparing every other host public key with the corresponding participant out-of-band, +or more conveniently, by comparing a short *parameters hash* (a hash of the session parameters) with every other participant out-of-band. + +[^trust-anchor]: No protocol can prevent man-in-the-middle attacks without this or a comparable assumption. +Note that this requirement is implicit in other schemes as well. +For example, setting up a multi-signature wallet via non-interactive key aggregation in MuSig2 [[BIP 327](bip-0327.mediawiki)] +also requires the assumption that all participants have authentic copies of each other's individual public keys. + +Each party (i.e., participant or coordinator) is assumed to be either *honest* (i.e., reliable and adhering to the protocol) +or *faulty* (i.e., controlled by an attacker or defective). + +### Network Setup + +Each participant has a point-to-point communication link to the coordinator +(but participants do not need direct communication links to each other). + +Transmission errors may hamper functionality (i.e., a DKG session may fail), +but even an attacker in full control of communication links will not be able to hamper security. + +### DKG Outputs + +If a ChillDKG session returns an output to a participant or the coordinator, +then we say that this party *deems the protocol session successful*. +In that case, the DKG output is a triple consisting of a *secret share* for participating in FROST signing sessions (individual to each participant, not returned to the coordinator), the *threshold public key* representing the `t`-of-`n` policy of the group (common to all participants and the coordinator), and a list of `n` *public shares* for verification of individual contributions to a FROST signing session (common to all participants and the coordinator).[^id-position-mapping] +See [BIP 445](bip-0445.md) for details on signing. + +[^id-position-mapping]: The secret sharing is as expected by [BIP 445](bip-0445.md), i.e., there exists a scalar polynomial `f` of degree `t - 1` such that `f(0)` is the discrete logarithm of the threshold public key and every participant with identifier `i` has secret share `f(i + 1)`. + +Moreover, all parties obtain *recovery data* (common to all participants and the coordinator), whose purpose is detailed in the next subsection. + +### Backup and Recovery + +Losing the secret share or the threshold public key, e.g., after the loss of a participant device, will render the participant incapable of participating in signing sessions. +As these values depend on the contributions of the other participants to the DKG session, they can, +unlike deterministically derived secret keys [[BIP 32](bip-0032.mediawiki)] as typically used for single-signer Schnorr signatures [[BIP 340](bip-0340.mediawiki)] or MuSig [[BIP 327](bip-0327.mediawiki)], +not be rederived solely from the participant's seed. + +To facilitate backups of a DKG session, +ChillDKG offers the possibility to recover a participant's DKG output from the participant's host secret key and the recovery data of the specific session. +As a result, a full backup of a participant consists of the host secret key as well as the recovery data of all DKG sessions the participant has successfully participated in. + +Since the recovery data is the same for all participants, +if a participant loses the backup of the recovery data of the DKG session, +they can request it from any other participant or the coordinator. +Moreover, the recovery data contains secrets only in encrypted form and is self-authenticating +so that it can, in principle, be stored with an untrusted third-party backup provider. + +Users **should** be aware that the session parameters (the threshold and the host public keys) and public parts of the DKG output (the threshold public key and the public shares) can be inferred from the recovery data, which may constitute a privacy issue. +To eliminate this issue, a participant can encrypt the recovery data using an encryption key derived from their host secret key before giving it out to untrusted parties. +Recovery from encrypted recovery data still requires only the participant's host secret key, with no additional secrets needed. +This BIP does not specify the method of encryption. + +Keeping backups of the secret key accessible and secure is hard (typically similarly hard as keeping the participant devices themselves). +As a consequence, it may not be an unreasonable strategy in a threshold setup not to perform backups of host secret keys at all, +and simply hope that `t` honest and working participants will remain available. +As soon as one or more participants are lost or broken, a new DKG session can be performed with the lost participants replaced. +The obvious drawback of this method is that it will result in a change of the threshold public key, +and the application will, therefore, need to transition to the new threshold public key, +e.g., funds stored under the current threshold public key need to be transferred to the new key. + +Whether to perform backups of host secret keys and how to manage them ultimately depends on the requirements of the application, +and we believe that a general recommendation is not useful. + +### Recovering Stuck Parties + +The mere fact that a protocol party deems a ChillDKG session successful does not imply that other parties deem it successful yet. +Indeed, due to failing communication links or invalid messages sent by faulty parties, +it is possible that one party has deemed the DKG session successful, but others have not (yet) and thus are stuck in the DKG session. +In that case, the successful parties can eventually convince the stuck parties to consider the DKG session successful by presenting the recovery data to them. +The recovery data can, e.g., be attached to the first request to initiate a FROST signing session. + +An important implication of the above is that anyone who uses the threshold public key, +and thereby relies on the participants' ability to participate in signing sessions, +**must** ensure that the participants have already deemed the DKG session successful, +or at least, that the recovery data will be available to convince any stuck participants of the success of the DKG session. + +For an example of what could go wrong, +assume that some participant deems the DKG session successful and uses the threshold public key by sending funds to some Bitcoin address derived from it. +Even though everything looks fine from the perspective of this participant, +it is entirely possible that this participant is the only one who has deemed the DKG session successful, +and thus (besides the untrusted coordinator) the only one who knows the recovery data. +If the recovery data is lost now because this participant's permanent storage fails, +the other participants cannot be convinced to deem the DKG session successful +(without the help of the untrusted coordinator) +and so the funds will be lost. + +Thus, anyone who intends to use the threshold public key +**should** first obtain explicit confirmations from all participants that they have deemed the DKG session successful, +which will also imply that all participants have a redundant copy of the recovery data. +One simple method of obtaining confirmation is to collect signed confirmation messages from all participants. + +Depending on the application, other methods may be appropriate. +For example, in a scenario where a single user employs multiple signing devices in the same room to set up a threshold wallet, +the user could check that all `n` devices signal confirmation via their display. +Alternatively, the user could check all `n` devices when generating a receiving address for the first time, +which constitutes the first use of the threshold public key. + +If a recovering party (see [Backup and Recovery](#backup-and-recovery)) cannot (re-)obtain confirmations, +this simply means they **should** stop using the threshold public key going forward, +e.g., stop sending additional funds to addresses derived from it. +(But, in contrast to the bad example laid out above, +it will still be possible to spend the funds, +and even recovered participants can participate in signing sessions.) + +To facilitate this confirmation process, +ChillDKG provides optional functionality for creating and verifying acknowledgment signatures on the recovery data. + +### Blaming Faulty Parties + +Any faulty party can make a ChillDKG session abort by sending a message that deviates from the protocol specification. +In order to resolve such situations, ChillDKG offers a *blame functionality*. +Under the assumption that communication links are reliable, +the blame functionality enables honest protocol parties to identify and blame at least one participant suspected to be faulty: + - If an honest participant aborts the session, then this participant will blame at least one participant or the coordinator. + - If an honest coordinator aborts the session, then the coordinator will blame at least one participant. + +Moreover, a party which aborts due to a timeout while waiting for a protocol message +(instead of aborting after having received an invalid protocol message) +will trivially blame the party who is supposed to send the outstanding message. + +The guarantees provided by the blame functionality are limited, +and its primary purpose is to support manual investigation and debugging efforts. +Different parties, even if honest, are not guaranteed to blame the same party, +and there is, in general, no way to verify an accusation by some party that another party is to blame. + +Nevertheless, under the condition that all messages in the ChillDKG session have been transmitted correctly over the communication links, +and, in case of a participant blaming another participant, under the additional condition that the coordinator is honest, +the aborting party will be guaranteed that the blamed party is indeed faulty. + +It is important to understand that this guarantee is conditional. +For example, assume that the condition of an honest coordinator is violated. +In that case, even if all participants are honest, the faulty coordinator can deviate from the protocol in a way that makes one participant blame another participant, when, in fact, it is the coordinator who is faulty and not the blamed participant. + +In some cases,[^incorrect-shares] an aborting participant needs to obtain an auxiliary *investigation message* from the coordinator +in order to single out and blame another participant (see [Overview of a ChillDKG session](#overview-of-a-chilldkg-session)). + +[^incorrect-shares]: Namely, when having received incorrect secret shares. + +### Threat Model and Security Goals + +We expect ChillDKG to provide the following informal security goals when it is used to set up keys for the FROST threshold signature scheme. +If a participant deems a protocol session successful (as defined in [DKG Outputs](#dkg-outputs)), then this participant is assured that: + - A coalition of at most `t - 1` faulty participants and a faulty coordinator cannot forge a signature under the returned threshold public key on any message `m` for which no signing session with at least one honest participant was initiated. (Unforgeability)[^unforgeability-formal] + - All honest participants who deem the protocol session successful will have correct and consistent protocol outputs. + In particular, they agree on the threshold public key, the list of public shares, and the recovery data. + Moreover, any `t` of them have secret shares consistent with the threshold public key.[^correctness-formal] + This means that any `t` participants have all the necessary inputs to run FROST signing sessions which produce signatures valid under the threshold public key. + - The success certificate will, when presented to any other (honest) participant, convince that other participant to deem the protocol successful. + +[^unforgeability-formal]: See Chu, Gerhart, Ruffing, and Schröder [Definition 3, [CGRS23](https://eprint.iacr.org/2023/899)] for a formal definition. + +[^correctness-formal]: See Ruffing, Ronge, Jin, Schneider-Bensch, and Schröder [Definition 2.5, [RRJSS22](https://eprint.iacr.org/2022/550)] for a formal definition. + +### Overview of a ChillDKG Session + +(See also [`python/example.py`](./bip-chilldkg/python/example.py).) + +The following figure shows an example ChillDKG involving the participants and the coordinator. +For simplicity, only one participant is depicted. +Arrows indicate network messages between the parties. +Each message sent by the coordinator is a broadcast message, +i.e., the coordinator sends the same message to each participant.[^no-reliable-broadcast] +Unless participants abort due to errors, all participants run the same code and send messages in the same steps. + +[^no-reliable-broadcast]: Recall that we do not assume a *reliable* broadcast channel but instead that the coordinator has separate point-to-point communication links to each participant. In other words, the protocol prescribes that an honest coordinator sends the same message to every participant, but the security of the protocol does not depend on the coordinator adhering to that prescription. + +![The figure shows the message flow between a participant and a coordinator. +The first of two phases named "Generation of host public keys" involves the participant invoking the hostpubkey_gen function with parameter hostseckey and sending the returned hostpubkey to the coordinator. +The second phase named "Session" is initiated by the coordinator sending hostpubkeys and the threshold t to the participant. +The participant invokes participant_step1 and sends the returned pmsg1 to the coordinator. +The coordinator invokes coordinator_step1 and sends the returned cmsg1 to the participant. +The participant invokes participant_step2 and sends the returned pmsg2 to the coordinator. +The coordinator invokes coordinator_finalize and sends the returned cmsg2 to the participant. +The participant invokes participant_finalize, which ends the second phase. +](./bip-chilldkg/images/chilldkg-sequence.png "ChillDKG") + +A participant can run multiple sessions with the same hostseckey, provided that the session state as output from any of the "step" functions is not reused. +Multiple sessions may be run concurrently. + +Whenever an invoked function fails and raises an error, the corresponding party will abort the session and, +in most cases, blame a participant or the coordinator for the failure of the session. +However, if a participant aborts during the `participant_step2` function, +there may be insufficient information to determine another participant to blame. +In this case, an optional *investigation procedure* is available: +The aborting participant can ask the coordinator for an auxiliary *investigation message* (generated via the `coordinator_investigate` function), +which will allow the participant to blame a specific other participant (via the `participant_investigate` function). + +Applications may choose to let the coordinator always create and send investigation messages, +(i.e., even if not asked for by an aborting participant). +While different aborting participants will need different investigation messages, +an investigation message intended for some participant does not need to be kept confidential from other participants. +Thus, applications may additionally choose to let the coordinator send all `n` investigation messages to all `n` participants. + +### API Documentation + +This subsection is an export of the API documentation generated from the docstrings in the reference implementation +(see [`python/chilldkg_ref/chilldkg.py`](./bip-chilldkg/python/chilldkg_ref/chilldkg.py).) + +In addition to the exceptions documented for each function below, all public API functions may raise Python built-in exceptions such as `TypeError` or `ValueError` when called with arguments of unexpected structure (e.g., wrong type or wrong length). These structural errors are not documented per function. + + +#### hostpubkey\_gen + +```python +def hostpubkey_gen(hostseckey: bytes) -> bytes +``` + +Compute the participant's host public key from the host secret key. + +The host public key is the long-term cryptographic identity of the +participant. + +This function interprets `hostseckey` as big-endian integer, and computes +the corresponding "plain" public key in compressed serialization (33 bytes, +starting with 0x02 or 0x03). This is the key generation procedure +traditionally used in Bitcoin, e.g., for ECDSA. In other words, this +function is equivalent to `IndividualPubkey` as defined in +[[BIP 327](bip-0327.mediawiki#key-generation-of-an-individual-signer)]. + +*Arguments*: + +- `hostseckey` - This participant's long-term secret key (32 bytes). + The key **must** be 32 bytes of cryptographically secure randomness + with sufficient entropy to be unpredictable. All outputs of a + successful participant in a session can be recovered from (a backup + of) the key and per-session recovery data. + + The same host secret key (and thus the same host public key) can be + used in multiple DKG sessions. A host public key can be correlated + to the threshold public key resulting from a DKG session only by + parties who observed the session, namely the participants, the + coordinator (and any eavesdropper). + + +*Returns*: + + The host public key (33 bytes). + + +*Raises*: + +- `HostSeckeyError` - If the host secret key is invalid. + +#### HostSeckeyError Exception + +```python +class HostSeckeyError(ValueError) +``` + +Raised if the host secret key is invalid. + +#### SessionParams Tuples + +```python +class SessionParams(NamedTuple): + hostpubkeys: list[bytes] + t: int +``` + +A `SessionParams` tuple holds the common parameters of a DKG session. + +*Attributes*: + +- `hostpubkeys` - Ordered list of the host public keys of all participants. +- `t` - The participation threshold `t`. + This is the number of participants that will be required to sign. + It must hold that `1 <= t <= len(hostpubkeys) <= 2**32 - 1`. + + Each participant **must** ensure to have authentic copies of all other + participants' host public keys before the start of the session, e.g., by + confirming authenticity of each host public key with the expected key + holder out of band. This is analogous to traditional threshold signatures + (known as "multisig" in the Bitcoin community), + [[BIP 383](bip-0383.mediawiki)], where a signer needs the other signers' + authentic extended public keys ("xpubs") to generate multisig addresses, + or MuSig2 [[BIP 327](bip-0327.mediawiki)], where a signer needs the other + participants' authentic individual public keys to generate an aggregated + public key. + + A DKG session will fail if the participants and the coordinator in a session + don't have the `hostpubkeys` in the same order. This will make sure that + honest participants agree on the order as part of the session, which is + useful if the order carries an implicit meaning in the application (e.g., if + the first `t` participants are the primary participants for signing and the + others are fallback participants). If there is no canonical order of the + participants in the application, the caller can sort the list of host public + keys with the [KeySort algorithm specified in + BIP 327](bip-0327.mediawiki#key-sorting) to abstract away from the order. + +#### params\_hash + +```python +def params_hash(params: SessionParams) -> bytes +``` + +Return a hash of the session parameters for out-of-band comparison. + +In the common scenario that the participants obtain host public keys from +the other participants over channels that do not provide end-to-end +authentication of the sending participant (e.g., if the participants simply +send their unauthenticated host public keys to the coordinator, who is +supposed to relay them to all participants), the parameters hash serves as a +convenient way to perform an out-of-band comparison of all host public keys. +It is a collision-resistant cryptographic hash of the `SessionParams` tuple. +As a result, if all participants have obtained an identical parameters hash +(as can be verified out-of-band), then they all agree on all host public +keys and the threshold `t`, and in particular, all participants have +obtained authentic public host keys. + +*Returns*: + +- `bytes` - The parameters hash, a 32-byte string. + + +*Raises*: + +- `InvalidHostPubkeyError` - If `hostpubkeys` contains an invalid public key. +- `DuplicateHostPubkeyError` - If `hostpubkeys` contains duplicates. +- `ThresholdOrCountError` - If `1 <= t <= len(hostpubkeys) <= 2**32 - 1` does + not hold. + +#### SessionParamsError Exception + +```python +class SessionParamsError(ValueError) +``` + +Base exception for invalid `SessionParams` tuples. + +#### DuplicateHostPubkeyError Exception + +```python +class DuplicateHostPubkeyError(SessionParamsError) +``` + +Raised if two participants have identical host public keys. + +This exception is raised when two participants have an identical host public +key in the `SessionParams` tuple. Assuming the host public keys in question +have been transmitted correctly, this exception implies that at least one of +the two participants is faulty (because duplicates occur only with +negligible probability if keys are generated honestly). + +*Attributes*: + +- `participant_id1` _int_ - Identifier of the first participant. +- `participant_id2` _int_ - Identifier of the second participant. + +#### InvalidHostPubkeyError Exception + +```python +class InvalidHostPubkeyError(SessionParamsError) +``` + +Raised if a host public key is invalid. + +This exception is raised when a host public key in the `SessionParams` tuple +is not a valid public key in compressed serialization. Assuming the host +public keys in question has been transmitted correctly, this exception +implies that the corresponding participant is faulty. + +*Attributes*: + +- `participant_id` _int_ - Identifier of the participant. + +#### ThresholdOrCountError Exception + +```python +class ThresholdOrCountError(SessionParamsError) +``` + +Raised if `1 <= t <= len(hostpubkeys) <= 2**32 - 1` does not hold. + +#### DKGOutput Tuples + +```python +class DKGOutput(NamedTuple): + secshare: bytes | None + thresh_pk: bytes + pubshares: list[bytes] +``` + +Holds the outputs of a DKG session. + +*Attributes*: + +- `secshare` - Secret share of the participant (32 bytes, or `None` for + coordinator). +- `thresh_pk` - Generated threshold public key representing the group + (33 bytes, in compressed serialization). +- `pubshares` - Public shares of the participants (33 bytes each, in + compressed serialization). + +#### participant\_step1 + +```python +def participant_step1(hostseckey: bytes, params: SessionParams, random: bytes) -> tuple[ParticipantState1, bytes] +``` + +Perform a participant's first step of a ChillDKG session. + +*Arguments*: + +- `hostseckey` - Participant's long-term host secret key (32 bytes). +- `params` - Common session parameters. +- `random` - FRESH random byte string (32 bytes). + + +*Returns*: + +- `ParticipantState1` - The participant's session state after this step, to + be passed as an argument to `participant_step2`. The state **must + not** be reused (i.e., it must be passed only to one + `participant_step2` call). +- `bytes` - The first message to be sent to the coordinator + (`33*t + 32*n + 97` bytes). + + +*Raises*: + +- `HostSeckeyError` - If the host secret key is invalid, or if the key does + not match any entry of `hostpubkeys`. +- `InvalidHostPubkeyError` - If `hostpubkeys` contains an invalid public key. +- `DuplicateHostPubkeyError` - If `hostpubkeys` contains duplicates. +- `ThresholdOrCountError` - If `1 <= t <= len(hostpubkeys) <= 2**32 - 1` does + not hold. +- `RandomnessError` - If `random` is all zeroes (i.e., `b"\x00" * 32`). This + check guards against the case of a malfunctioning random number + generator. + +#### RandomnessError Exception + +```python +class RandomnessError(ValueError) +``` + +Raised if the randomness is all zeroes (i.e., `b"\x00" * 32`). + +#### participant\_step2 + +```python +def participant_step2(hostseckey: bytes, state1: ParticipantState1, cmsg1: bytes, aux_rand: bytes) -> tuple[ParticipantState2, bytes] +``` + +Perform a participant's second step of a ChillDKG session. + +*Warning:* +After sending the returned message to the coordinator, the caller **must +not** erase the hostseckey, even if the coordinator reply needed for the +`participant_finalize` call is not received. The underlying reason is that +some other participant may receive the coordinator reply, deem the DKG +session successful and use the resulting threshold public key (e.g., by +sending funds to it). If the coordinator reply remains missing, that other +participant can, at any point in the future, convince this participant of +the success of the DKG session by presenting recovery data, from which this +participant can recover the DKG output using the `participant_recover` +function. + +*Arguments*: + +- `hostseckey` - Participant's long-term host secret key (32 bytes). +- `state1` - The participant's session state as output by + `participant_step1`. +- `cmsg1` - The first message received from the coordinator + (`162*n + 33*(t-1)` bytes). +- `aux_rand` - Auxiliary randomness (32 bytes). FRESH 32-byte randomness + is optimal, but 16 random bytes or a counter padded to 32 bytes + is acceptable (see BIP 340). + + +*Returns*: + +- `ParticipantState2` - The participant's session state after this step, to + be passed as an argument to `participant_finalize`. The state **must + not** be reused (i.e., it must be passed only to one + `participant_finalize` call). +- `bytes` - The second message to be sent to the coordinator (64 bytes). + + +*Raises*: + +- `HostSeckeyError` - If the host secret key is invalid or if it does not + match the one used in `participant_step1`. +- `FaultyCoordinatorError` - If the coordinator is faulty. See the + documentation of the exception for further details. +- `FaultyParticipantOrCoordinatorError` - If another known participant or the + coordinator is faulty. See the documentation of the exception for + further details. +- `UnknownFaultyParticipantOrCoordinatorError` - If another unknown + participant or the coordinator is faulty, but running the optional + investigation procedure of the protocol is necessary to determine a + suspected participant. See the documentation of the exception for + further details. + +#### participant\_finalize + +```python +def participant_finalize(state2: ParticipantState2, cmsg2: bytes) -> tuple[DKGOutput, RecoveryData] +``` + +Perform a participant's final step of a ChillDKG session. + +If this function returns properly (without an exception), then this +participant deems the DKG session successful. It is, however, possible that +other participants have received a `cmsg2` from the coordinator that made +them raise an exception instead, or that they have not received a `cmsg2` +from the coordinator at all. These participants can, at any point in time in +the future (e.g., when initiating a signing session), be convinced to deem +the session successful by presenting the recovery data to them, from which +they can recover the DKG outputs using the `participant_recover` function. + +Since returning successfully does not imply that other participants deem +the DKG session successful, returning successfully also does not imply +that redundant copies of the recovery data exist. For example, it could +be the case that other participants raised an exception instead, and this +participant will be the only one that obtained the recovery data. In that +case, if this participant's storage fails, the only copy of the recovery +data is lost. As a result, this participant will not be able to convince +any other participants to deem the DKG session successful, and it will +not be possible to create a signature. + +To protect against this scenario, callers **should** ensure that all +participants deem the DKG session successful (which also implies that +they have a redundant copy of the recovery data) before using the +threshold public key (e.g., before sending funds to it). The recommended +way of doing so is by collecting acknowledgment signatures via +`participant_recovery_ack_sign`. Callers can alternatively employ some +other means to ensure that they will always have access to the recovery +data (which can be used to convince other participants that the DKG +session was successful). For example, they could use a custom redundant +way of backing up the recovery data. + +*Warning:* +Changing perspectives, this implies that, even when obtaining an exception, +the caller **must not** conclude that the DKG session has failed, and as a +consequence, the caller **must not** erase the hostseckey. The underlying +reason is that some other participant may deem the DKG session successful +and use the resulting threshold public key (e.g., by sending funds to it). +That other participant can, at any point in the future, convince this +participant of the success of the DKG session by presenting recovery data to +this participant. + +*Arguments*: + +- `state2` - The participant's state as output by `participant_step2`. +- `cmsg2` - The second message received from the coordinator + (`64*n` bytes). + + +*Returns*: + +- `DKGOutput` - The DKG output. +- `bytes` - The serialized recovery data. + + +*Raises*: + +- `FaultyCoordinatorError` - If the coordinator is faulty. See the + documentation of the exception for further details. + +#### participant\_investigate + +```python +def participant_investigate(error: UnknownFaultyParticipantOrCoordinatorError, cinv: bytes) -> NoReturn +``` + +Investigate who is to blame for a failed ChillDKG session. + +This function can optionally be called when `participant_step2` raises +`UnknownFaultyParticipantOrCoordinatorError`. It narrows down the suspected +faulty parties by analyzing the investigation message provided by the +coordinator. + +This function does not return normally. Instead, it raises one of two +exceptions. + +*Arguments*: + +- `error` - `UnknownFaultyParticipantOrCoordinatorError` raised by + `participant_step2`. +- `cinv` - Coordinator investigation message for this participant as output + by `coordinator_investigate` (`65*n` bytes). + + +*Raises*: + +- `FaultyParticipantOrCoordinatorError` - If another known participant or the + coordinator is faulty. See the documentation of the exception for + further details. +- `FaultyCoordinatorError` - If the coordinator is faulty. See the + documentation of the exception for further details. + +#### coordinator\_step1 + +```python +def coordinator_step1(pmsgs1: list[bytes], params: SessionParams) -> tuple[CoordinatorState, bytes] +``` + +Perform the coordinator's first step of a ChillDKG session. + +*Arguments*: + +- `pmsgs1` - List of first messages received from the participants + (`33*t + 32*n + 97` bytes each). The list's length must equal + the total number of participants. +- `params` - Common session parameters. + + +*Returns*: + +- `CoordinatorState` - The coordinator's session state after this step, to be + passed as an argument to `coordinator_finalize`. The state is not + supposed to be reused (i.e., it is supposed to be passed only to one + `coordinator_finalize` call). +- `bytes` - The first message to be sent to all participants + (`162*n + 33*(t-1)` bytes). + + +*Raises*: + +- `InvalidHostPubkeyError` - If `hostpubkeys` contains an invalid public key. +- `DuplicateHostPubkeyError` - If `hostpubkeys` contains duplicates. +- `ThresholdOrCountError` - If `1 <= t <= len(hostpubkeys) <= 2**32 - 1` does + not hold. +- `FaultyParticipantError` - If a participant is faulty. See the + documentation of the exception for further details. + +#### coordinator\_finalize + +```python +def coordinator_finalize(state: CoordinatorState, pmsgs2: list[bytes]) -> tuple[bytes, DKGOutput, RecoveryData] +``` + +Perform the coordinator's final step of a ChillDKG session. + +If this function returns properly (without an exception), then the +coordinator deems the DKG session successful. The returned `CoordinatorMsg2` +is supposed to be sent to all participants, who are supposed to pass it as +input to the `participant_finalize` function. It is, however, possible that +some participants pass a wrong and invalid message to `participant_finalize` +(e.g., because the message is transmitted incorrectly). These participants +can, at any point in time in the future (e.g., when initiating a signing +session), be convinced to deem the session successful by presenting the +recovery data to them, from which they can recover the DKG outputs using the +`participant_recover` function. + +If this function raises an exception, then the DKG session was not +successful from the perspective of the coordinator. In this case, it is, in +principle, possible to recover the DKG outputs of the coordinator using the +`coordinator_recover` function together with the recovery data from a +successful participant, should one exist. Any such successful participant +is either faulty, or has received messages from other participants via a +communication channel beside the coordinator. + +*Arguments*: + +- `state` - The coordinator's session state as output by `coordinator_step1`. +- `pmsgs2` - List of second messages received from the participants + (64 bytes each). The list's length must equal the total number + of participants. + + +*Returns*: + +- `bytes` - The second message to be sent to all participants (`64*n` bytes). +- `DKGOutput` - The DKG output. Since the coordinator does not have a secret + share, the DKG output will have the `secshare` field set to `None`. +- `bytes` - The serialized recovery data. + + +*Raises*: + +- `FaultyParticipantError` - If a participant is faulty. See the + documentation of the exception for further details. + +#### coordinator\_investigate + +```python +def coordinator_investigate(pmsgs: list[bytes], params: SessionParams) -> list[bytes] +``` + +Generate investigation messages for a ChillDKG session. + +The investigation messages will allow the participants to investigate who is +to blame for a failed ChillDKG session (see `participant_investigate`). + +Each message is intended for a single participant but can be safely +broadcast to all participants because the messages contain no confidential +information. + +*Arguments*: + +- `pmsgs` - List of serialized first messages received from the participants + (`33*t + 32*n + 97` bytes each). +- `params` - Common session parameters. + + +*Returns*: + +- `List[bytes]` - A list of investigation messages, each intended for a + single participant (`65*n` bytes each). + + +*Raises*: + +- `FaultyParticipantError` - If a participant is faulty. See the + documentation of the exception for further details. + +#### participant\_recover + +```python +def participant_recover(hostseckey: bytes, recovery_data: RecoveryData) -> tuple[DKGOutput, SessionParams] +``` + +Recover the DKG output of a participant of a ChillDKG session. + +This function serves two different purposes: +1. To recover from an exception in `participant_finalize`, after +obtaining the recovery data from another participant or the +coordinator. See `participant_finalize` for background. +2. To reproduce the DKG outputs on a new device, e.g., to recover from a +backup after data loss. + +*Arguments*: + +- `hostseckey` - This participant's long-term host secret key (32 bytes). +- `recovery_data` - Recovery data from a successful session. + + +*Returns*: + +- `DKGOutput` - The recovered DKG output. +- `SessionParams` - The common parameters of the recovered session. + + +*Raises*: + +- `HostSeckeyError` - If the host secret key is invalid, or if the key does not + match the recovery data. + (This can also occur if the recovery data is invalid.) +- `RecoveryDataError` - If recovery failed due to invalid recovery data. + +#### coordinator\_recover + +```python +def coordinator_recover(recovery_data: RecoveryData) -> tuple[DKGOutput, SessionParams] +``` + +Recover the DKG output of the coordinator of a ChillDKG session. + +This function serves two different purposes: +1. To recover from an exception in `coordinator_finalize`, after +obtaining the recovery data from a participant. See +`coordinator_finalize` for background. +2. To reproduce the DKG outputs on a new device, e.g., to recover from a +backup after data loss. + +*Arguments*: + +- `recovery_data` - Recovery data from a successful session. + + +*Returns*: + +- `DKGOutput` - The recovered DKG output. Since the coordinator does not + have a secret share, the DKG output will have the `secshare` + field set to `None`. +- `SessionParams` - The common parameters of the recovered session. + + +*Raises*: + +- `RecoveryDataError` - If recovery failed due to invalid recovery data. + +#### RecoveryDataError Exception + +```python +class RecoveryDataError(ValueError) +``` + +Raised if the recovery data is invalid. + +#### participant\_recovery\_ack\_sign + +```python +def participant_recovery_ack_sign(hostseckey: bytes, recovery_data: RecoveryData, params: SessionParams, aux_rand: bytes) -> bytes +``` + +Sign recovery data to create a recovery acknowledgment. + +This function allows a participant to create an explicit acknowledgment +signature on the recovery data. This can be used for an optional +acknowledgment round where participants acknowledge that they have +successfully received the complete recovery data. + +*Arguments*: + +- `hostseckey` - Participant's long-term host secret key (32 bytes). +- `recovery_data` - Recovery data from a successful session. +- `params` - Common session parameters. +- `aux_rand` - Auxiliary randomness (32 bytes). FRESH 32-byte randomness + is optimal, but 16 random bytes or a counter padded to 32 bytes + is acceptable (see BIP 340). + + +*Returns*: + +- `bytes` - Acknowledgment signature (64 bytes). + + +*Raises*: + +- `HostSeckeyError` - If the host secret key is invalid, or if it does not + match any host public key. +- `InvalidHostPubkeyError` - If `hostpubkeys` contains an invalid public key. +- `DuplicateHostPubkeyError` - If `hostpubkeys` contains duplicates. +- `ThresholdOrCountError` - If `1 <= t <= len(hostpubkeys) <= 2**32 - 1` does + not hold. +- `RecoveryDataError` - If the recovery data is invalid or does not match + the provided parameters. + +#### participant\_recovery\_acks\_verify + +```python +def participant_recovery_acks_verify(recovery_data: RecoveryData, params: SessionParams, ack_sigs: list[bytes]) -> None +``` + +Verify recovery acknowledgment signatures from all participants. + +This function is used to ensure that all participants have +received the recovery data before the threshold public key is used +(e.g., before funds are sent to it). + +*Arguments*: + +- `recovery_data` - Recovery data from a successful session. +- `params` - Common session parameters. +- `ack_sigs` - List of acknowledgment signatures (64 bytes each) + from all participants, in the same order as `hostpubkeys`. + + +*Raises*: + +- `InvalidHostPubkeyError` - If `hostpubkeys` contains an invalid public key. +- `DuplicateHostPubkeyError` - If `hostpubkeys` contains duplicates. +- `ThresholdOrCountError` - If `1 <= t <= len(hostpubkeys) <= 2**32 - 1` does + not hold. +- `RecoveryDataError` - If the recovery data is invalid or does not match + the provided parameters. +- `InvalidRecoveryAckError` - If any recovery acknowledgment signature is + invalid. Note that this does NOT mean the DKG failed + (reaching this point implies the DKG itself was successful). + It only means it cannot be confirmed that all participants + have a copy of the recovery data. + +#### InvalidRecoveryAckError Exception + +```python +class InvalidRecoveryAckError(FaultyParticipantError) +``` + +Raised if a recovery acknowledgment signature is invalid. + +*Attributes*: + +- `participant_id` _int_ - Identifier of the participant whose signature is + invalid. + +#### ProtocolError Exception + +```python +class ProtocolError(Exception) +``` + +Base exception for errors caused by received protocol messages. + +#### FaultyParticipantError Exception + +```python +class FaultyParticipantError(ProtocolError) +``` + +Raised if a participant is faulty. + +This exception is raised by the coordinator code when it detects faulty +behavior by a participant, i.e., a participant has deviated from the +protocol. The identifier of the participant is provided as part of the exception. +Assuming protocol messages have been transmitted correctly and the +coordinator itself is not faulty, this exception implies that the +participant is indeed faulty. + +This exception is raised only by the coordinator code. Some faulty behavior +by participants will be detected by the other participants instead. +See `FaultyParticipantOrCoordinatorError` for details. + +*Attributes*: + +- `participant_id` _int_ - Identifier of the faulty participant. + +#### FaultyParticipantOrCoordinatorError Exception + +```python +class FaultyParticipantOrCoordinatorError(ProtocolError) +``` + +Raised if another known participant or the coordinator is faulty. + +This exception is raised by the participant code when it detects what looks +like faulty behavior by a suspected participant. The identifier of the suspected +participant is provided as part of the exception. + +Importantly, this exception is not proof that the suspected participant is +indeed faulty. It is instead possible that the coordinator has deviated from +the protocol in a way that makes it look as if the suspected participant has +deviated from the protocol. In other words, assuming messages have been +transmitted correctly and the raising participant is not faulty, this +exception implies that +- the suspected participant is faulty, +- *or* the coordinator is faulty (and has framed the suspected +participant). + +This exception is raised only by the participant code. Some faulty behavior +by participants will be detected by the coordinator instead. See +`FaultyParticipantError` for details. + +*Attributes*: + +- `participant_id` _int_ - Identifier of the suspected participant. + +#### FaultyCoordinatorError Exception + +```python +class FaultyCoordinatorError(ProtocolError) +``` + +Raised if the coordinator is faulty. + +This exception is raised by the participant code when it detects faulty +behavior by the coordinator, i.e., the coordinator has deviated from the +protocol. Assuming protocol messages have been transmitted correctly and the +raising participant is not faulty, this exception implies that the +coordinator is indeed faulty. + +#### UnknownFaultyParticipantOrCoordinatorError Exception + +```python +class UnknownFaultyParticipantOrCoordinatorError(ProtocolError) +``` + +Raised if another unknown participant or the coordinator is faulty. + +This exception is raised by the participant code when it detects what looks +like faulty behavior by some other participant, but there is insufficient +information to determine which participant should be suspected. + +To determine a suspected participant, the raising participant may choose to +run the optional investigation procedure of the protocol, which requires +obtaining an investigation message from the coordinator. See the +`participant_investigate` function for details. + +This is only raised for specific faulty behavior by another participant +which cannot be attributed to another participant without further help of +the coordinator (namely, sending invalid encrypted secret shares). + +*Attributes*: + +- `inv_data` - Information required to perform the investigation. + + +## Changelog + +To help the reader understand updates to this document, we attach a version number that resembles "semantic versioning" (`MAJOR.MINOR.PATCH`). +The `MAJOR` version is incremented if changes to the BIP are introduced that are incompatible with prior versions. +An exception to this rule is `MAJOR` version zero (0.y.z) which is for development and does not need to be incremented if backwards-incompatible changes are introduced. +The `MINOR` version is incremented whenever the inputs or the output of an algorithm changes in a backward-compatible way or new backward-compatible functionality is added. +The `PATCH` version is incremented for other noteworthy changes (bug fixes, test vectors, important clarifications, etc.). + +* *0.3.0* (2026-07-30): Major expansion of the reference implementation with message serialization, comprehensive test coverage, and API refinements: + * Add message serialization for all protocol messages, enabling byte-level interoperability between implementations. + * Add optional recovery acknowledgment round: `participant_recovery_ack_sign` and `participant_recovery_acks_verify` let participants confirm receipt of recovery data before use. + * Add comprehensive test vectors for all public API functions, including per-participant edge cases, boundary configurations, and exhaustive error scenarios. + * Allow single-participant sessions (`n = 1`). + * Fix blaming for public nonces: pubnonces are now validated as curve points, and failures are attributed to the sending participant instead of surfacing as an unattributed `ValueError`. + * Fix blaming in `participant_finalize`: an invalid certificate signature is now attributed to the coordinator, since the coordinator verifies all signatures before broadcasting the certificate. + * Fix TapTweak to use BIP 341's x-only serialization. + * Split `recover` into separate functions `participant_recover` and `coordinator_recover`. + * Rewrite Setup section to make the authentic-hostpubkeys requirement explicit as a precondition of the session. + * Fix EncPedPop description to match the reference implementation. + * Expose randomness as explicit parameters: `participant_step1` takes `random` and `participant_step2` takes `aux_rand`, which were previously generated internally. + * Remove message classes from the public API; protocol messages are passed as `bytes`. + * Remove `ParticipantMsgParseError` and `CoordinatorMsgParseError` from the public API. + * Add `RandomnessError`, `InvalidRecoveryAckError`, and `FaultyParticipantError` to the public API. + * Align terminology and naming with the draft of [BIP 445](bip-0445.md), including renaming participant "index" to "identifier". + * Rename variables `id` to `participant_id` (and similar) to avoid shadowing the Python built-in `id` function as well as for enhanced clarity; this change also affects some field names in the test vectors. + * Rename `params_id` to `params_hash` to avoid confusion with the participant identifier and update the hash tag accordingly. + * Rename `secp256k1proto` to `secp256k1lab` and separate it as an independent subtree for reuse across projects; see the [upstream repository](https://github.com/secp256k1lab/secp256k1lab). + * Update the preamble per BIP 3. +* *0.2.0* (2024-12-19): In addition to various readability improvements to specification and reference implementation, the following major changes were implemented: + * Fix security vulnerability where the CertEq signature did not cover the entire message. + * Add blame functionality to identify faulty parties, including an investigation phase. + * Make threshold public key Taproot-safe by default. + * Let each participant encrypt the secret share intended for themselves so that it can be decrypted instead of re-derived during recovery. The encryption is symmetric to avoid the overhead of an ECDH computation. +* *0.1.0* (2024-07-08): Publication of draft BIP on the bitcoin-dev mailing list + +## Acknowledgments + +We thank Lloyd Fournier for many ideas as well as insightful and deep discussions. +We also thank Alex Akselrod, Sebastian Falbesoner, Nick Farrow, Matt Leon, pool2win, Jesse Posner, and SeedHammer for their comments and contributions to this document. diff --git a/bip-chilldkg/COPYING b/bip-chilldkg/COPYING new file mode 100644 index 0000000000..1b484bb810 --- /dev/null +++ b/bip-chilldkg/COPYING @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) The ChillDKG authors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/bip-chilldkg/all.sh b/bip-chilldkg/all.sh new file mode 100755 index 0000000000..2d04120169 --- /dev/null +++ b/bip-chilldkg/all.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +set -euo pipefail + +./update-pydoc.sh + +cd python || exit 1 +./tests.sh +./example.py diff --git a/bip-chilldkg/images/Makefile b/bip-chilldkg/images/Makefile new file mode 100644 index 0000000000..2b36cf4bfa --- /dev/null +++ b/bip-chilldkg/images/Makefile @@ -0,0 +1,4 @@ +all: chilldkg-sequence.png + +chilldkg-sequence.png: chilldkg-sequence.txt + cat chilldkg-sequence.txt | plantuml -pipe > chilldkg-sequence.png diff --git a/bip-chilldkg/images/chilldkg-sequence.png b/bip-chilldkg/images/chilldkg-sequence.png new file mode 100644 index 0000000000000000000000000000000000000000..69508f47bc2546f50e5ce3df1b1841653769bb62 GIT binary patch literal 14398 zcmbumWl&v9*EPCvcL?qtJh(fA;6a1CySux)JHg#8I0SbIuEE`9Bi~NWd7gUjSM}bi zTetu1?zwuc)!lopF~*!TTtQ9}2_6p~001CKeHQx)0Dy%60N`k_5TG9Sh2uI%4T zI#yN=c6KgqZWdl%K|VeqAt6p-VJ=ZoQE_pp&z}WkWksc=q-15kDk@4SDScK@P*7I> z_U)UjuI^WDZEYPLBRxGO6BBjQKh4_8O2^h#&(hM;!ouFs(a6ck$==@0)6>%3-ONrJA~!cTJ-sj|r!+S&GQS|Is5mY^KR++8yriVAq%@(dJh8eat^7}Gs;OzK zsZDQa%&Py>I-8n$T3U14I|{nHyIWfay1U1^dy0GeN(ToAhlb{chO0(KYbK`}M*g(L z$;p+e>Bi}qrul`o=|64#*RQ=_OC2k#UF#b?tE;O^OZ)5V$D3Py+dBjM2g5r%JKNhA zJ3E&Lha<-)W-{0LfH8?>V^U+CM-O13_&fUt`#0em2 zY-8-8?__L5V&G0<=Hz7O$iu{BXQglB^^SEUt zCro`AU`7qSaF5__o@d}ljvz`=LCJyag;6(fX%4ZbW>1HIzNJDKD@I86kovTp%B1^d z#YZ<}xC!UW%tlky8m+o7?c!o&ZEEl%8 zZ0~1E;S>=~S?l>&o1>4j$y>PXx2eNbAEnnlT{I0eWd1E;vPx!LcUvl&9!b%nLCsqn zNm60yHV!p=PI3KV5|du6-k>RGc}^Eh5)z9u{u6aNf%ge+=tsJ-Yo^}(2WzZet@(tA zj#Y$bipnYRFS!rDs6BpGd}Jz@nDsr~L-&OH=IBn{un3%X-G@mHBkqm#F8Td9nmIi% zMPL3^XRJ7>8xRT}#!zAz3jnY~NQnt6yRDpgqd3hicD{3B*~4)JLC!)3K(L_ZQ3M-J z*H-(pH`4u>LxYBrP-I>@)lPSv4?aaT&P5f#&RpAB z+aABSR!LhZ#9HQYAxK14RIq+TF+GK(IeAX<~}9 zILcK-2*dJ0(dj6l(fuytXn#&Wv#5aZ-e9;jlI;) zAeyDJoDgTA4KJw*bMI~$8<0wRj;4$bqK%2CKU;sNQpGPjQ4{iIU3;y4xDo<*v@7+KNp6#GsbgL;&xr%bz80 z-E-vIL2HVO**S`EWns|7?W{cuevIbK>a-dv$7pPGp_FO^af6)pQMDY&LCkX>Z9JUK zgI=Aj6JX$h4eQ}2IUU=^6oTx$iy8M;6-WC#8n!TC5s4762}dU}KN0s#RZaSL^mZpKA}w;T!KdqDM$)R{+{c-XM8~Fka)L5A@@NZIo9)e|VPr%k!;q?YqV5W5xn&~olz(h`OXaZCZ( zq!rb=V0Y8Qh|Oz1fBq1@gPF#^MFdty`5JR#pPQNI6K$D_ea)~WG40BwNm)EC7|ZKv zyqG4%;epqinKT?KP1nWv>cgk>+zWTSZP6GCS^&Y(KbymzQ1H9y!Ta&#B zygO4M-m;2Db#KNa8Fu~WKNlu6Kb|)T@yqz!m#|mg#rzv@>--NM@iD}QU*>jXlF|jb zWhj)AO^tykhvXl>gZ`C)8Dolq7>?%i6-hgKB`(etN&*%p3dkJ{`?BaY&B(EbKbJVA zeyse=L`VCN6CoVWI}2!jRMv!H9XNnEjCM%60f#mFu(U==J%1b-5d0i-R%#T7oD0jg#N&Z+$!`H`&B@b%> zZOXtm5~aR0cr~fbssB>&ThMhvaGs@Et5Mfs?Z+{{DU9>G$kLvl^==B`FYj*A(bE4}# zW@^8mdEO5h!OT8n}`&yE> z>O!-7vn@J`a z^1FV!r5*vNm!0TQ#v)m}eFq%7d#XkW&u_yw>5YzWR3INWe`&@Ni7Lo-V=Hn2np<$Cb*G+} zR(647`U=(SW^K4Qp||2~=PM7{FpmA;Yq|%FGh+T0&N_mi{8tWK!=pIEvc3^aciG079)KYLjmkJJS$A>Q42(Cx-j z$x34wLFM9(D=5t8MkMXRaj!_4~A%}NRN49u*vl|T_1q5^W42ldJ6U>xWeqw92rxzK6hOiFRXqE5QN4m z>}WgQmhnmO(u8R(R%mEVI#{o0+&hr+;`O51oLoc7zB=K@eX-#>dAa8Y0v6k`Q`;6l zbx*`IF%Uw>BH^p(K(@$HGT^B0LCNz9!JJfIW38B;USZFH`@y@ptm)u*ra;R>#JJyu zm>wmZ5P2cmc>9;Tdj5noM_>=m7%W=U@xC0irQUXVl{3WN2$4Ro z!N#0);@u)TIFkw_nPB4@f@IbgpZUbKn>YyE=LWDtq}sG*2$t9L{UYFt-qICs z>#4vMQUQY-qw%#gz!5bwz=Bpwj!_?`|N68cjl}4BfVj&uUH=lcV5e~iDQYTtoX*Eq&~Vo z5PC5Ba-lG4;^J=O{PdnSL0b5*@>_a2A8cJqi&sRYlrUB;MpZx#^<0+rm)H2R$86R~ z)%|a3R|v!d15+LXC(1h#bq$Rc`#OjxZ$DyVYX?7uGc!@$3YcpcUv^|cc7`M3vqwup z*dbmsYN1A&X`o9i=Jm3)_Kzh>d1J64D}MT)BLy6;L;D2dN8FD?_8 zpZvHt*cZmny7gsmL=gB~PUPhlW3fW=yrBi3!t^eZ0L*xx+)nc&bXA44jI6>0%~{ZkIgR*h zvJMZE!&%UMPT@Gvh!0x|<2^Kk&sto+c#)df{NfzAoZm+M-(9dToGdF`+)!6&p?ZNZWFJ+b zZuE{uXHB>1VQB-L?;J1d7)&cRsEb8Qz9X(q7SU%xW@}ap-7A@t0$UmOHfNig+I(1B zTFDx2!0#r|-K;fe3HTcD@*b>ha)nLBH^2|qdbmHu8nu_Uz}Uae)6TqG#&K~C+T2iG;RIW0O88QGxl68k((M9)Qv(}v!cM?@J6Lm~ylRDhQi9!6oq-)L>w z@h9nW&Fuk3kJ82JV#ZZ5dIqq9rMIPX={GL!xIejaAM^>I4xjF?C9kd=Wo8#7i2~u& zKKP|ONJyQ17CxroiFA}|2isLY*uhtjQ+p&xhXHbb+I@Xq45e8tYqkMOmZ?oFvR>}7 z=>#yeo83(ju8Pry+Sl+Y0o5*DeIv9$lbkmAi+-y?2{E+!z zcc6@MGQ2kWR;@{JD^QL@pPIGGb^OtXcuTTy)5(wF)eeG-msGGw0Gr=!;%MBnx)TF$ z<@}}y)-{`5ZFc+95KRR&Y#07gjKRWBL80T1`)wLvZtU`oJaPeYlyy+D`--}o+kwe=aV;dDBNNo9^ql_lvvPhej(=P*;h>d;Z=?n zYND{@YQp%H3LCHa!sf1TFSiVqTF|jQL-I&Kw;qTAZqVxC>S%U!U z%{&r?W+Dd#BYtJ-jTjZ5)vynTD-?tM_nC_Iz_pD+_AmXlkjx-^5h+=Yp`sIM?O^Agnin@6%)J^bOI5o0OYqugetVWh2 zkc;ZNn3DP$=GFqjb6L1il5A)Syx88jp8`L9SezEmsK-5yRG$#16Iw`T^sCp4Y@<8+ z(WtJSv=m4Xd;W7uRyyGfw--5e<^y{Z?xk=t?izrcH9d0<#KNO=lMp9hvg7LUsO?HE zwK5!Q<}!I)KfbhCzU9Z%?U7bg_>+3%3eYOtg)x4~-&|UN;K$b_9-Yv+y%r z&?|!%v`=n$*CRcfqbI5yXT18Pm_uERID`~iGZ5)M=by`#mBGoh$!B!8%x(KQkT0Fmdj2paX!8EnpZ49RF%ijmzLk96Uc!?2TAfB zgilfLvnBaMa10LsL2Q9=pV-qJQWBstM6@EdIZQt0Vb#2KpH6wx4pwYu-; zi=Ns}Nh7U$s8M;mfEYuBcwh#-^MlasVN6n#W_5@jasR4li1%Uc%i4B4&(Ww1sY-Vy_MzDM^^3F?sk+@RARw+?#c#Q$)qm4(l(w__gux zPh%iccI+y&0->L0x(Sk>18gK->hyiS{>?Eu9m; z!Yp8=rWqtmq@+eT^Mbn1SxU~Swr#?YaXCLO?`+eFvkTb;oGTziizvBT`o%fUtX#T8 zcHu;}4FhF=P9$3`wcrsC)aS#s2lmIdXXdpXmI`rY2O_nkJo?yDNs&qaL=qlu?cT;H z_mSZWGsaeVq8jd(PXca9uFJ+qy0UGrc_Woxgz&X|9&YueAV=MfC{jn4IWh_fq&CCx zF_k*hRhJoQy$dEX(X)m0WBHXjKx-Wc4RT*Ff(Ko{bwegRa)0prD9~T$N%QwPdX&mI zhJP?3K~m9Btsc6ZP_BP2vl`u5$14%Q!xJh|Y;=B-W~JYl2qAPkyzjI4NNZ29Y{OTC zcAFL1%vfUia{$R5PWYst5PSwn4_^A0G&1y#)~!(Bzog|*0Gp8W^J&{*tN$Oyqj1;P zE+1W6ys%bCd6JH-z-g0K)6$Luyi+9^W#*wQ`pg!4&K1lOA?Ni+&i$R)i|<73k1ETm zya;V09+Bz0X_vWbSB+uXt%jj!JtM>yRiLSnCa4QHj;y;7nu3@JvGufhjNrNy4I(`Q zPYo{ZjX&`6QBevH33Hi!Rhb4mx)|I?n7IKdsqXS~qXHx9gC^ecV-@-M^YTFDwO{7M zY$cE`oNZsns8~7(vDJLN`d?QiO;n7SE`N9?H^16*sr?^P(@J0iwy>_6j~WuG z`H3$Y=kwxl31*PYwocDq2-rd6<1i^@+h-s8#h+==VT2!1tV129;B@E{?;i@pmU-yk^#UC)Vr6w8?tzh&YSGCHeYZJQaA-55*J>E}Jsd4{0@YS)?;uY*hK!OOQtVl86m%MjP)xNM>_E@FhO z7h_BWKWSo1qWhpAMSNnD>FF!k=4eIZ+*6F;y24UAfUDn$w~G+&q)kn^C<}za7aP$a zT)iE5oYzn$gx+>^3&!f=wPc-$XowzYn6zA!!Q`*ziu3?hxn&`+@xl&54c>I>K8j8K zViXTkXiq6t9l#J=-Dlym$!*Ct`0SEY*e>!j>UQ`SLufOUPvd@M;Fu0?wX~QAoJw6y zeeI+3z0jDjkk|+So_EXDFS?^UrG$so{8_j`R=`YF?Ajr_N`SD@QEhQd8TB`{I0oD< z>_U6GQ{S5x8n$59+kDsWbYODf@}FWL;)b9x&F(Oeh!A?PVTFcWE>-=6;@xkyv?JUF z78{eLG*V)5nE?_rB4PkAF9Lw0$KGVO109}QiRy>?I)GgsfJh|F?}22~CiP4|?U(?G zUImC|5yHf2HV$yuFJOnD6oW+VkwjI$>*6n|4QVrZhq~l^WIyiP`@3PNL8xGi#W+zO z{$oilY#o-ew@&-}m(6mR?pP6q&|z?QO~J_i5a8R9s3hPqB!Ej0OYb%W-S@i+4J_L9 z5gwsY9b^oy-e%5C?N9w=X9NP0$Q~%`L)XXsbu@-*%C{jSL$J>k3aDfxt*{_F&H5*= zFZOl!Es$u1W-A}&H=SYE&x%QyG0N<)C)3Db|K;o`Z+-zfnsFJxuN8_mApp;a~Zs9CGKOduZBYadNzo>n+Ek|Kkoo zY@a293j8Mxp8sN>=>ooiJba;&$m7eM+2tv@Ub8ptvd`yc?|wvq;mgypx}#9usO9_D z;d{~gGklXv;Sf{Z7+JwJ{qg1t$!6jNYhOTA0r#cy^S^qZ-RRXBb@#G~9E^+bG`LLV z)Xgcmk9Rop1>1+u&BQ&;7xK--5!Shgu{LMDN=G%$OpK0IP^5d0_rEr1b%zI9}5VwY|y`@FpUjeu`fKrka<{?a|nFh%J)zqNo-ALYX?)YjX;mApWc$o&_)R^nX9 zhD*+nc&47cL;ylhrenzNFKl@GJ{c)ukEYDy0{+g^Dnjr zE1=~@@SWukknAOjFekbF4q7^A^)n?bL$w|6!@KN~*`cWCMmM+=Vrc=_tb^qPPa=`R}&~G3L)X{u5#QpI@jQJIu2)U6b$8W;_v80owHR1BwtvfzZt9pP;}W z-|hLc-wz0^vZ;xcNz@Mkvde=z1xg8GNO5p@J;o8_Fqcp-Pavc7_s&lXvjh~SevJ7L zJAH`z?`n)zYQ22w?e(|cxDZ4}jk|nF^86S~=~i3`Qs$M!JJs8>+Prgg6+to1IA6=f zg{j^lk(nhwKa_Ql3N@XQnk@0pJ>cUZxGtfX-vwi+dQ5Za#LgYLO8vOX0g)v65NwcO zjx$&}--4~_8@dxp!5oAkTF3o{!RUWKy_?%UT<}=MI?Rn~DS(+P`-9BUv$V(0GX!Gq z2vk0>3o)7jQb)v*FLO+dLlE}29^b`hAngbbQi{$!GTX-N6u<~n{6yoX!Qdtt7s2>e ztNKd8>V+ZPGHyFOTM?RvsjU;lFobG_^aZ>u6W6IAv3eAtxDL^HK!N!0B8EbF5Ou#F>l4<$()}!kEDlo|D1Nx0_b6ABt2HrEhLPacbI}X zjN&mk`to>~#>wh|+v*#gFrjfWp2u{tc6a99WSwaXM5*ka$TdaHtP`FQKP{e`KdR&2J%eniq&G)4i(`$yfXB zB2f*7L2U@+pJzj3iA!*$x2OZbyniAUC#0%I71CTRayb{y19*@&E9rDTN?qA-oc4&e zV5=%gh3`(^*UBG(f%6{}RMDxn%)FLK5jQpYbrG!>;{++s37s8^zRP<;gTlw8e}m6| zy)|EZxWejTQkY-)_G%Xe`>X~TZ8f+?NNBqv8w+)J_qz%zRH4$>2r+z6%qB|FX{R3) zd?wjM+{ojF3o^^-mHe@33;&j~?zjF zrYnZeKA&=#2Asa0qz_#o0RCkXl2Gsetm1+NaZG^j*3i)WGi(~Gu6i4Wm|Wj1aZeYq zP}jv!*Hq#Ku;F3|9FL*>c*ug-DPJaGh_cJAnAzd9FC^|1=tJ@L?;!}Rb)NBmd9>IQ zsl6D(Oo%El`~gdCqF@VMq(&B$#hxGnhjcCIgaTp&3h*F?Fq6M!J9g+x$m4b##~KlX z4CPx%u006x|IQh9;tDmXDKaEm(2hQ$hOf}NlM9q0+)@Lg(Z8SG&-pphd7#N?Gl@ z(=7nY`&p743xF4R+cbBc<5_hgGx z9WGu|hEbY3%fV-6PQ1T-9S8-%@rb^nOUl~D`zKWkXP>Si1o|p|;|rRR7Sa&kae-K~ zvvoV0jfR|-^r4_CF=a)NTAzYQ*trpg!HT%Ar78TyQeI7YT0qqkh|lURbH^qrZTp%s z^5s6rZ)1 zog+j2AGSN}UwJ%0sJJljGnKO@K{}Ae+zIW%R9xx7C3B9hX3a>=v`(i+PeSci);Ud3 zfFlwHaypfQOv;TBoJ&Oe$qzhILVj+?nP-0ud8Nn~9UIgtev_tD(DEVPr={pbkT~65 zZ^7Of25_;6-aFiejCfEq7zP^bPr9Yw_{jp%dMwKT=(=lt`wyxLQ*e#9>3zMaZQVmK zOe*u`w7X4Np;ZxLz;G^>Ktw|R_q1f=rUTs5z-T1QxtY}zD*@YdGzc9D3!AXO1Eyt% z!p-0O=H-#o&3VXmqK7e2)aNL}IjH6BT7yhf=8GD(nslP5TDa&0seAV{Lwgv}@?q%Jhe2H}1VgiY9=G#X6P))mo;jc?3Y58cSd zVTmZ~;r|Pi4(E@E83i{WY6%fnVLx(M8XyvXXXTL&WA`K8>-xVEgL-hpA*xn#?*g=K zI^)L3nW};uHV>1wZhkE&p1Qstq)en5!M`}SSg}XYJVc|c6Lhr`_Ry_>b6?l%PIrSp zUb^_#TRd~fU|dXkI9j#K2=^lrsKS_>;dFh~S=3KqBFV-7+{V;0O;aa1L*Vwodn%|>k+gwWGwn=0sD>}(xx`Vf>;zim)5MQ_?nf1&!C_pxw3R`&mWhiY-dI|ii zJF-l5*^eK5tBFv2Fx?`fuLBoJr|0H3q!^5C#7uw(>KNbaGOT*Ffd1Dl_njNNWUa0;se3h+Fk#d% z!aL=&E*Sx&Ptwb+#xaoXY>?mup7sHzhvqpwiNE_fkJFs;^*PtWCoM?H>6GQ+S$+_W zI&Guq|AD^Mp~>8dqI0U>zcuVL0V<%o@5Y?5^BY4#QcO_a#+1%03@T%JPem_I`630V z=1K-ngkE8<+jc<<>>7sLZ8o2qKpeQcg(BIuiSqN=El?`qb4`V~Y8E;&T%SPBtKRjS z5Wx%Rw7>-676hR30rT^N9R4t^LMVn`WXOJsPJBI&)3T80FUtxbzQ^p50Mrdd@ezC< z;OOrbt7DW6zsK;!B*eBr`D0pY%~ufqpXAm7l#a9wX0G;dzutfIMnIXh{*VOXbnpmy z0xqClLga)zuwDabJuy69(8&%Bq%aezO#{Ob`-%KHjHz-5G-wDKB;pm~9q`BHY0=7O zU>X}h1hnOW)w4i+tiQ;S{zenGCyw{WpjXQ&*i=;?zvq9IQMk-{INLCX5D#nlE$#*p zTla8O?neYR5YJScX$Xg>eub?rnX#hx@`bHK?j&2AOXB(oNzDbJB3IM^;peS4t%vfd@KNpgBG;3-8 zOzkBu@l)(jgTE7^vOqAC#S-Qb-bOnWg{8kQ-x2$yB~aVTi?RG+9gxwuBy8V5y7*Os zQk8S62Vx+8rP6r3!a5!789>{)O#l;arzho10Hgzmupzr8u(%wDr(a}Wj5C#z0<6EWe&`DCuou~x=z*U7%GGDU34~Tz(Dj=`)O+)x34~!)U z*@Wwn`yqMxWWDlXq#`gy2KZNM-@?r}+W>lV4q(nuL?QSQ^>fbs$W1l{J6k9EM{JjD zoRDB`8Bh(j{SuC{E?`pIGC;HIvz329lhHN?|Oby;wZ2*-ulry;D)aA$6sb0qOD}|=MlXrzFuBj_+kb193~}^ z{Y^3Y4_uv|LaaXp_6|Q_)m;p+6c|Ah8+M7n`=ofk^MAX39bSHDg4!-T{IKYKRpIgD zWHll6_Z8IZD4m?UVmjV4M-<2nt{erViGq?N-w*J)0=$V0-;S9R_|)%TNea+FXoy8Q zJ%6W54w%PHnf5W)aPB>ge5qzwr|0Iz5%a~zAxxiG@v1O`>;5$o%<8oii}(+QtBLa# zjL9!oYIpu<-dp>Tsd)5Y=@zZ`jR9r<;DF}w^*3t2K!VuT(gVcuvm%|qy|-9bP12uB zs+BTo!9lu#Qc&6THxBO@Jz_{7pKsg)wXu}nXbd+GMjj;FlIkuj5d!uLE{uQKvtjF( zd$E9!Dbwg5WO{Fm7U7*sy-A$>J<3~G6GUZIU&Fef&EJ%t2;6+$Y8|fQO#e)>Ens{K zv`2Jiszip-mg1)=8a0Kx7v4HRVWj>%DxC9Y3Yg%(7AAQdmF7BT@n82EAc0m9k57Rk zl#vv#Xjy-3)IeZ%NTGctv~bIJ;TdyhIrZzF;kmcIq8uvsw-lc!l2c%qmmmSqhdytn z{cu|jM z#rf$4N{1cK!xW1uNMzG?#3W64)(v(*1!+n%f>i!`|7y&zbF|ud$ts1=Eg?tVvlL~~ zX?Xo<`EB1W8i;XV%|p;-1@g3UY96E#i#R&om_Sj)3@`S)-5brIVhj9OGqN~8g7vG? zX0)RS*rxxfT-7mVfI6K2FR36(kqs1QECdCuWAH{aqsfnT2PY_BYse{(UGKYN4rz!P zSlRCRKHRa|TkFoge-Y6GiXN?~oUb%12Rk+L6g51(*7Oq}p%BLS6bTS4&cesNMa!#v zmONX|3!YMF56OlWDoIh*OL{0p<)%=d+2ps>%s8VQ zvKBVG><9N{Yw)e5g~^wuN@Q9mF2pMr0N0Uv z!4si{oX*rTf#;Sx3Y218WHn$txy++$e&Dp!m~8bJ-*(+-m&Ibj($02AZ7h4C z;0t4x{r@i(0GCDm|6D9E!V>3KG4hqs!ebBC?G+uiU?Ui+S}?a^Pya}Ln%~ot=Pj2@cMyE$1G6V1b%Dsg7(VM@hA`h7f;&O^6L}dZcLl%=>uOT5khPY4A&v zaE#d$UjTQ!L@ZQfF}7H(6+b*P#2f6;I<{zNyFL8pchwZ@&uOx~prV68sNRbpEb?QM z&))HK_M$^U_Em2~9-yEIb~NcHKxl*HcVcBuxu;W`IQ^u=(la$7em1J(h0)R*7Yne;vd0-#)f$I{nNE2Da;Le4EHgS1R>V=G4{)2yd=SW)UQ(PY|1!j@;I zew%!fJQj}M;1h1w_GKH^Ms!8nsykWbngzG0KPJRrCG4NCyi5=10MoJSCA4{%yYNi5 zlCW!y*i@08$d+*HkDvb(EsX1pSzC?cEBgXEg{NvSMjzE&9JgaqJ{*Jj#LyJAK|AaN z!wt+Q7~2uyWq`avyhg<0)BP|D;Kxe4%I zTko$sE(t|b>ETW7=55&4P963e8W-|fRML5+>dzM%=u|2bXy&Ua{vMdIFthAWhHI6ek z$pvC?mzPPqjwmvmZSg#Z!5^7L7v%3d%ufvz?2D20mRPLGsE81rNzae#@s}|7<_&*1E=S=_r literal 0 HcmV?d00001 diff --git a/bip-chilldkg/images/chilldkg-sequence.txt b/bip-chilldkg/images/chilldkg-sequence.txt new file mode 100644 index 0000000000..a0ebf32f2a --- /dev/null +++ b/bip-chilldkg/images/chilldkg-sequence.txt @@ -0,0 +1,22 @@ +@startuml +skinparam sequenceMessageAlign center +participant Participant +participant Coordinator + +group Generation of host public keys +rnote over Participant: hostpubkey_gen(hostseckey) +Participant->Coordinator: hostpubkey (33 B) +end + +group Session +Coordinator->Participant: hostpubkeys, t (33n+4 B) +rnote over Participant: participant_step1(...) +Participant->Coordinator: pmsg1 (33t+32n+97 B) +rnote over Coordinator: coordinator_step1(...) +Coordinator->Participant: cmsg1 (33(t-1)+162n B) +rnote over Participant: participant_step2(...) +Participant->Coordinator: pmsg2 (64 B) +rnote over Coordinator: coordinator_finalize(...) +Coordinator->Participant: cmsg2 (64n B) +rnote over Participant: participant_finalize(...) +end diff --git a/bip-chilldkg/python/.ruff.toml b/bip-chilldkg/python/.ruff.toml new file mode 100644 index 0000000000..4586e127ee --- /dev/null +++ b/bip-chilldkg/python/.ruff.toml @@ -0,0 +1,12 @@ +# Exclude vendored package. +extend-exclude = ["secp256k1lab/**"] + +[lint] +extend-ignore = [ + # We catch "Exception" for good reasons. + "BLE001", + # Too many false positives (https://github.com/astral-sh/ruff/issues/7847). + "B023", + # We sort __all__ manually. + "RUF022", +] diff --git a/bip-chilldkg/python/chilldkg_ref/__init__.py b/bip-chilldkg/python/chilldkg_ref/__init__.py new file mode 100644 index 0000000000..287997fe92 --- /dev/null +++ b/bip-chilldkg/python/chilldkg_ref/__init__.py @@ -0,0 +1,7 @@ +import sys +from pathlib import Path + +__all__ = ["chilldkg"] + +# Prefer the vendored copy of secp256k1lab. +sys.path.insert(0, str(Path(__file__).parent / "../secp256k1lab/src")) diff --git a/bip-chilldkg/python/chilldkg_ref/chilldkg.py b/bip-chilldkg/python/chilldkg_ref/chilldkg.py new file mode 100644 index 0000000000..63ca9a3b40 --- /dev/null +++ b/bip-chilldkg/python/chilldkg_ref/chilldkg.py @@ -0,0 +1,1256 @@ +"""Reference implementation of ChillDKG. + +WARNING: This code is slow and trivially vulnerable to side channel attacks. Do +not use for anything but tests. + +The public API consists of all functions with docstrings, including the types in +their arguments and return values, and the exceptions they raise; see also the +`__all__` list. All other definitions are internal. + +In addition to the exceptions documented for each function, all public API +functions may raise Python built-in exceptions such as `TypeError` or +`ValueError` when called with arguments of unexpected structure (e.g., wrong +type or wrong length). These structural errors are not documented per function. +""" + +from __future__ import annotations + +from typing import Any, NamedTuple, NewType, NoReturn + +from secp256k1lab.bip340 import schnorr_sign, schnorr_verify +from secp256k1lab.keys import pubkey_gen_plain +from secp256k1lab.secp256k1 import GE, Scalar +from secp256k1lab.util import bytes_from_int + +from . import encpedpop +from .util import ( + BIP_TAG, + FaultyCoordinatorError, + FaultyParticipantError, + FaultyParticipantOrCoordinatorError, + MsgParseError, + ProtocolError, + UnknownFaultyParticipantOrCoordinatorError, + tagged_hash_bip_dkg, +) +from .vss import VSSCommitment + +__all__ = [ + # Functions + "hostpubkey_gen", + "params_hash", + "participant_step1", + "participant_step2", + "participant_finalize", + "participant_investigate", + "coordinator_step1", + "coordinator_finalize", + "coordinator_investigate", + "participant_recover", + "coordinator_recover", + "participant_recovery_ack_sign", + "participant_recovery_acks_verify", + # Exceptions + "HostSeckeyError", + "SessionParamsError", + "InvalidHostPubkeyError", + "DuplicateHostPubkeyError", + "ThresholdOrCountError", + "RandomnessError", + "ProtocolError", + "FaultyParticipantError", + "FaultyParticipantOrCoordinatorError", + "FaultyCoordinatorError", + "UnknownFaultyParticipantOrCoordinatorError", + "RecoveryDataError", + "InvalidRecoveryAckError", + # Types + "SessionParams", + "DKGOutput", + "ParticipantState1", + "ParticipantState2", + "CoordinatorState", + "RecoveryData", +] + + +### +### Equality check protocol CertEq +### + + +def certeq_message(x: bytes, participant_id: int) -> bytes: + # Domain separation as described in BIP 340 + prefix = (BIP_TAG + "certeq message").encode() + prefix = prefix + b"\x00" * (33 - len(prefix)) + assert len(prefix) == 33 + return prefix + participant_id.to_bytes(4, "big") + x + + +def certeq_participant_step( + hostseckey: bytes, participant_id: int, x: bytes, aux_rand: bytes +) -> bytes: + msg = certeq_message(x, participant_id) + return schnorr_sign(msg, hostseckey, aux_rand=aux_rand) + + +def certeq_cert_len(n: int) -> int: + return 64 * n + + +def certeq_verify(hostpubkeys: list[bytes], x: bytes, cert: bytes) -> None: + n = len(hostpubkeys) + if len(cert) != certeq_cert_len(n): + raise ValueError + for i in range(n): + msg = certeq_message(x, i) + valid = schnorr_verify( + msg, + # Dropping the sign byte from hostpubkeys[i] is okay because msg + # commits on the full hostpubkeys[i]: it encodes all hostpubkeys + # together with the id i. + hostpubkeys[i][1:33], + cert[i * 64 : (i + 1) * 64], + ) + if not valid: + raise InvalidSignatureInCertificateError(i) + + +def certeq_coordinator_step(sigs: list[bytes]) -> bytes: + cert = b"".join(sigs) + return cert + + +class InvalidSignatureInCertificateError(ValueError): + def __init__(self, participant_id: int, *args: Any): + self.participant_id = participant_id + super().__init__(participant_id, *args) + + +### +### Recovery acknowledgment helpers +### + + +def recovery_ack_message(x: bytes, participant_id: int) -> bytes: + # Domain separation as described in BIP 340 + prefix = (BIP_TAG + "recovery acknowledgment").encode() + prefix = prefix + b"\x00" * (33 - len(prefix)) + assert len(prefix) == 33 + return prefix + participant_id.to_bytes(4, "big") + x + + +def recovery_ack_sign( + hostseckey: bytes, participant_id: int, x: bytes, aux_rand: bytes +) -> bytes: + msg = recovery_ack_message(x, participant_id) + return schnorr_sign(msg, hostseckey, aux_rand=aux_rand) + + +### +### Host keys +### + + +def hostpubkey_gen(hostseckey: bytes) -> bytes: + """Compute the participant's host public key from the host secret key. + + The host public key is the long-term cryptographic identity of the + participant. + + This function interprets `hostseckey` as big-endian integer, and computes + the corresponding "plain" public key in compressed serialization (33 bytes, + starting with 0x02 or 0x03). This is the key generation procedure + traditionally used in Bitcoin, e.g., for ECDSA. In other words, this + function is equivalent to `IndividualPubkey` as defined in + [[BIP 327](bip-0327.mediawiki#key-generation-of-an-individual-signer)]. + + Arguments: + hostseckey: This participant's long-term secret key (32 bytes). + The key **must** be 32 bytes of cryptographically secure randomness + with sufficient entropy to be unpredictable. All outputs of a + successful participant in a session can be recovered from (a backup + of) the key and per-session recovery data. + + The same host secret key (and thus the same host public key) can be + used in multiple DKG sessions. A host public key can be correlated + to the threshold public key resulting from a DKG session only by + parties who observed the session, namely the participants, the + coordinator (and any eavesdropper). + + Returns: + The host public key (33 bytes). + + Raises: + HostSeckeyError: If the host secret key is invalid. + """ + if len(hostseckey) != 32: + raise ValueError + + try: + return pubkey_gen_plain(hostseckey) + except ValueError: + raise HostSeckeyError + + +class HostSeckeyError(ValueError): + """Raised if the host secret key is invalid.""" + + +### +### Session input and outputs +### + + +# It would be more idiomatic Python to make this a real (data)class, perform +# data validation in the constructor, and add methods to it, but let's stick to +# simple tuples in the public API in order to keep it approachable to readers +# who are not too familiar with Python. +class SessionParams(NamedTuple): + """A `SessionParams` tuple holds the common parameters of a DKG session. + + Attributes: + hostpubkeys: Ordered list of the host public keys of all participants. + t: The participation threshold `t`. + This is the number of participants that will be required to sign. + It must hold that `1 <= t <= len(hostpubkeys) <= 2**32 - 1`. + + Each participant **must** ensure to have authentic copies of all other + participants' host public keys before the start of the session, e.g., by + confirming authenticity of each host public key with the expected key + holder out of band. This is analogous to traditional threshold signatures + (known as "multisig" in the Bitcoin community), + [[BIP 383](bip-0383.mediawiki)], where a signer needs the other signers' + authentic extended public keys ("xpubs") to generate multisig addresses, + or MuSig2 [[BIP 327](bip-0327.mediawiki)], where a signer needs the other + participants' authentic individual public keys to generate an aggregated + public key. + + A DKG session will fail if the participants and the coordinator in a session + don't have the `hostpubkeys` in the same order. This will make sure that + honest participants agree on the order as part of the session, which is + useful if the order carries an implicit meaning in the application (e.g., if + the first `t` participants are the primary participants for signing and the + others are fallback participants). If there is no canonical order of the + participants in the application, the caller can sort the list of host public + keys with the [KeySort algorithm specified in + BIP 327](bip-0327.mediawiki#key-sorting) to abstract away from the order. + """ + + hostpubkeys: list[bytes] + t: int + + +def params_validate(params: SessionParams) -> None: + (hostpubkeys, t) = params + + if not (1 <= t <= len(hostpubkeys) <= 2**32 - 1): + raise ThresholdOrCountError + + # Check that all hostpubkeys are valid + for i, hostpubkey in enumerate(hostpubkeys): + try: + _ = GE.from_bytes_compressed(hostpubkey) + except ValueError as e: + raise InvalidHostPubkeyError(i) from e + + # Check for duplicate hostpubkeys and find the corresponding ids + hostpubkey_to_id: dict[bytes, int] = {} + for i, hostpubkey in enumerate(hostpubkeys): + if hostpubkey in hostpubkey_to_id: + raise DuplicateHostPubkeyError(hostpubkey_to_id[hostpubkey], i) + hostpubkey_to_id[hostpubkey] = i + + +def params_hash(params: SessionParams) -> bytes: + """Return a hash of the session parameters for out-of-band comparison. + + In the common scenario that the participants obtain host public keys from + the other participants over channels that do not provide end-to-end + authentication of the sending participant (e.g., if the participants simply + send their unauthenticated host public keys to the coordinator, who is + supposed to relay them to all participants), the parameters hash serves as a + convenient way to perform an out-of-band comparison of all host public keys. + It is a collision-resistant cryptographic hash of the `SessionParams` tuple. + As a result, if all participants have obtained an identical parameters hash + (as can be verified out-of-band), then they all agree on all host public + keys and the threshold `t`, and in particular, all participants have + obtained authentic public host keys. + + Returns: + bytes: The parameters hash, a 32-byte string. + + Raises: + InvalidHostPubkeyError: If `hostpubkeys` contains an invalid public key. + DuplicateHostPubkeyError: If `hostpubkeys` contains duplicates. + ThresholdOrCountError: If `1 <= t <= len(hostpubkeys) <= 2**32 - 1` does + not hold. + """ + params_validate(params) + hostpubkeys, t = params + + t_bytes = t.to_bytes(4, byteorder="big") + params_hash = tagged_hash_bip_dkg( + "params_hash", + t_bytes + b"".join(hostpubkeys), + ) + assert len(params_hash) == 32 + return params_hash + + +class SessionParamsError(ValueError): + """Base exception for invalid `SessionParams` tuples.""" + + +class DuplicateHostPubkeyError(SessionParamsError): + """Raised if two participants have identical host public keys. + + This exception is raised when two participants have an identical host public + key in the `SessionParams` tuple. Assuming the host public keys in question + have been transmitted correctly, this exception implies that at least one of + the two participants is faulty (because duplicates occur only with + negligible probability if keys are generated honestly). + + Attributes: + participant_id1 (int): Identifier of the first participant. + participant_id2 (int): Identifier of the second participant. + """ + + def __init__(self, participant_id1: int, participant_id2: int, *args: Any): + self.participant_id1 = participant_id1 + self.participant_id2 = participant_id2 + super().__init__(participant_id1, participant_id2, *args) + + +class InvalidHostPubkeyError(SessionParamsError): + """Raised if a host public key is invalid. + + This exception is raised when a host public key in the `SessionParams` tuple + is not a valid public key in compressed serialization. Assuming the host + public keys in question has been transmitted correctly, this exception + implies that the corresponding participant is faulty. + + Attributes: + participant_id (int): Identifier of the participant. + """ + + def __init__(self, participant_id: int, *args: Any): + self.participant_id = participant_id + super().__init__(participant_id, *args) + + +class ThresholdOrCountError(SessionParamsError): + """Raised if `1 <= t <= len(hostpubkeys) <= 2**32 - 1` does not hold.""" + + +# This is really the same definition as in simplpedpop and encpedpop. We repeat +# it here only to have its docstring in this module. +class DKGOutput(NamedTuple): + """Holds the outputs of a DKG session. + + Attributes: + secshare: Secret share of the participant (32 bytes, or `None` for + coordinator). + thresh_pk: Generated threshold public key representing the group + (33 bytes, in compressed serialization). + pubshares: Public shares of the participants (33 bytes each, in + compressed serialization). + """ + + secshare: bytes | None + thresh_pk: bytes + pubshares: list[bytes] + + +RecoveryData = NewType("RecoveryData", bytes) + + +### +### Messages +### + + +class ParticipantMsg1(NamedTuple): + enc_pmsg: encpedpop.ParticipantMsg + + @staticmethod + def len_bytes(*, t: int, n: int) -> int: + return encpedpop.ParticipantMsg.len_bytes(t=t, n=n) + + @staticmethod + def from_bytes(b: bytes, *, t: int, n: int) -> ParticipantMsg1: + if len(b) != ParticipantMsg1.len_bytes(t=t, n=n): + raise ValueError + enc_pmsg = encpedpop.ParticipantMsg.from_bytes( + b, t=t, n=n + ) # MsgParseError if invalid + return ParticipantMsg1(enc_pmsg) + + def to_bytes(self) -> bytes: + return self.enc_pmsg.to_bytes() + + +class ParticipantMsg2(NamedTuple): + sig: bytes + + @staticmethod + def len_bytes() -> int: + return 64 + + @staticmethod + def from_bytes(b: bytes) -> ParticipantMsg2: + if len(b) != ParticipantMsg2.len_bytes(): + raise ValueError + return ParticipantMsg2(b) + + def to_bytes(self) -> bytes: + return self.sig + + +class CoordinatorMsg1(NamedTuple): + enc_cmsg: encpedpop.CoordinatorMsg + enc_secshares: list[Scalar] + + @staticmethod + def len_bytes(*, t: int, n: int) -> int: + return encpedpop.CoordinatorMsg.len_bytes(t=t, n=n) + 32 * n + + @staticmethod + def from_bytes(b: bytes, *, t: int, n: int) -> CoordinatorMsg1: + if len(b) != CoordinatorMsg1.len_bytes(t=t, n=n): + raise ValueError + + # Read enc_cmsg + enc_cmsg_len = encpedpop.CoordinatorMsg.len_bytes(t=t, n=n) + enc_cmsg, rest = ( + encpedpop.CoordinatorMsg.from_bytes(b[:enc_cmsg_len], t=t, n=n), + b[enc_cmsg_len:], + ) # MsgParseError if invalid + + # Read enc_secshares (32*n bytes) + try: + enc_secshares = [ + Scalar.from_bytes_checked(rest[i : i + 32]) # ValueError if overflow + for i in range(0, 32 * n, 32) + ] + except ValueError as e: + raise MsgParseError("invalid encrypted secret shares") from e + + return CoordinatorMsg1(enc_cmsg, enc_secshares) + + def to_bytes(self) -> bytes: + return self.enc_cmsg.to_bytes() + b"".join( + share.to_bytes() for share in self.enc_secshares + ) + + +class CoordinatorMsg2(NamedTuple): + cert: bytes + + @staticmethod + def len_bytes(*, n: int) -> int: + return certeq_cert_len(n) + + @staticmethod + def from_bytes(b: bytes, *, n: int) -> CoordinatorMsg2: + if len(b) != CoordinatorMsg2.len_bytes(n=n): + raise ValueError + return CoordinatorMsg2(b) + + def to_bytes(self) -> bytes: + return self.cert + + +class CoordinatorInvestigationMsg(NamedTuple): + enc_cinv: encpedpop.CoordinatorInvestigationMsg + + @staticmethod + def len_bytes(*, n: int) -> int: + return encpedpop.CoordinatorInvestigationMsg.len_bytes(n=n) + + @staticmethod + def from_bytes(b: bytes, *, n: int) -> CoordinatorInvestigationMsg: + if len(b) != CoordinatorInvestigationMsg.len_bytes(n=n): + raise ValueError + enc_cinv = encpedpop.CoordinatorInvestigationMsg.from_bytes( + b, n=n + ) # MsgParseError if invalid + return CoordinatorInvestigationMsg(enc_cinv) + + def to_bytes(self) -> bytes: + return self.enc_cinv.to_bytes() + + +class RecoveryAckMsg(NamedTuple): + sig: bytes + + @staticmethod + def len_bytes() -> int: + return 64 + + @staticmethod + def from_bytes(b: bytes) -> RecoveryAckMsg: + if len(b) != RecoveryAckMsg.len_bytes(): + raise ValueError + return RecoveryAckMsg(b) + + def to_bytes(self) -> bytes: + return self.sig + + +def deserialize_recovery_data( + b: bytes, +) -> tuple[int, VSSCommitment, list[bytes], list[bytes], list[Scalar], bytes]: + rest = b + + # Read t (4 bytes) + if len(rest) < 4: + raise ValueError + t, rest = int.from_bytes(rest[:4], byteorder="big"), rest[4:] + + # Read sum_coms (33*t bytes) + if len(rest) < 33 * t: + raise ValueError + sum_coms, rest = ( + VSSCommitment.from_bytes(rest[: 33 * t], t=t), + rest[33 * t :], + ) + + # Compute n + n, remainder = divmod(len(rest), (33 + 33 + 32 + 64)) + if remainder != 0: + raise ValueError + + # Read hostpubkeys (33*n bytes) + assert len(rest) >= 33 * n + hostpubkeys, rest = [rest[i : i + 33] for i in range(0, 33 * n, 33)], rest[33 * n :] + + # Read pubnonces (33*n bytes) + assert len(rest) >= 33 * n + pubnonces, rest = [rest[i : i + 33] for i in range(0, 33 * n, 33)], rest[33 * n :] + + # Read enc_secshares (32*n bytes) + assert len(rest) >= 32 * n + enc_secshares, rest = ( + [Scalar.from_bytes_checked(rest[i : i + 32]) for i in range(0, 32 * n, 32)], + rest[32 * n :], + ) + + # Read cert (64*n bytes) + assert len(rest) >= 64 * n + cert, rest = rest[: 64 * n], rest[64 * n :] + + assert len(rest) == 0 + return (t, sum_coms, hostpubkeys, pubnonces, enc_secshares, cert) + + +### +### Participant +### + + +class ParticipantState1(NamedTuple): + params: SessionParams + participant_id: int + enc_state: encpedpop.ParticipantState + + +class ParticipantState2(NamedTuple): + params: SessionParams + eq_input: bytes + dkg_output: DKGOutput + + +def participant_step1( + hostseckey: bytes, params: SessionParams, random: bytes +) -> tuple[ParticipantState1, bytes]: + """Perform a participant's first step of a ChillDKG session. + + Arguments: + hostseckey: Participant's long-term host secret key (32 bytes). + params: Common session parameters. + random: FRESH random byte string (32 bytes). + + Returns: + ParticipantState1: The participant's session state after this step, to + be passed as an argument to `participant_step2`. The state **must + not** be reused (i.e., it must be passed only to one + `participant_step2` call). + bytes: The first message to be sent to the coordinator + (`33*t + 32*n + 97` bytes). + + Raises: + HostSeckeyError: If the host secret key is invalid, or if the key does + not match any entry of `hostpubkeys`. + InvalidHostPubkeyError: If `hostpubkeys` contains an invalid public key. + DuplicateHostPubkeyError: If `hostpubkeys` contains duplicates. + ThresholdOrCountError: If `1 <= t <= len(hostpubkeys) <= 2**32 - 1` does + not hold. + RandomnessError: If `random` is all zeroes (i.e., `b"\\x00" * 32`). This + check guards against the case of a malfunctioning random number + generator. + """ + hostpubkey = hostpubkey_gen(hostseckey) # ValueError if len(hostseckey) != 32 + + params_validate(params) + (hostpubkeys, t) = params + + try: + participant_id = hostpubkeys.index(hostpubkey) + except ValueError as e: + raise HostSeckeyError( + "Host secret key does not match any host public key" + ) from e + if len(random) != 32: + raise ValueError + if random == b"\x00" * 32: + raise RandomnessError + + enc_state, enc_pmsg = encpedpop.participant_step1( + # We know that EncPedPop uses its seed only by feeding it to a hash + # function. Thus, it is sufficient that the seed has a high entropy, + # and so we can simply pass the hostseckey as seed. + seed=hostseckey, + deckey=hostseckey, + t=t, + # This requires the joint security of Schnorr signatures and ECDH. + enckeys=hostpubkeys, + participant_id=participant_id, + random=random, + ) + + state1 = ParticipantState1(params, participant_id, enc_state) + pmsg1 = enc_pmsg + return state1, pmsg1 + + +class RandomnessError(ValueError): + """Raised if the randomness is all zeroes (i.e., `b"\\x00" * 32`).""" + + +def participant_step2( + hostseckey: bytes, + state1: ParticipantState1, + cmsg1: bytes, + aux_rand: bytes, +) -> tuple[ParticipantState2, bytes]: + """Perform a participant's second step of a ChillDKG session. + + **Warning:** + After sending the returned message to the coordinator, the caller **must + not** erase the hostseckey, even if the coordinator reply needed for the + `participant_finalize` call is not received. The underlying reason is that + some other participant may receive the coordinator reply, deem the DKG + session successful and use the resulting threshold public key (e.g., by + sending funds to it). If the coordinator reply remains missing, that other + participant can, at any point in the future, convince this participant of + the success of the DKG session by presenting recovery data, from which this + participant can recover the DKG output using the `participant_recover` + function. + + Arguments: + hostseckey: Participant's long-term host secret key (32 bytes). + state1: The participant's session state as output by + `participant_step1`. + cmsg1: The first message received from the coordinator + (`162*n + 33*(t-1)` bytes). + aux_rand: Auxiliary randomness (32 bytes). FRESH 32-byte randomness + is optimal, but 16 random bytes or a counter padded to 32 bytes + is acceptable (see BIP 340). + + Returns: + ParticipantState2: The participant's session state after this step, to + be passed as an argument to `participant_finalize`. The state **must + not** be reused (i.e., it must be passed only to one + `participant_finalize` call). + bytes: The second message to be sent to the coordinator (64 bytes). + + Raises: + HostSeckeyError: If the host secret key is invalid or if it does not + match the one used in `participant_step1`. + FaultyCoordinatorError: If the coordinator is faulty. See the + documentation of the exception for further details. + FaultyParticipantOrCoordinatorError: If another known participant or the + coordinator is faulty. See the documentation of the exception for + further details. + UnknownFaultyParticipantOrCoordinatorError: If another unknown + participant or the coordinator is faulty, but running the optional + investigation procedure of the protocol is necessary to determine a + suspected participant. See the documentation of the exception for + further details. + """ + hostpubkey = hostpubkey_gen( + hostseckey + ) # HostSeckeyError if invalid or ValueError if len(hostseckey) != 32 + if len(aux_rand) != 32: + raise ValueError + + params, participant_id, enc_state = state1 + if hostpubkey != params.hostpubkeys[participant_id]: + raise HostSeckeyError( + "Host secret key does not match the one used in participant_step1" + ) + t = enc_state.simpl_state.t + try: + cmsg1_parsed = CoordinatorMsg1.from_bytes(cmsg1, t=t, n=len(params.hostpubkeys)) + except MsgParseError as e: + raise FaultyCoordinatorError(*e.args) from e + enc_cmsg, enc_secshares = cmsg1_parsed + + enc_dkg_output, eq_input = encpedpop.participant_step2( + state=enc_state, + deckey=hostseckey, + cmsg=enc_cmsg.to_bytes(), + enc_secshare=enc_secshares[participant_id], + ) + + # Include the enc_shares in eq_input to ensure that participants agree on + # all shares, which in turn ensures that they have the right recovery data. + eq_input += b"".join([bytes_from_int(int(share)) for share in enc_secshares]) + dkg_output = DKGOutput._make(enc_dkg_output) + state2 = ParticipantState2(params, eq_input, dkg_output) + sig = certeq_participant_step(hostseckey, participant_id, eq_input, aux_rand) + pmsg2 = ParticipantMsg2(sig).to_bytes() + return state2, pmsg2 + + +def participant_finalize( + state2: ParticipantState2, cmsg2: bytes +) -> tuple[DKGOutput, RecoveryData]: + """Perform a participant's final step of a ChillDKG session. + + If this function returns properly (without an exception), then this + participant deems the DKG session successful. It is, however, possible that + other participants have received a `cmsg2` from the coordinator that made + them raise an exception instead, or that they have not received a `cmsg2` + from the coordinator at all. These participants can, at any point in time in + the future (e.g., when initiating a signing session), be convinced to deem + the session successful by presenting the recovery data to them, from which + they can recover the DKG outputs using the `participant_recover` function. + + Since returning successfully does not imply that other participants deem + the DKG session successful, returning successfully also does not imply + that redundant copies of the recovery data exist. For example, it could + be the case that other participants raised an exception instead, and this + participant will be the only one that obtained the recovery data. In that + case, if this participant's storage fails, the only copy of the recovery + data is lost. As a result, this participant will not be able to convince + any other participants to deem the DKG session successful, and it will + not be possible to create a signature. + + To protect against this scenario, callers **should** ensure that all + participants deem the DKG session successful (which also implies that + they have a redundant copy of the recovery data) before using the + threshold public key (e.g., before sending funds to it). The recommended + way of doing so is by collecting acknowledgment signatures via + `participant_recovery_ack_sign`. Callers can alternatively employ some + other means to ensure that they will always have access to the recovery + data (which can be used to convince other participants that the DKG + session was successful). For example, they could use a custom redundant + way of backing up the recovery data. + + **Warning:** + Changing perspectives, this implies that, even when obtaining an exception, + the caller **must not** conclude that the DKG session has failed, and as a + consequence, the caller **must not** erase the hostseckey. The underlying + reason is that some other participant may deem the DKG session successful + and use the resulting threshold public key (e.g., by sending funds to it). + That other participant can, at any point in the future, convince this + participant of the success of the DKG session by presenting recovery data to + this participant. + + Arguments: + state2: The participant's state as output by `participant_step2`. + cmsg2: The second message received from the coordinator + (`64*n` bytes). + + Returns: + DKGOutput: The DKG output. + bytes: The serialized recovery data. + + Raises: + FaultyCoordinatorError: If the coordinator is faulty. See the + documentation of the exception for further details. + """ + params, eq_input, dkg_output = state2 + cmsg2_parsed = CoordinatorMsg2.from_bytes(cmsg2, n=len(params.hostpubkeys)) + try: + certeq_verify(params.hostpubkeys, eq_input, cmsg2_parsed.cert) + except InvalidSignatureInCertificateError as e: + raise FaultyCoordinatorError( + "Coordinator has provided a certificate with an invalid signature" + ) from e + return dkg_output, RecoveryData(eq_input + cmsg2_parsed.cert) + + +def participant_investigate( + error: UnknownFaultyParticipantOrCoordinatorError, + cinv: bytes, +) -> NoReturn: + """Investigate who is to blame for a failed ChillDKG session. + + This function can optionally be called when `participant_step2` raises + `UnknownFaultyParticipantOrCoordinatorError`. It narrows down the suspected + faulty parties by analyzing the investigation message provided by the + coordinator. + + This function does not return normally. Instead, it raises one of two + exceptions. + + Arguments: + error: `UnknownFaultyParticipantOrCoordinatorError` raised by + `participant_step2`. + cinv: Coordinator investigation message for this participant as output + by `coordinator_investigate` (`65*n` bytes). + + Raises: + FaultyParticipantOrCoordinatorError: If another known participant or the + coordinator is faulty. See the documentation of the exception for + further details. + FaultyCoordinatorError: If the coordinator is faulty. See the + documentation of the exception for further details. + """ + assert isinstance(error.inv_data, encpedpop.ParticipantInvestigationData) + n = error.inv_data.simpl_bstate.n + try: + cinv_parsed = CoordinatorInvestigationMsg.from_bytes(cinv, n=n) + except MsgParseError as e: + raise FaultyCoordinatorError(*e.args) from e + encpedpop.participant_investigate( + error=error, + cinv=cinv_parsed.enc_cinv.to_bytes(), + ) + + +### +### Coordinator +### + + +class CoordinatorState(NamedTuple): + params: SessionParams + eq_input: bytes + dkg_output: DKGOutput + + +def coordinator_step1( + pmsgs1: list[bytes], params: SessionParams +) -> tuple[CoordinatorState, bytes]: + """Perform the coordinator's first step of a ChillDKG session. + + Arguments: + pmsgs1: List of first messages received from the participants + (`33*t + 32*n + 97` bytes each). The list's length must equal + the total number of participants. + params: Common session parameters. + + Returns: + CoordinatorState: The coordinator's session state after this step, to be + passed as an argument to `coordinator_finalize`. The state is not + supposed to be reused (i.e., it is supposed to be passed only to one + `coordinator_finalize` call). + bytes: The first message to be sent to all participants + (`162*n + 33*(t-1)` bytes). + + Raises: + InvalidHostPubkeyError: If `hostpubkeys` contains an invalid public key. + DuplicateHostPubkeyError: If `hostpubkeys` contains duplicates. + ThresholdOrCountError: If `1 <= t <= len(hostpubkeys) <= 2**32 - 1` does + not hold. + FaultyParticipantError: If a participant is faulty. See the + documentation of the exception for further details. + """ + params_validate(params) + hostpubkeys, t = params + if len(pmsgs1) != len(hostpubkeys): + raise ValueError + + pmsgs1_parsed = [] + for participant_id, pmsg1 in enumerate(pmsgs1): + try: + parsed = ParticipantMsg1.from_bytes(pmsg1, t=t, n=len(hostpubkeys)) + except MsgParseError as e: + raise FaultyParticipantError(participant_id, *e.args) from e + pmsgs1_parsed.append(parsed) + + enc_cmsg, enc_dkg_output, eq_input, enc_secshares = encpedpop.coordinator_step( + pmsgs=[pmsg1.enc_pmsg.to_bytes() for pmsg1 in pmsgs1_parsed], + t=t, + enckeys=hostpubkeys, + ) + enc_cmsg_parsed = encpedpop.CoordinatorMsg.from_bytes( + enc_cmsg, t=t, n=len(hostpubkeys) + ) + eq_input += b"".join([bytes_from_int(int(share)) for share in enc_secshares]) + dkg_output = DKGOutput._make(enc_dkg_output) # Convert to chilldkg.DKGOutput type + state = CoordinatorState(params, eq_input, dkg_output) + cmsg1 = CoordinatorMsg1(enc_cmsg_parsed, enc_secshares).to_bytes() + return state, cmsg1 + + +def coordinator_finalize( + state: CoordinatorState, pmsgs2: list[bytes] +) -> tuple[bytes, DKGOutput, RecoveryData]: + """Perform the coordinator's final step of a ChillDKG session. + + If this function returns properly (without an exception), then the + coordinator deems the DKG session successful. The returned `CoordinatorMsg2` + is supposed to be sent to all participants, who are supposed to pass it as + input to the `participant_finalize` function. It is, however, possible that + some participants pass a wrong and invalid message to `participant_finalize` + (e.g., because the message is transmitted incorrectly). These participants + can, at any point in time in the future (e.g., when initiating a signing + session), be convinced to deem the session successful by presenting the + recovery data to them, from which they can recover the DKG outputs using the + `participant_recover` function. + + If this function raises an exception, then the DKG session was not + successful from the perspective of the coordinator. In this case, it is, in + principle, possible to recover the DKG outputs of the coordinator using the + `coordinator_recover` function together with the recovery data from a + successful participant, should one exist. Any such successful participant + is either faulty, or has received messages from other participants via a + communication channel beside the coordinator. + + Arguments: + state: The coordinator's session state as output by `coordinator_step1`. + pmsgs2: List of second messages received from the participants + (64 bytes each). The list's length must equal the total number + of participants. + + Returns: + bytes: The second message to be sent to all participants (`64*n` bytes). + DKGOutput: The DKG output. Since the coordinator does not have a secret + share, the DKG output will have the `secshare` field set to `None`. + bytes: The serialized recovery data. + + Raises: + FaultyParticipantError: If a participant is faulty. See the + documentation of the exception for further details. + """ + params, eq_input, dkg_output = state + if len(pmsgs2) != len(params.hostpubkeys): + raise ValueError + + pmsgs2_parsed = [ParticipantMsg2.from_bytes(pmsg2) for pmsg2 in pmsgs2] + cert = certeq_coordinator_step([pmsg2.sig for pmsg2 in pmsgs2_parsed]) + try: + certeq_verify(params.hostpubkeys, eq_input, cert) + except InvalidSignatureInCertificateError as e: + raise FaultyParticipantError( + e.participant_id, + "Participant has provided an invalid signature for the certificate", + ) from e + cmsg2 = CoordinatorMsg2(cert).to_bytes() + return cmsg2, dkg_output, RecoveryData(eq_input + cert) + + +def coordinator_investigate(pmsgs: list[bytes], params: SessionParams) -> list[bytes]: + """Generate investigation messages for a ChillDKG session. + + The investigation messages will allow the participants to investigate who is + to blame for a failed ChillDKG session (see `participant_investigate`). + + Each message is intended for a single participant but can be safely + broadcast to all participants because the messages contain no confidential + information. + + Arguments: + pmsgs: List of serialized first messages received from the participants + (`33*t + 32*n + 97` bytes each). + params: Common session parameters. + + Returns: + List[bytes]: A list of investigation messages, each intended for a + single participant (`65*n` bytes each). + + Raises: + FaultyParticipantError: If a participant is faulty. See the + documentation of the exception for further details. + """ + n = len(pmsgs) + t = params.t + pmsgs_parsed = [] + for participant_id, pmsg in enumerate(pmsgs): + try: + parsed = ParticipantMsg1.from_bytes(pmsg, t=t, n=n) + except MsgParseError as e: + raise FaultyParticipantError(participant_id, *e.args) from e + pmsgs_parsed.append(parsed) + enc_cinvs = encpedpop.coordinator_investigate( + [pmsg.enc_pmsg.to_bytes() for pmsg in pmsgs_parsed], t + ) + return enc_cinvs + + +### +### Recovery +### + + +def recover( + hostseckey: bytes | None, recovery_data: RecoveryData +) -> tuple[DKGOutput, SessionParams]: + try: + (t, sum_coms, hostpubkeys, pubnonces, enc_secshares, cert) = ( + deserialize_recovery_data(recovery_data) + ) + except Exception as e: + raise RecoveryDataError("Failed to deserialize recovery data") from e + + n = len(hostpubkeys) + params = SessionParams(hostpubkeys, t) + try: + params_validate(params) + except SessionParamsError as e: + raise RecoveryDataError("Invalid session parameters in recovery data") from e + + # Verify cert + eq_input = recovery_data[: -len(cert)] + try: + certeq_verify(hostpubkeys, eq_input, cert) + except InvalidSignatureInCertificateError as e: + raise RecoveryDataError("Invalid certificate in recovery data") from e + + # Compute threshold pubkey and individual pubshares + sum_coms, tweak, _ = sum_coms.invalid_taproot_commit() + thresh_pk = sum_coms.commitment_to_secret() + pubshares = [sum_coms.pubshare(i) for i in range(n)] + + if hostseckey is not None: + hostpubkey = hostpubkey_gen(hostseckey) # ValueError or HostSeckeyError + try: + participant_id = hostpubkeys.index(hostpubkey) + except ValueError as e: + raise HostSeckeyError( + "Host secret key does not match any host public key in the recovery data" + ) from e + + # Decrypt share + enc_context = encpedpop.serialize_enc_context(t, hostpubkeys) + secshare = encpedpop.decrypt_sum( + hostseckey, + hostpubkeys[participant_id], + pubnonces, + enc_context, + participant_id, + enc_secshares[participant_id], + ) + secshare_tweaked = secshare + tweak + + # This is just a sanity check. Our signature is valid, so we have done + # an equivalent check already during the actual session. + assert VSSCommitment.verify_secshare( + secshare_tweaked, pubshares[participant_id] + ) + else: + secshare_tweaked = None + + dkg_output = DKGOutput( + None if secshare_tweaked is None else secshare_tweaked.to_bytes(), + thresh_pk.to_bytes_compressed(), + [pubshare.to_bytes_compressed() for pubshare in pubshares], + ) + return dkg_output, params + + +def participant_recover( + hostseckey: bytes, recovery_data: RecoveryData +) -> tuple[DKGOutput, SessionParams]: + """Recover the DKG output of a participant of a ChillDKG session. + + This function serves two different purposes: + 1. To recover from an exception in `participant_finalize`, after + obtaining the recovery data from another participant or the + coordinator. See `participant_finalize` for background. + 2. To reproduce the DKG outputs on a new device, e.g., to recover from a + backup after data loss. + + Arguments: + hostseckey: This participant's long-term host secret key (32 bytes). + recovery_data: Recovery data from a successful session. + + Returns: + DKGOutput: The recovered DKG output. + SessionParams: The common parameters of the recovered session. + + Raises: + HostSeckeyError: If the host secret key is invalid, or if the key does not + match the recovery data. + (This can also occur if the recovery data is invalid.) + RecoveryDataError: If recovery failed due to invalid recovery data. + """ + if hostseckey is None: + raise ValueError + return recover(hostseckey, recovery_data) + + +def coordinator_recover( + recovery_data: RecoveryData, +) -> tuple[DKGOutput, SessionParams]: + """Recover the DKG output of the coordinator of a ChillDKG session. + + This function serves two different purposes: + 1. To recover from an exception in `coordinator_finalize`, after + obtaining the recovery data from a participant. See + `coordinator_finalize` for background. + 2. To reproduce the DKG outputs on a new device, e.g., to recover from a + backup after data loss. + + Arguments: + recovery_data: Recovery data from a successful session. + + Returns: + DKGOutput: The recovered DKG output. Since the coordinator does not + have a secret share, the DKG output will have the `secshare` + field set to `None`. + SessionParams: The common parameters of the recovered session. + + Raises: + RecoveryDataError: If recovery failed due to invalid recovery data. + """ + return recover(None, recovery_data) + + +class RecoveryDataError(ValueError): + """Raised if the recovery data is invalid.""" + + +### +### Recovery acknowledgment +### + + +def participant_recovery_ack_sign( + hostseckey: bytes, + recovery_data: RecoveryData, + params: SessionParams, + aux_rand: bytes, +) -> bytes: + """Sign recovery data to create a recovery acknowledgment. + + This function allows a participant to create an explicit acknowledgment + signature on the recovery data. This can be used for an optional + acknowledgment round where participants acknowledge that they have + successfully received the complete recovery data. + + Arguments: + hostseckey: Participant's long-term host secret key (32 bytes). + recovery_data: Recovery data from a successful session. + params: Common session parameters. + aux_rand: Auxiliary randomness (32 bytes). FRESH 32-byte randomness + is optimal, but 16 random bytes or a counter padded to 32 bytes + is acceptable (see BIP 340). + + Returns: + bytes: Acknowledgment signature (64 bytes). + + Raises: + HostSeckeyError: If the host secret key is invalid, or if it does not + match any host public key. + InvalidHostPubkeyError: If `hostpubkeys` contains an invalid public key. + DuplicateHostPubkeyError: If `hostpubkeys` contains duplicates. + ThresholdOrCountError: If `1 <= t <= len(hostpubkeys) <= 2**32 - 1` does + not hold. + RecoveryDataError: If the recovery data is invalid or does not match + the provided parameters. + """ + hostpubkey = hostpubkey_gen(hostseckey) # ValueError if len(hostseckey) != 32 + + params_validate(params) + (hostpubkeys, t) = params + + try: + participant_id = hostpubkeys.index(hostpubkey) + except ValueError as e: + raise HostSeckeyError( + "Host secret key does not match any host public key" + ) from e + if len(aux_rand) != 32: + raise ValueError + + try: + (t_rec, _, hostpubkeys_rec, _, _, _) = deserialize_recovery_data(recovery_data) + except Exception as e: + raise RecoveryDataError("Failed to deserialize recovery data") from e + + if t_rec != t or hostpubkeys_rec != hostpubkeys: + raise RecoveryDataError( + "Recovery data does not match the provided session parameters" + ) + + sig = recovery_ack_sign(hostseckey, participant_id, recovery_data, aux_rand) + rmsg = RecoveryAckMsg(sig).to_bytes() + return rmsg + + +def participant_recovery_acks_verify( + recovery_data: RecoveryData, params: SessionParams, ack_sigs: list[bytes] +) -> None: + """Verify recovery acknowledgment signatures from all participants. + + This function is used to ensure that all participants have + received the recovery data before the threshold public key is used + (e.g., before funds are sent to it). + + Arguments: + recovery_data: Recovery data from a successful session. + params: Common session parameters. + ack_sigs: List of acknowledgment signatures (64 bytes each) + from all participants, in the same order as `hostpubkeys`. + + Raises: + InvalidHostPubkeyError: If `hostpubkeys` contains an invalid public key. + DuplicateHostPubkeyError: If `hostpubkeys` contains duplicates. + ThresholdOrCountError: If `1 <= t <= len(hostpubkeys) <= 2**32 - 1` does + not hold. + RecoveryDataError: If the recovery data is invalid or does not match + the provided parameters. + InvalidRecoveryAckError: If any recovery acknowledgment signature is + invalid. Note that this does NOT mean the DKG failed + (reaching this point implies the DKG itself was successful). + It only means it cannot be confirmed that all participants + have a copy of the recovery data. + """ + params_validate(params) + (hostpubkeys, t) = params + + if len(ack_sigs) != len(hostpubkeys): + raise ValueError + + try: + (t_rec, _, hostpubkeys_rec, _, _, _) = deserialize_recovery_data(recovery_data) + except Exception as e: + raise RecoveryDataError("Failed to deserialize recovery data") from e + + if t_rec != t or hostpubkeys_rec != hostpubkeys: + raise RecoveryDataError( + "Recovery data does not match the provided session parameters" + ) + + for i, sig in enumerate(ack_sigs): + rmsg = RecoveryAckMsg.from_bytes(sig) + msg = recovery_ack_message(recovery_data, i) + valid = schnorr_verify( + msg, + # Dropping the sign byte from hostpubkeys[i] is okay because msg + # commits on the full hostpubkeys[i]: it encodes all hostpubkeys + # together with the id i. + hostpubkeys[i][1:33], + rmsg.sig, + ) + if not valid: + raise InvalidRecoveryAckError(i) + + +class InvalidRecoveryAckError(FaultyParticipantError): + """Raised if a recovery acknowledgment signature is invalid. + + Attributes: + participant_id (int): Identifier of the participant whose signature is + invalid. + """ + + def __init__(self, participant_id: int, *args: Any): + self.participant_id = participant_id + super().__init__(participant_id, *args) diff --git a/bip-chilldkg/python/chilldkg_ref/encpedpop.py b/bip-chilldkg/python/chilldkg_ref/encpedpop.py new file mode 100644 index 0000000000..e07e1d5693 --- /dev/null +++ b/bip-chilldkg/python/chilldkg_ref/encpedpop.py @@ -0,0 +1,470 @@ +from __future__ import annotations + +from typing import NamedTuple, NoReturn + +from secp256k1lab.ecdh import ecdh_libsecp256k1 +from secp256k1lab.keys import pubkey_gen_plain +from secp256k1lab.secp256k1 import GE, Scalar + +from . import simplpedpop +from .util import ( + FaultyCoordinatorError, + FaultyParticipantError, + FaultyParticipantOrCoordinatorError, + MsgParseError, + UnknownFaultyParticipantOrCoordinatorError, + tagged_hash_bip_dkg, +) + +### +### Encryption +### + + +def ecdh( + seckey: bytes, my_pubkey: bytes, their_pubkey: bytes, context: bytes, sending: bool +) -> Scalar: + data = ecdh_libsecp256k1(seckey, their_pubkey) + if sending: + data += my_pubkey + their_pubkey + else: + data += their_pubkey + my_pubkey + assert len(data) == 32 + 2 * 33 + data += context + ret: Scalar = Scalar.from_bytes_wrapping( + tagged_hash_bip_dkg("encpedpop ecdh", data) + ) + return ret + + +def self_pad(symkey: bytes, nonce: bytes, context: bytes) -> Scalar: + # Pad for symmetric encryption to ourselves + pad: Scalar = Scalar.from_bytes_wrapping( + tagged_hash_bip_dkg("encaps_multi self_pad", symkey + nonce + context) + ) + return pad + + +def encaps_multi( + secnonce: bytes, + pubnonce: bytes, + deckey: bytes, + enckeys: list[bytes], + context: bytes, + participant_id: int, +) -> list[Scalar]: + # This is effectively the "Hashed ElGamal" multi-recipient KEM described in + # Section 5 of "Multi-recipient encryption, revisited" by Alexandre Pinto, + # Bertram Poettering, Jacob C. N. Schuldt (AsiaCCS 2014). Its crucial + # feature is to feed the index of the enckey to the hash function. The only + # difference is that we feed also the pubnonce and context data into the + # hash function. + pads = [] + for i, enckey in enumerate(enckeys): + context_ = i.to_bytes(4, byteorder="big") + context + if i == participant_id: + # We're encrypting to ourselves, so we use a symmetrically derived + # pad to save the ECDH computation. + pad = self_pad(symkey=deckey, nonce=pubnonce, context=context_) + else: + pad = ecdh( + seckey=secnonce, + my_pubkey=pubnonce, + their_pubkey=enckey, + context=context_, + sending=True, + ) + pads.append(pad) + return pads + + +def encrypt_multi( + secnonce: bytes, + pubnonce: bytes, + deckey: bytes, + enckeys: list[bytes], + context: bytes, + participant_id: int, + plaintexts: list[Scalar], +) -> list[Scalar]: + pads = encaps_multi(secnonce, pubnonce, deckey, enckeys, context, participant_id) + if len(plaintexts) != len(pads): + raise ValueError + ciphertexts = [plaintext + pad for plaintext, pad in zip(plaintexts, pads)] + return ciphertexts + + +def decaps_multi( + deckey: bytes, + enckey: bytes, + pubnonces: list[bytes], + context: bytes, + participant_id: int, +) -> list[Scalar]: + context_ = participant_id.to_bytes(4, byteorder="big") + context + pads = [] + for sender_id, pubnonce in enumerate(pubnonces): + if sender_id == participant_id: + pad = self_pad(symkey=deckey, nonce=pubnonce, context=context_) + else: + try: + pad = ecdh( + seckey=deckey, + my_pubkey=enckey, + their_pubkey=pubnonce, + context=context_, + sending=False, + ) + except ValueError as e: + # Since deckey and enckey are well-formed, the error must have been + # triggered by an invalid pubnonce. + raise FaultyParticipantOrCoordinatorError( + sender_id, "invalid public nonce" + ) from e + pads.append(pad) + return pads + + +def decrypt_sum( + deckey: bytes, + enckey: bytes, + pubnonces: list[bytes], + context: bytes, + participant_id: int, + sum_ciphertexts: Scalar, +) -> Scalar: + if participant_id >= len(pubnonces): + raise IndexError + pads = decaps_multi(deckey, enckey, pubnonces, context, participant_id) + sum_plaintexts: Scalar = sum_ciphertexts - Scalar.sum(*pads) + return sum_plaintexts + + +### +### Messages +### + + +class ParticipantMsg(NamedTuple): + simpl_pmsg: simplpedpop.ParticipantMsg + pubnonce: bytes + enc_shares: list[Scalar] + + @staticmethod + def len_bytes(*, t: int, n: int) -> int: + return simplpedpop.ParticipantMsg.len_bytes(t=t) + 33 + 32 * n + + @staticmethod + def from_bytes(b: bytes, *, t: int, n: int) -> ParticipantMsg: + if len(b) != ParticipantMsg.len_bytes(t=t, n=n): + raise ValueError + + # Read simpl_pmsg + simpl_pmsg_len = simplpedpop.ParticipantMsg.len_bytes(t=t) + simpl_pmsg, rest = ( + simplpedpop.ParticipantMsg.from_bytes(b[:simpl_pmsg_len], t=t), + b[simpl_pmsg_len:], + ) # MsgParseError if invalid + + # Read pubnonce (33 bytes) + pubnonce, rest = rest[:33], rest[33:] + + # Read enc_secshares (32*n bytes) + try: + enc_secshares = [ + Scalar.from_bytes_checked(rest[i : i + 32]) # ValueError if overflow + for i in range(0, 32 * n, 32) + ] + except ValueError as e: + raise MsgParseError("invalid encrypted secret share") from e + + return ParticipantMsg(simpl_pmsg, pubnonce, enc_secshares) + + def to_bytes(self) -> bytes: + return ( + self.simpl_pmsg.to_bytes() + + self.pubnonce + + b"".join(share.to_bytes() for share in self.enc_shares) + ) + + +class CoordinatorMsg(NamedTuple): + simpl_cmsg: simplpedpop.CoordinatorMsg + pubnonces: list[bytes] + + @staticmethod + def len_bytes(*, t: int, n: int) -> int: + return simplpedpop.CoordinatorMsg.len_bytes(t=t, n=n) + 33 * n + + @staticmethod + def from_bytes(b: bytes, *, t: int, n: int) -> CoordinatorMsg: + if len(b) != CoordinatorMsg.len_bytes(t=t, n=n): + raise ValueError + + # Read simpl_cmsg + simpl_cmsg_len = simplpedpop.CoordinatorMsg.len_bytes(t=t, n=n) + simpl_cmsg, rest = ( + simplpedpop.CoordinatorMsg.from_bytes(b[:simpl_cmsg_len], t=t, n=n), + b[simpl_cmsg_len:], + ) # MsgParseError if invalid + + # Read pubnonces (33*n bytes) + pubnonces = [rest[i : i + 33] for i in range(0, 33 * n, 33)] + + return CoordinatorMsg(simpl_cmsg, pubnonces) + + def to_bytes(self) -> bytes: + return self.simpl_cmsg.to_bytes() + b"".join(self.pubnonces) + + +class CoordinatorInvestigationMsg(NamedTuple): + enc_partial_secshares: list[Scalar] + partial_pubshares: list[GE] + + @staticmethod + def len_bytes(*, n: int) -> int: + return simplpedpop.CoordinatorInvestigationMsg.len_bytes(n=n) + 32 * n + + @staticmethod + def from_bytes(b: bytes, *, n: int) -> CoordinatorInvestigationMsg: + if len(b) != CoordinatorInvestigationMsg.len_bytes(n=n): + raise ValueError + + # Read enc_partial_secshares (32*n bytes) + try: + enc_partial_secshares, rest = ( + [ + Scalar.from_bytes_checked(b[i : i + 32]) + for i in range(0, 32 * n, 32) + ], # ValueError if overflow + b[32 * n :], + ) + except ValueError as e: + raise MsgParseError("invalid encrypted partial secshare") from e + + # Read partial_pubshares (33*n bytes) + try: + partial_pubshares = [ + GE.from_bytes_compressed_with_infinity(rest[i : i + 33]) + for i in range(0, 33 * n, 33) + ] + except ValueError as e: + raise MsgParseError("invalid partial pubshare") from e + + return CoordinatorInvestigationMsg(enc_partial_secshares, partial_pubshares) + + def to_bytes(self) -> bytes: + secshares_bytes = b"".join( + share.to_bytes() for share in self.enc_partial_secshares + ) + pubshares_bytes = b"".join( + P.to_bytes_compressed_with_infinity() for P in self.partial_pubshares + ) + return secshares_bytes + pubshares_bytes + + +### +### Participant +### + + +class ParticipantState(NamedTuple): + simpl_state: simplpedpop.ParticipantState + pubnonce: bytes + enckeys: list[bytes] + participant_id: int + + +class ParticipantInvestigationData(NamedTuple): + simpl_bstate: simplpedpop.ParticipantInvestigationData + enc_secshare: Scalar + pads: list[Scalar] + + +def serialize_enc_context(t: int, enckeys: list[bytes]) -> bytes: + return t.to_bytes(4, byteorder="big") + b"".join(enckeys) + + +def participant_step1( + seed: bytes, + deckey: bytes, + enckeys: list[bytes], + t: int, + participant_id: int, + random: bytes, +) -> tuple[ParticipantState, bytes]: + if t >= 2 ** (4 * 8): + raise ValueError + if len(random) != 32: + raise ValueError + n = len(enckeys) + + # Derive an encryption nonce and a seed for SimplPedPop. + # + # SimplPedPop will use its seed to derive the secret shares, which we will + # encrypt using the encryption nonce. That means that all entropy used in + # the derivation of simpl_seed should also be in the derivation of the + # pubnonce, to ensure that we never encrypt different secret shares with the + # same encryption pads. The foolproof way to achieve this is to simply + # derive the nonce from simpl_seed. + enc_context = serialize_enc_context(t, enckeys) + simpl_seed = tagged_hash_bip_dkg("encpedpop seed", seed + random + enc_context) + simpl_aux_rand = tagged_hash_bip_dkg("simplpedpop aux", simpl_seed) + secnonce = tagged_hash_bip_dkg("encpedpop secnonce", simpl_seed) + pubnonce = pubkey_gen_plain(secnonce) + + simpl_state, simpl_pmsg, shares = simplpedpop.participant_step1( + simpl_seed, t, n, participant_id, simpl_aux_rand + ) + assert len(shares) == n + + enc_shares = encrypt_multi( + secnonce, pubnonce, deckey, enckeys, enc_context, participant_id, shares + ) + simpl_pmsg_parsed = simplpedpop.ParticipantMsg.from_bytes(simpl_pmsg, t=t) + + pmsg = ParticipantMsg(simpl_pmsg_parsed, pubnonce, enc_shares).to_bytes() + state = ParticipantState(simpl_state, pubnonce, enckeys, participant_id) + return state, pmsg + + +def participant_step2( + state: ParticipantState, + deckey: bytes, + cmsg: bytes, + enc_secshare: Scalar, +) -> tuple[simplpedpop.DKGOutput, bytes]: + simpl_state, pubnonce, enckeys, participant_id = state + try: + cmsg_parsed = CoordinatorMsg.from_bytes(cmsg, t=simpl_state.t, n=len(enckeys)) + except MsgParseError as e: + raise FaultyCoordinatorError(*e.args) from e + simpl_cmsg, pubnonces = cmsg_parsed + + reported_pubnonce = pubnonces[participant_id] + if reported_pubnonce != pubnonce: + raise FaultyCoordinatorError("Coordinator replied with wrong pubnonce") + + enc_context = serialize_enc_context(simpl_state.t, enckeys) + pads = decaps_multi( + deckey, enckeys[participant_id], pubnonces, enc_context, participant_id + ) + secshare = enc_secshare - Scalar.sum(*pads) + + try: + dkg_output, eq_input = simplpedpop.participant_step2( + simpl_state, simpl_cmsg.to_bytes(), secshare + ) + except UnknownFaultyParticipantOrCoordinatorError as e: + assert isinstance(e.inv_data, simplpedpop.ParticipantInvestigationData) + # Translate simplpedpop.ParticipantInvestigationData into our own + # encpedpop.ParticipantInvestigationData. + inv_data = ParticipantInvestigationData(e.inv_data, enc_secshare, pads) + raise UnknownFaultyParticipantOrCoordinatorError(inv_data, *e.args) from e + + eq_input += b"".join(enckeys) + b"".join(pubnonces) + return dkg_output, eq_input + + +def participant_investigate( + error: UnknownFaultyParticipantOrCoordinatorError, + cinv: bytes, +) -> NoReturn: + simpl_inv_data, enc_secshare, pads = error.inv_data + try: + cinv_parsed = CoordinatorInvestigationMsg.from_bytes(cinv, n=simpl_inv_data.n) + except MsgParseError as e: + raise FaultyCoordinatorError(*e.args) from e + enc_partial_secshares, partial_pubshares = cinv_parsed + partial_secshares = [ + enc_partial_secshare - pad + for enc_partial_secshare, pad in zip(enc_partial_secshares, pads) + ] + + simpl_cinv = simplpedpop.CoordinatorInvestigationMsg(partial_pubshares) + try: + simplpedpop.participant_investigate( + UnknownFaultyParticipantOrCoordinatorError(simpl_inv_data), + simpl_cinv.to_bytes(), + partial_secshares, + ) + except simplpedpop.SecshareSumError as e: + # The secshare is not equal to the sum of the partial secshares in the + # investigation message. 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 secshares, which is the + # coordinator's fault. + assert Scalar.sum(*enc_partial_secshares) != enc_secshare + raise FaultyCoordinatorError( + "Sum of encrypted partial secshares not equal to encrypted secshare" + ) from e + + +### +### Coordinator +### + + +def coordinator_step( + pmsgs: list[bytes], + t: int, + enckeys: list[bytes], +) -> tuple[bytes, simplpedpop.DKGOutput, bytes, list[Scalar]]: + n = len(enckeys) + if n != len(pmsgs): + raise ValueError + + pmsgs_parsed = [] + for i, pmsg in enumerate(pmsgs): + try: + parsed = ParticipantMsg.from_bytes(pmsg, t=t, n=n) + except MsgParseError as e: + raise FaultyParticipantError(i, *e.args) from e + pmsgs_parsed.append(parsed) + simpl_cmsg, dkg_output, eq_input = simplpedpop.coordinator_step( + pmsgs=[pmsg.simpl_pmsg.to_bytes() for pmsg in pmsgs_parsed], t=t, n=n + ) + simpl_cmsg_parsed = simplpedpop.CoordinatorMsg.from_bytes(simpl_cmsg, t=t, n=n) + pubnonces = [pmsg.pubnonce for pmsg in pmsgs_parsed] + enc_secshares = [ + Scalar.sum(*([pmsg.enc_shares[i] for pmsg in pmsgs_parsed])) for i in range(n) + ] + eq_input += b"".join(enckeys) + b"".join(pubnonces) + + # In ChillDKG, the coordinator needs to broadcast the entire enc_secshares + # array to all participants. But in pure EncPedPop, the coordinator needs to + # send to each participant i only their entry enc_secshares[i]. + # + # Since broadcasting the entire array is not necessary, we don't include it + # in encpedpop.CoordinatorMsg, but only return it as a side output, so that + # chilldkg.coordinator_step can pick it up. Implementations of pure + # EncPedPop will need to decide how to transmit enc_secshares[i] to + # participant i for participant_step2(); we leave this unspecified. + return ( + CoordinatorMsg(simpl_cmsg_parsed, pubnonces).to_bytes(), + dkg_output, + eq_input, + enc_secshares, + ) + + +def coordinator_investigate(pmsgs: list[bytes], t: int) -> list[bytes]: + n = len(pmsgs) + pmsgs_parsed = [ParticipantMsg.from_bytes(pmsg, t=t, n=n) for pmsg in pmsgs] + simpl_pmsgs = [pmsg.simpl_pmsg.to_bytes() for pmsg in pmsgs_parsed] + + all_enc_partial_secshares = [ + [pmsg.enc_shares[i] for pmsg in pmsgs_parsed] for i in range(n) + ] + simpl_cinvs = simplpedpop.coordinator_investigate(simpl_pmsgs, t) + simpl_cinvs_parsed = [ + simplpedpop.CoordinatorInvestigationMsg.from_bytes(simpl_cinv, n=n) + for simpl_cinv in simpl_cinvs + ] + cinvs = [ + CoordinatorInvestigationMsg( + all_enc_partial_secshares[i], simpl_cinvs_parsed[i].partial_pubshares + ).to_bytes() + for i in range(n) + ] + return cinvs diff --git a/bip-chilldkg/python/chilldkg_ref/simplpedpop.py b/bip-chilldkg/python/chilldkg_ref/simplpedpop.py new file mode 100644 index 0000000000..4ad1b4fba8 --- /dev/null +++ b/bip-chilldkg/python/chilldkg_ref/simplpedpop.py @@ -0,0 +1,429 @@ +from __future__ import annotations + +from typing import NamedTuple, NewType, NoReturn + +from secp256k1lab.bip340 import schnorr_sign, schnorr_verify +from secp256k1lab.secp256k1 import GE, Scalar + +from .util import ( + BIP_TAG, + FaultyCoordinatorError, + FaultyParticipantError, + FaultyParticipantOrCoordinatorError, + MsgParseError, + UnknownFaultyParticipantOrCoordinatorError, +) +from .vss import VSS, VSSCommitment + +### +### Exceptions +### + + +class SecshareSumError(ValueError): + pass + + +### +### Proofs of possession (pops) +### + + +Pop = NewType("Pop", bytes) + +POP_MSG_TAG = BIP_TAG + "pop message" + + +def pop_msg(participant_id: int) -> bytes: + return participant_id.to_bytes(4, byteorder="big") + + +def pop_prove(seckey: bytes, participant_id: int, aux_rand: bytes) -> Pop: + sig = schnorr_sign( + pop_msg(participant_id), seckey, aux_rand=aux_rand, tag_prefix=POP_MSG_TAG + ) + return Pop(sig) + + +def pop_verify(pop: Pop, pubkey: bytes, participant_id: int) -> bool: + return schnorr_verify(pop_msg(participant_id), pubkey, pop, tag_prefix=POP_MSG_TAG) + + +### +### Messages +### + + +class ParticipantMsg(NamedTuple): + com: VSSCommitment + pop: Pop + + @staticmethod + def len_bytes(*, t: int) -> int: + return 33 * t + 64 + + @staticmethod + def from_bytes(b: bytes, *, t: int) -> ParticipantMsg: + if len(b) != ParticipantMsg.len_bytes(t=t): + raise ValueError + + # Read com (33*t bytes) + try: + com = VSSCommitment.from_bytes(b[: 33 * t], t=t) + except ValueError as e: + raise MsgParseError("invalid VSS commitment") from e + # Read pop (64 bytes) + pop = Pop(b[33 * t :]) + + return ParticipantMsg(com, pop) + + def to_bytes(self) -> bytes: + return self.com.to_bytes() + self.pop + + +class CoordinatorMsg(NamedTuple): + coms_to_secrets: list[GE] + sum_coms_to_nonconst_terms: list[GE] + pops: list[Pop] + + @staticmethod + def len_bytes(*, t: int, n: int) -> int: + return 97 * n + 33 * (t - 1) + + @staticmethod + def from_bytes(b: bytes, *, t: int, n: int) -> CoordinatorMsg: + if len(b) != CoordinatorMsg.len_bytes(t=t, n=n): + raise ValueError + + # Read coms_to_secrets (33*n bytes) + try: + coms_to_secrets, rest = ( + [ + GE.from_bytes_compressed_with_infinity(b[i : i + 33]) + for i in range(0, 33 * n, 33) + ], + b[33 * n :], + ) + except ValueError as e: + raise MsgParseError("invalid commitment to secret") from e + + # Read sum_coms_to_nonconst_terms (33*(t-1) bytes) + try: + sum_coms_to_nonconst_terms, rest = ( + [ + GE.from_bytes_compressed_with_infinity(rest[i : i + 33]) + for i in range(0, 33 * (t - 1), 33) + ], + rest[33 * (t - 1) :], + ) + except ValueError as e: + raise MsgParseError("invalid sum commitment to non-constant term") from e + + # Read pops (64*n bytes) + pops = [Pop(rest[i : i + 64]) for i in range(0, 64 * n, 64)] + + return CoordinatorMsg(coms_to_secrets, sum_coms_to_nonconst_terms, pops) + + def to_bytes(self) -> bytes: + return b"".join( + [ + P.to_bytes_compressed_with_infinity() + for P in self.coms_to_secrets + self.sum_coms_to_nonconst_terms + ] + ) + b"".join(self.pops) + + +class CoordinatorInvestigationMsg(NamedTuple): + partial_pubshares: list[GE] + + @staticmethod + def len_bytes(*, n: int) -> int: + return 33 * n + + @staticmethod + def from_bytes(b: bytes, *, n: int) -> CoordinatorInvestigationMsg: + if len(b) != CoordinatorInvestigationMsg.len_bytes(n=n): + raise ValueError + + # Read partial_pubshares (33*n bytes) + try: + partial_pubshares = [ + GE.from_bytes_compressed_with_infinity(b[i : i + 33]) + for i in range(0, 33 * n, 33) + ] + except ValueError as e: + raise MsgParseError("invalid partial pubshare") from e + + return CoordinatorInvestigationMsg(partial_pubshares) + + def to_bytes(self) -> bytes: + return b"".join( + [P.to_bytes_compressed_with_infinity() for P in self.partial_pubshares] + ) + + +### +### Other common definitions +### + + +class DKGOutput(NamedTuple): + secshare: bytes | None # None for coordinator + thresh_pk: bytes + pubshares: list[bytes] + + +def assemble_sum_coms( + coms_to_secrets: list[GE], sum_coms_to_nonconst_terms: list[GE] +) -> VSSCommitment: + # Sum the commitments to the secrets + return VSSCommitment( + [GE.sum(*(c for c in coms_to_secrets))] + sum_coms_to_nonconst_terms + ) + + +### +### Participant +### + + +class ParticipantState(NamedTuple): + t: int + n: int + participant_id: int + com_to_secret: GE + + +class ParticipantInvestigationData(NamedTuple): + n: int + participant_id: int + secshare: Scalar + pubshare: GE + + +# 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 participant_step1( + seed: bytes, t: int, n: int, participant_id: int, aux_rand: bytes +) -> tuple[ + ParticipantState, + bytes, + # The following return value is a list of n partial secret shares generated + # by this participant. The item at index i is supposed to be made available + # to participant i privately, e.g., via an external secure channel. See also + # the function participant_step2_prepare_secshare(). + list[Scalar], +]: + if t > n: + raise ValueError + if participant_id >= n: + raise IndexError + if len(seed) != 32: + raise ValueError + if len(aux_rand) != 32: + raise ValueError + + vss = VSS.generate(seed, t) # OverflowError if t >= 2**32 + partial_secshares_from_me = vss.secshares(n) + pop = pop_prove(vss.secret().to_bytes(), participant_id, aux_rand) + + com = vss.commit() + com_to_secret = com.commitment_to_secret() + msg = ParticipantMsg(com, pop).to_bytes() + state = ParticipantState(t, n, participant_id, com_to_secret) + return state, msg, partial_secshares_from_me + + +# Helper function to prepare the secshare for participant id's +# participant_step2() by summing the partial_secshares returned by all +# participants' participant_step1(). +# +# In a pure run of SimplPedPop where secret shares are sent via external secure +# channels (i.e., EncPedPop is not used), each participant needs to run this +# function in preparation of their participant_step2(). Since this computation +# involves secret data, it cannot be delegated to the coordinator as opposed to +# other aggregation steps. +# +# If EncPedPop is used instead (as a wrapper of SimplPedPop), the coordinator +# can securely aggregate the encrypted partial secshares into an encrypted +# secshare by exploiting the additively homomorphic property of the encryption. +def participant_step2_prepare_secshare( + partial_secshares: list[Scalar], +) -> Scalar: + secshare: Scalar # REVIEW Work around missing type annotation of Scalar.sum + secshare = Scalar.sum(*partial_secshares) + return secshare + + +def participant_step2( + state: ParticipantState, + cmsg: bytes, + secshare: Scalar, +) -> tuple[DKGOutput, bytes]: + t, n, participant_id, com_to_secret = state + try: + cmsg_parsed = CoordinatorMsg.from_bytes(cmsg, t=t, n=n) + except MsgParseError as e: + raise FaultyCoordinatorError(*e.args) from e + coms_to_secrets, sum_coms_to_nonconst_terms, pops = cmsg_parsed + + if coms_to_secrets[participant_id] != com_to_secret: + raise FaultyCoordinatorError( + "Coordinator sent unexpected first group element for local participant id" + ) + + for i in range(n): + if i == participant_id: + # No need to check our own pop. + continue + if coms_to_secrets[i].infinity: + raise FaultyParticipantOrCoordinatorError( + i, "Participant sent invalid commitment" + ) + # This can be optimized: We serialize the coms_to_secrets[i] here, but + # schnorr_verify (inside pop_verify) will need to deserialize it again, which + # involves computing a square root to obtain the y coordinate. + if not pop_verify(pops[i], coms_to_secrets[i].to_bytes_xonly(), i): + raise FaultyParticipantOrCoordinatorError( + i, "Participant sent invalid proof-of-knowledge" + ) + + sum_coms = assemble_sum_coms(coms_to_secrets, sum_coms_to_nonconst_terms) + # Verifying the tweaked secshare against the tweaked pubshare is equivalent + # to verifying the untweaked secshare against the untweaked pubshare, but + # avoids computing the untweaked pubshare in the happy path and thereby + # moves a group addition to the error path. + sum_coms_tweaked, tweak, pubtweak = sum_coms.invalid_taproot_commit() + pubshare_tweaked = sum_coms_tweaked.pubshare(participant_id) + secshare_tweaked = secshare + tweak + if not VSSCommitment.verify_secshare(secshare_tweaked, pubshare_tweaked): + pubshare = pubshare_tweaked - pubtweak + raise UnknownFaultyParticipantOrCoordinatorError( + ParticipantInvestigationData(n, participant_id, secshare, pubshare), + "Received invalid secshare; consider using " + "participant_investigate() to determine a faulty party", + ) + + thresh_pk = sum_coms_tweaked.commitment_to_secret() + pubshares = [ + sum_coms_tweaked.pubshare(i) + if i != participant_id + else pubshare_tweaked # We have computed our own pubshare already. + for i in range(n) + ] + dkg_output = DKGOutput( + secshare_tweaked.to_bytes(), + thresh_pk.to_bytes_compressed(), + [pubshare.to_bytes_compressed() for pubshare in pubshares], + ) + eq_input = t.to_bytes(4, byteorder="big") + sum_coms.to_bytes() + return dkg_output, eq_input + + +def participant_investigate( + error: UnknownFaultyParticipantOrCoordinatorError, + cinv: bytes, + partial_secshares: list[Scalar], +) -> NoReturn: + n, participant_id, secshare, pubshare = error.inv_data + if len(partial_secshares) != n: + raise ValueError + + try: + cinv_parsed = CoordinatorInvestigationMsg.from_bytes(cinv, n=n) + except MsgParseError as e: + raise FaultyCoordinatorError(*e.args) from e + partial_pubshares = cinv_parsed.partial_pubshares + + if GE.sum(*partial_pubshares) != pubshare: + raise FaultyCoordinatorError("Sum of partial pubshares not equal to pubshare") + + if Scalar.sum(*partial_secshares) != secshare: + raise SecshareSumError("Sum of partial secshares not equal to secshare") + + for i in range(n): + if not VSSCommitment.verify_secshare( + partial_secshares[i], partial_pubshares[i] + ): + if i != participant_id: + raise FaultyParticipantOrCoordinatorError( + i, "Participant sent invalid partial secshare" + ) + else: + # We are not faulty, so the coordinator must be. + raise FaultyCoordinatorError( + "Coordinator fiddled with the share from me to myself" + ) + + # We now know: + # - The sum of the partial secshares is equal to the secshare. + # - The sum of the partial pubshares is equal to the pubshare. + # - Every partial secshare matches its corresponding partial pubshare. + # Hence, the secshare matches the pubshare. + assert VSSCommitment.verify_secshare(secshare, pubshare) + + # This should never happen (unless the caller fiddled with the inputs). + raise RuntimeError( + "participant_investigate() was called, but all inputs are consistent." + ) + + +### +### Coordinator +### + + +def coordinator_step( + pmsgs: list[bytes], t: int, n: int +) -> tuple[bytes, DKGOutput, bytes]: + if len(pmsgs) != n: + raise ValueError + pmsgs_parsed = [] + for i, pmsg in enumerate(pmsgs): + try: + parsed = ParticipantMsg.from_bytes(pmsg, t=t) + except MsgParseError as e: + raise FaultyParticipantError(i, *e.args) from e + pmsgs_parsed.append(parsed) + # Sum the commitments to the i-th coefficients for i > 0 + # + # This procedure corresponds to the one described by Pedersen in Section 5.1 + # of "Non-Interactive and Information-Theoretic Secure Verifiable Secret + # Sharing". However, we don't sum the commitments to the secrets (i == 0) + # because they'll be necessary to check the pops. + coms_to_secrets = [pmsg.com.commitment_to_secret() for pmsg in pmsgs_parsed] + # But we can sum the commitments to the non-constant terms. + sum_coms_to_nonconst_terms = [ + GE.sum(*(pmsg.com.commitment_to_nonconst_terms()[j] for pmsg in pmsgs_parsed)) + for j in range(t - 1) + ] + pops = [pmsg.pop for pmsg in pmsgs_parsed] + cmsg = CoordinatorMsg(coms_to_secrets, sum_coms_to_nonconst_terms, pops).to_bytes() + + sum_coms = assemble_sum_coms(coms_to_secrets, sum_coms_to_nonconst_terms) + sum_coms_tweaked, _, _ = sum_coms.invalid_taproot_commit() + thresh_pk = sum_coms_tweaked.commitment_to_secret() + pubshares = [sum_coms_tweaked.pubshare(i) for i in range(n)] + + dkg_output = DKGOutput( + None, + thresh_pk.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 + + +def coordinator_investigate(pmsgs: list[bytes], t: int) -> list[bytes]: + n = len(pmsgs) + pmsgs_parsed = [ParticipantMsg.from_bytes(pmsg, t=t) for pmsg in pmsgs] + all_partial_pubshares = [ + [pmsg.com.pubshare(i) for pmsg in pmsgs_parsed] for i in range(n) + ] + return [ + CoordinatorInvestigationMsg(all_partial_pubshares[i]).to_bytes() + for i in range(n) + ] diff --git a/bip-chilldkg/python/chilldkg_ref/util.py b/bip-chilldkg/python/chilldkg_ref/util.py new file mode 100644 index 0000000000..dc283276c8 --- /dev/null +++ b/bip-chilldkg/python/chilldkg_ref/util.py @@ -0,0 +1,106 @@ +from typing import Any + +from secp256k1lab.util import tagged_hash + +BIP_TAG = "BIP DKG/" + + +def tagged_hash_bip_dkg(tag: str, msg: bytes) -> bytes: + return tagged_hash(BIP_TAG + tag, msg) + + +class ProtocolError(Exception): + """Base exception for errors caused by received protocol messages.""" + + +class FaultyParticipantError(ProtocolError): + """Raised if a participant is faulty. + + This exception is raised by the coordinator code when it detects faulty + behavior by a participant, i.e., a participant has deviated from the + protocol. The identifier of the participant is provided as part of the exception. + Assuming protocol messages have been transmitted correctly and the + coordinator itself is not faulty, this exception implies that the + participant is indeed faulty. + + This exception is raised only by the coordinator code. Some faulty behavior + by participants will be detected by the other participants instead. + See `FaultyParticipantOrCoordinatorError` for details. + + Attributes: + participant_id (int): Identifier of the faulty participant. + """ + + def __init__(self, participant_id: int, *args: Any): + self.participant_id = participant_id + super().__init__(participant_id, *args) + + +class FaultyParticipantOrCoordinatorError(ProtocolError): + """Raised if another known participant or the coordinator is faulty. + + This exception is raised by the participant code when it detects what looks + like faulty behavior by a suspected participant. The identifier of the suspected + participant is provided as part of the exception. + + Importantly, this exception is not proof that the suspected participant is + indeed faulty. It is instead possible that the coordinator has deviated from + the protocol in a way that makes it look as if the suspected participant has + deviated from the protocol. In other words, assuming messages have been + transmitted correctly and the raising participant is not faulty, this + exception implies that + - the suspected participant is faulty, + - *or* the coordinator is faulty (and has framed the suspected + participant). + + This exception is raised only by the participant code. Some faulty behavior + by participants will be detected by the coordinator instead. See + `FaultyParticipantError` for details. + + Attributes: + participant_id (int): Identifier of the suspected participant. + """ + + def __init__(self, participant_id: int, *args: Any): + self.participant_id = participant_id + super().__init__(participant_id, *args) + + +class FaultyCoordinatorError(ProtocolError): + """Raised if the coordinator is faulty. + + This exception is raised by the participant code when it detects faulty + behavior by the coordinator, i.e., the coordinator has deviated from the + protocol. Assuming protocol messages have been transmitted correctly and the + raising participant is not faulty, this exception implies that the + coordinator is indeed faulty. + """ + + +class UnknownFaultyParticipantOrCoordinatorError(ProtocolError): + """Raised if another unknown participant or the coordinator is faulty. + + This exception is raised by the participant code when it detects what looks + like faulty behavior by some other participant, but there is insufficient + information to determine which participant should be suspected. + + To determine a suspected participant, the raising participant may choose to + run the optional investigation procedure of the protocol, which requires + obtaining an investigation message from the coordinator. See the + `participant_investigate` function for details. + + This is only raised for specific faulty behavior by another participant + which cannot be attributed to another participant without further help of + the coordinator (namely, sending invalid encrypted secret shares). + + Attributes: + inv_data: Information required to perform the investigation. + """ + + def __init__(self, inv_data: Any, *args: Any): + self.inv_data = inv_data + super().__init__(*args) + + +class MsgParseError(ValueError): + pass diff --git a/bip-chilldkg/python/chilldkg_ref/vss.py b/bip-chilldkg/python/chilldkg_ref/vss.py new file mode 100644 index 0000000000..bd57fcaf5b --- /dev/null +++ b/bip-chilldkg/python/chilldkg_ref/vss.py @@ -0,0 +1,153 @@ +from __future__ import annotations + +from secp256k1lab.secp256k1 import GE, G, Scalar +from secp256k1lab.util import tagged_hash + +from .util import tagged_hash_bip_dkg + + +class Polynomial: + # A scalar polynomial. + # + # A polynomial f of degree at most t - 1 is represented by a list `coeffs` + # of t coefficients, i.e., f(x) = coeffs[0] + ... + coeffs[t-1] * + # x^(t-1).""" + coeffs: list[Scalar] + + def __init__(self, coeffs: list[Scalar]) -> None: + self.coeffs = coeffs + + def eval(self, x: Scalar) -> Scalar: + # Evaluate a polynomial at position x. + + value = Scalar(0) + # Reverse coefficients to compute evaluation via Horner's method + for coeff in self.coeffs[::-1]: + value = value * x + coeff + return value + + def __call__(self, x: Scalar) -> Scalar: + return self.eval(x) + + +class VSSCommitment: + # Infinity GEs are allowed in VSSCommitment to avoid that a participant can + # force the sum of valid commitments to be invalid. + ges: list[GE] + + @staticmethod + def len_bytes(*, t: int) -> int: + return 33 * t + + @staticmethod + def from_bytes(b: bytes, *, t: int) -> VSSCommitment: + if len(b) != VSSCommitment.len_bytes(t=t): + raise ValueError + ges = [ + GE.from_bytes_compressed_with_infinity(b[i : i + 33]) + for i in range(0, 33 * t, 33) + ] + return VSSCommitment(ges) + + def __init__(self, ges: list[GE]) -> None: + self.ges = ges + + def to_bytes(self) -> bytes: + # Return commitments to the coefficients of f. + return b"".join([ge.to_bytes_compressed_with_infinity() for ge in self.ges]) + + def t(self) -> int: + return len(self.ges) + + def pubshare(self, i: int) -> GE: + pubshare: GE = GE.batch_mul( + *(((i + 1) ** j, self.ges[j]) for j in range(len(self.ges))) + ) + return pubshare + + @staticmethod + def verify_secshare(secshare: Scalar, pubshare: GE) -> bool: + # The caller needs to provide the correct pubshare(i) + actual = secshare * G + valid: bool = actual == pubshare + return valid + + def __add__(self, other: VSSCommitment) -> VSSCommitment: + assert self.t() == other.t() + return VSSCommitment([self.ges[i] + other.ges[i] for i in range(self.t())]) + + def commitment_to_secret(self) -> GE: + return self.ges[0] + + def commitment_to_nonconst_terms(self) -> list[GE]: + return self.ges[1 : self.t()] + + def invalid_taproot_commit(self) -> tuple[VSSCommitment, Scalar, GE]: + # Return a modified VSS commitment such that the threshold public key + # generated from it has an unspendable BIP 341 Taproot script path. + # + # Specifically, for a VSS commitment `com`, we have + # `com.invalid_taproot_commit().commitment_to_secret() = com.commitment_to_secret() + t*G`, + # where the tweak `t` commits to an empty message, which is invalid + # according to BIP 341 for Taproot script spends. This follows BIP 341's + # recommended approach for committing to an unspendable script path. + # + # This prevents a malicious participant from secretly inserting a + # *valid* Taproot commitment to a script path into the summed VSS + # commitment during the DKG protocol. If the resulting threshold public + # key was used directly in a BIP 341 Taproot output, the malicious + # participant would be able to spend the output using their hidden + # script path. + # + # The function returns the updated VSS commitment and the tweak `t` + # which must be added to all secret shares of the commitment. + pk = self.commitment_to_secret() + secshare_tweak = Scalar.from_bytes_checked( + tagged_hash("TapTweak", pk.to_bytes_xonly()) + ) + pubshare_tweak = secshare_tweak * G + vss_tweak = VSSCommitment([pubshare_tweak] + [GE()] * (self.t() - 1)) + return (self + vss_tweak, secshare_tweak, pubshare_tweak) + + +class VSS: + f: Polynomial + + def __init__(self, f: Polynomial) -> None: + self.f = f + + @staticmethod + def generate(seed: bytes, t: int) -> VSS: + coeffs = [ + Scalar.from_bytes_checked( + tagged_hash_bip_dkg("vss coeffs", seed + i.to_bytes(4, byteorder="big")) + ) + for i in range(t) + ] + return VSS(Polynomial(coeffs)) + + def secshare_for(self, i: int) -> Scalar: + # Return the secret share for the participant with id i. + # + # This computes f(i+1). + if i < 0: + raise ValueError(f"Invalid participant id: {i}") + x = Scalar(i + 1) + # Ensure we don't compute f(0), which is the secret. + assert x != Scalar(0) + return self.f(x) + + def secshares(self, n: int) -> list[Scalar]: + # Return the secret shares for the participants with ids 0..n-1. + # + # This computes [f(1), ..., f(n)]. + return [self.secshare_for(i) for i in range(n)] + + def commit(self) -> VSSCommitment: + return VSSCommitment([c * G for c in self.f.coeffs]) + + def secret(self) -> Scalar: + # Return the secret to be shared. + # + # This computes f(0). + return self.f.coeffs[0] diff --git a/bip-chilldkg/python/example.py b/bip-chilldkg/python/example.py new file mode 100755 index 0000000000..a7e0ae0e6d --- /dev/null +++ b/bip-chilldkg/python/example.py @@ -0,0 +1,307 @@ +#!/usr/bin/env python3 + +"""Example of a full ChillDKG session""" + +import argparse +import asyncio +import pprint +import sys +from random import randint +from secrets import token_bytes as random_bytes + +from chilldkg_ref import chilldkg +from chilldkg_ref.chilldkg import ( + DKGOutput, + FaultyParticipantOrCoordinatorError, + RecoveryData, + SessionParams, + UnknownFaultyParticipantOrCoordinatorError, + coordinator_finalize, + coordinator_investigate, + coordinator_step1, + hostpubkey_gen, + params_hash, + participant_finalize, + participant_investigate, + participant_step1, + participant_step2, +) + +# +# Network mocks to simulate full DKG sessions +# + + +class CoordinatorChannels: + def __init__(self, n): + self.n = n + self.queues = [] + for i in range(n): + self.queues += [asyncio.Queue()] + + def set_participant_queues(self, participant_queues): + self.participant_queues = participant_queues + + def send_to(self, i, m): + assert self.participant_queues is not None + self.participant_queues[i].put_nowait(m) + + def send_all(self, m): + assert self.participant_queues is not None + for i in range(self.n): + self.participant_queues[i].put_nowait(m) + + async def receive_from(self, i): + item = await self.queues[i].get() + return item + + +class ParticipantChannel: + def __init__(self, coord_queue): + self.queue = asyncio.Queue() + self.coord_queue = coord_queue + + # Send m to coordinator + def send(self, m): + self.coord_queue.put_nowait(m) + + async def receive(self): + item = await self.queue.get() + return item + + +# +# Helper functions +# + + +def pphex(thing): + """Pretty print an object with bytes as hex strings""" + + def hexlify(thing): + if isinstance(thing, bytes): + return thing.hex() + if isinstance(thing, dict): + return {k: hexlify(v) for k, v in thing.items()} + if hasattr(thing, "_asdict"): # NamedTuple + return hexlify(thing._asdict()) + if isinstance(thing, list): + return [hexlify(v) for v in thing] + return thing + + pprint.pp(hexlify(thing)) + + +# +# Protocol parties +# + + +async def participant( + chan: ParticipantChannel, + hostseckey: bytes, + params: SessionParams, + investigation_procedure: bool, +) -> tuple[DKGOutput, RecoveryData]: + # TODO Top-level error handling + random = random_bytes(32) + state1, pmsg1 = participant_step1(hostseckey, params, random) + + chan.send(pmsg1) + cmsg1 = await chan.receive() + + # Participants can implement an optional investigation procedure. This + # allows the participant to determine which participant is faulty when an + # `UnknownFaultyParticipantOrCoordinatorError` is raised. The investigation + # procedure requires the participant to receive an extra "investigation + # message" from the coordinator that contains necessary information. + # + # In this example, if the investigation procedure is enabled, the + # participant expects the coordinator to send a investigation message. + # Alternatively, an implementation of the participant can explicitly request + # the investigation message only if participant_step2 fails. + if investigation_procedure: + cinv = await chan.receive() + + try: + random = random_bytes(32) + state2, eq_round1 = participant_step2(hostseckey, state1, cmsg1, random) + except UnknownFaultyParticipantOrCoordinatorError as e: + if investigation_procedure: + participant_investigate(e, cinv) + else: + # If this participant does not implement the investigation + # procedure, it cannot determine which party is faulty. Re-raise + # UnknownFaultyPartyError in this case. + raise + + chan.send(eq_round1) + cmsg2 = await chan.receive() + + return participant_finalize(state2, cmsg2) + + +async def coordinator( + chans: CoordinatorChannels, params: SessionParams, investigation_procedure: bool +) -> tuple[DKGOutput, RecoveryData]: + (hostpubkeys, _) = params + n = len(hostpubkeys) + + pmsgs1 = [] + for i in range(n): + pmsgs1.append(await chans.receive_from(i)) + state, cmsg1 = coordinator_step1(pmsgs1, params) + chans.send_all(cmsg1) + + # If the coordinator implements the investigation procedure and it is + # enabled, it sends an extra message to the participants. + if investigation_procedure: + inv_msgs = coordinator_investigate(pmsgs1, params) + for i in range(n): + chans.send_to(i, inv_msgs[i]) + + sigs = [] + for i in range(n): + sigs += [await chans.receive_from(i)] + cmsg2, dkg_output, recovery_data = coordinator_finalize(state, sigs) + chans.send_all(cmsg2) + + return dkg_output, recovery_data + + +# +# DKG Session +# + + +# This is a dummy participant used to demonstrate the investigation procedure. +# It picks a random victim participant and sends an invalid share to it. +async def faulty_participant( + chan: ParticipantChannel, + hostseckey: bytes, + params: SessionParams, + participant_id: int, +): + n = len(params.hostpubkeys) + random = random_bytes(32) + _, pmsg1 = participant_step1(hostseckey, params, random) + pmsg1_parsed = chilldkg.ParticipantMsg1.from_bytes(pmsg1, t=params.t, n=n) + + assert len(pmsg1_parsed.enc_pmsg.enc_shares) == n + # Pick random victim that is not this participant + victim = (participant_id + randint(1, n - 1)) % n + pmsg1_parsed.enc_pmsg.enc_shares[victim] += 17 + + chan.send(pmsg1_parsed.to_bytes()) + + +def simulate_chilldkg_full( + hostseckeys: list[bytes], params: SessionParams, faulty_id: int | None +) -> list[tuple[DKGOutput, RecoveryData] | None]: + n = len(hostseckeys) + assert n == len(params.hostpubkeys) + + # For demonstration purposes, we enable the investigation pro if a participant is + # faulty. + investigation_procedure = faulty_id is not None + + async def session(): + coord_chans = CoordinatorChannels(n) + participant_chans = [ + ParticipantChannel(coord_chans.queues[i]) for i in range(n) + ] + coord_chans.set_participant_queues( + [participant_chans[i].queue for i in range(n)] + ) + coroutines = [coordinator(coord_chans, params, investigation_procedure)] + [ + participant( + participant_chans[i], hostseckeys[i], params, investigation_procedure + ) + if i != faulty_id + else faulty_participant(participant_chans[i], hostseckeys[i], params, i) + for i in range(n) + ] + return await asyncio.gather(*coroutines) + + outputs = asyncio.run(session()) + return outputs + + +def main(): + parser = argparse.ArgumentParser(description="ChillDKG example") + parser.add_argument( + "--faulty-participant", + action="store_true", + help="When this flag is set, one random participant will send an invalid message, and the investigation procedure will be enabled for other participants and the coordinator.", + ) + parser.add_argument( + "t", nargs="?", type=int, default=2, help="Signing threshold [default = 2]" + ) + parser.add_argument( + "n", nargs="?", type=int, default=3, help="Number of participants [default = 3]" + ) + args = parser.parse_args() + t = args.t + n = args.n + if args.faulty_participant: + faulty_id = randint(0, n - 1) + else: + faulty_id = None + + print("====== ChillDKG example session ======") + print(f"Using n = {n} participants and a threshold of t = {t}.") + if faulty_id is not None: + print(f"Participant {faulty_id} is faulty.") + print() + + # Generate common inputs for all participants and coordinator + hostseckeys = [random_bytes(32) for _ in range(n)] + hostpubkeys = [] + for i in range(n): + hostpubkeys += [hostpubkey_gen(hostseckeys[i])] + params = SessionParams(hostpubkeys, t) + + print("=== Host secret keys ===") + pphex(hostseckeys) + print() + + print("=== Session parameters ===") + pphex(params) + print() + print(f"Session parameters hash: {params_hash(params).hex()}") + print() + + try: + rets = simulate_chilldkg_full(hostseckeys, params, faulty_id) + except FaultyParticipantOrCoordinatorError as e: + print( + f"A participant has failed and is blaming either participant {e.participant_id} or the coordinator." + ) + # If the blamed participant is the faulty participant, exit with code 0. + # Otherwise, re-raise the exception. + if faulty_id == e.participant_id: + return 0 + else: + raise + + assert len(rets) == n + 1 + print("=== Coordinator's DKG output ===") + dkg_output, _ = rets[0] + pphex(dkg_output) + print() + + for i in range(n): + print(f"=== Participant {i}'s DKG output ===") + dkg_output, _ = rets[i + 1] + pphex(dkg_output) + print() + + # Check that all RecoveryData of all parties is identical + assert len({rets[i][1] for i in range(n + 1)}) == 1 + recovery_data = rets[0][1] + print(f"=== Common recovery data ({len(recovery_data)} bytes) ===") + print(recovery_data.hex()) + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/bip-chilldkg/python/gen_vector_utils/__init__.py b/bip-chilldkg/python/gen_vector_utils/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/bip-chilldkg/python/gen_vector_utils/coordinator.py b/bip-chilldkg/python/gen_vector_utils/coordinator.py new file mode 100644 index 0000000000..b5811a3ef9 --- /dev/null +++ b/bip-chilldkg/python/gen_vector_utils/coordinator.py @@ -0,0 +1,459 @@ +import copy + +from chilldkg_ref import chilldkg + +from .fixtures import AUX_RAND_HEX, HOSTSECKEYS_HEX, RANDOMS_HEX, THRESHOLD_CONFIGS +from .util import ( + assign_tc_ids, + bytes_to_hex, + dkg_output_asdict, + expect_exception, + expect_faulty_exception, + hex_list_to_bytes, + params_asdict, +) + +COORDINATOR_STEP1_DESCRIPTION = [ + "Test vectors for coordinator_step1(pmsgs1, params).", + "Aggregates participant round-1 messages and produces the coordinator's broadcast message (cmsg1).", + "", + "Assemble the pmsgs1 list from pmsg1Pool using pmsg1Indices:", + " pmsgs1 = [pmsg1Pool[i] for i in pmsg1Indices]", + " Pool entries at indices 0..n-1 are well-formed messages; higher indices may be malformed.", + "", + "For each valid test case:", + " Call coordinator_step1(pmsgs1, params).", + " Verify the returned cmsg1 equals expectedCmsg1.", + "", + "For each error test case:", + " Call coordinator_step1(pmsgs1, params).", + " Verify it raises an exception matching expectedError.", + " Error objects may include 'participantId' (identifier of the blamed participant).", +] + + +def generate_coordinator_step1_group(t, n): + hostseckeys = hex_list_to_bytes(HOSTSECKEYS_HEX[:n]) + hostpubkeys = [chilldkg.hostpubkey_gen(sk) for sk in hostseckeys] + params = chilldkg.SessionParams(hostpubkeys, t) + randoms = hex_list_to_bytes(RANDOMS_HEX[:n]) + assert len(randoms) == len(hostpubkeys) + + pmsgs1 = [] + for i in range(len(hostpubkeys)): + _, msg = chilldkg.participant_step1(hostseckeys[i], params, randoms[i]) + pmsgs1.append(msg) + _, expected_cmsg1 = chilldkg.coordinator_step1(pmsgs1, params) + + pmsg1_pool = [] + + # valid pmsgs1 at indices [0, 1, ..., n - 1] + for m in pmsgs1: + pmsg1_pool.append(bytes_to_hex(m)) + + valid_cases = [] + error_cases = [] + + # --- Valid test case --- + valid_cases.append( + { + "pmsg1Indices": list(range(len(pmsgs1))), # [0, 1, ..., n - 1] + "params": params_asdict(params), + "expectedCmsg1": bytes_to_hex(expected_cmsg1), + "comment": "valid coordinator step1", + } + ) + + # --- Error test case: Invalid threshold --- + invalid_params = chilldkg.SessionParams(hostpubkeys, 0) + error = expect_exception( + lambda: chilldkg.coordinator_step1(pmsgs1, invalid_params), + chilldkg.ThresholdOrCountError, + ) + error_cases.append( + { + "pmsg1Indices": list(range(len(pmsgs1))), # same valid pmsgs1 + "params": params_asdict(invalid_params), # t=0 + "expectedError": error, + "comment": "invalid threshold value", + } + ) + + # --- Error test case: t > n --- + invalid_params = chilldkg.SessionParams(hostpubkeys, n + 1) + error = expect_exception( + lambda: chilldkg.coordinator_step1(pmsgs1, invalid_params), + chilldkg.ThresholdOrCountError, + ) + error_cases.append( + { + "pmsg1Indices": list(range(len(pmsgs1))), # same valid pmsgs1 + "params": params_asdict(invalid_params), # t = n + 1 + "expectedError": error, + "comment": "invalid threshold value t > n", + } + ) + + # --- Error test case: hostpubkeys list contains an invalid value --- + invalid_hostpubkey = b"\x03" + 31 * b"\x00" + b"\x05" # Invalid x-coordinate + with_invalid = hostpubkeys[:-1] + [invalid_hostpubkey] + invalid_params = chilldkg.SessionParams(with_invalid, t) + error = expect_exception( + lambda: chilldkg.coordinator_step1(pmsgs1, invalid_params), + chilldkg.InvalidHostPubkeyError, + ) + error_cases.append( + { + "pmsg1Indices": list(range(len(pmsgs1))), + "params": params_asdict(invalid_params), + "expectedError": error, + "comment": "hostpubkeys list contains an invalid value", + } + ) + + # --- Error test case: hostpubkeys list contains duplicate values --- + with_duplicate = hostpubkeys[:-1] + [hostpubkeys[0]] + duplicate_params = chilldkg.SessionParams(with_duplicate, t) + error = expect_exception( + lambda: chilldkg.coordinator_step1(pmsgs1, duplicate_params), + chilldkg.DuplicateHostPubkeyError, + ) + error_cases.append( + { + "pmsg1Indices": list(range(len(pmsgs1))), + "params": params_asdict(duplicate_params), + "expectedError": error, + "comment": "hostpubkeys list contains duplicate values", + } + ) + + # --- Error test case: hostpubkeys list contains infinite value --- + infinity_hostpubkey = b"\x00" * 33 # Infinite point + with_infinity = hostpubkeys[:-1] + [infinity_hostpubkey] + invalid_params = chilldkg.SessionParams(with_infinity, t) + error = expect_exception( + lambda: chilldkg.coordinator_step1(pmsgs1, invalid_params), + chilldkg.InvalidHostPubkeyError, + ) + error_cases.append( + { + "pmsg1Indices": list(range(len(pmsgs1))), + "params": params_asdict(invalid_params), + "expectedError": error, + "comment": "hostpubkeys list contains an infinity point", + } + ) + + # --- Error test case: invalid pmsgs1 (n-1 entries instead of n) --- + short_pmsgs1 = pmsgs1[: n - 1] + error = expect_exception( + lambda: chilldkg.coordinator_step1(short_pmsgs1, params), + ValueError, + ) + error_cases.append( + { + "pmsg1Indices": list(range(n - 1)), + "params": params_asdict(params), + "expectedError": error, + "comment": "invalid pmsgs1: fewer entries than participants", + } + ) + + # --- Error test case: invalid pmsgs1 (n+1 entries instead of n) --- + long_pmsgs1 = pmsgs1 + [pmsgs1[0]] # Add an extra entry + error = expect_exception( + lambda: chilldkg.coordinator_step1(long_pmsgs1, params), + ValueError, + ) + error_cases.append( + { + "pmsg1Indices": list(range(n)) + [0], + "params": params_asdict(params), + "expectedError": error, + "comment": "invalid pmsgs1: more entries than participants", + } + ) + + # --- Error test case: participant (id 1) message has an enc_shares list of invalid length --- + invalid_pmsgs1 = copy.deepcopy(pmsgs1) + invalid_pmsg1_parsed = chilldkg.ParticipantMsg1.from_bytes( + invalid_pmsgs1[1], t=params.t, n=len(params.hostpubkeys) + ) + invalid_pmsg1_parsed.enc_pmsg.enc_shares.pop() + invalid_pmsgs1[1] = invalid_pmsg1_parsed.to_bytes() + + error = expect_exception( + lambda: chilldkg.coordinator_step1(invalid_pmsgs1, params), + ValueError, + ) + pmsg1_pool.append(bytes_to_hex(invalid_pmsgs1[1])) # index n + error_cases.append( + { + "pmsg1Indices": [ + len(pmsg1_pool) - 1 if i == 1 else i for i in range(n) + ], # [0, n, 2,..., n - 1] — index 1 replaced + "params": params_asdict(params), + "expectedError": error, + "comment": "participant (id 1) message has an enc_shares list of invalid length", + } + ) + + # --- Error test case: pmsg1 (index 0) is empty --- + empty_pmsgs1 = b"" + pmsg1_pool.append(bytes_to_hex(empty_pmsgs1)) # index n + 1 + invalid_pmsgs1 = [empty_pmsgs1] + pmsgs1[1:] + error = expect_exception( + lambda: chilldkg.coordinator_step1(invalid_pmsgs1, params), + ValueError, + ) + error_cases.append( + { + "pmsg1Indices": [ + len(pmsg1_pool) - 1 if i == 0 else i for i in range(n) + ], # [n + 1, 1, 2,..., n - 1] — index 0 replaced + "params": params_asdict(params), + "expectedError": error, + "comment": "missing simplpedpop participant message at index 0", + } + ) + + # --- Error test case: pmsg1 (index 1) truncated before pubnonce --- + simpl_pmsg_len = 33 * params.t + 64 + truncated_pmsg1 = pmsgs1[1][:simpl_pmsg_len] + pmsg1_pool.append(bytes_to_hex(truncated_pmsg1)) # index n + 2 + invalid_pmsgs1 = list(pmsgs1) + invalid_pmsgs1[1] = truncated_pmsg1 + error = expect_exception( + lambda: chilldkg.coordinator_step1(invalid_pmsgs1, params), + ValueError, + ) + error_cases.append( + { + "pmsg1Indices": [ + len(pmsg1_pool) - 1 if i == 1 else i for i in range(n) + ], # [0, n + 2, 2,..., n - 1] — index 1 replaced + "params": params_asdict(params), + "expectedError": error, + "comment": "missing public nonce in pmsg1 (index 1)", + } + ) + + return { + "pmsg1Pool": pmsg1_pool, + "validTestCases": valid_cases, + "errorTestCases": error_cases, + } + + +def generate_coordinator_step1_vectors(): + groups = [generate_coordinator_step1_group(t, n) for t, n in THRESHOLD_CONFIGS] + total_tests = assign_tc_ids(groups) + return { + "description": COORDINATOR_STEP1_DESCRIPTION, + "totalTests": total_tests, + "testGroups": groups, + } + + +COORDINATOR_FINALIZE_DESCRIPTION = [ + "Test vectors for coordinator_finalize(cstate, pmsgs2).", + "Collects participant round-2 signatures and produces the final certificate (cmsg2).", + "", + "Harness setup:", + " 1. Call coordinator_step1(pmsgs1, params) to obtain (cstate, cmsg1_out).", + " Assert cmsg1_out == cmsg1.", + "", + "Assemble the pmsgs2 list from pmsg2Pool using pmsg2Indices:", + " pmsgs2 = [pmsg2Pool[i] for i in pmsg2Indices]", + "", + "For each valid test case:", + " Call coordinator_finalize(cstate, pmsgs2).", + " Verify the result matches expectedOutput (cmsg2, dkgOutput, recoveryData).", + "", + "For each error test case:", + " Call coordinator_finalize(cstate, pmsgs2).", + " Verify it raises an exception matching expectedError.", +] + + +def generate_coordinator_finalize_group(t, n): + hostseckeys = hex_list_to_bytes(HOSTSECKEYS_HEX[:n]) + hostpubkeys = [chilldkg.hostpubkey_gen(sk) for sk in hostseckeys] + params = chilldkg.SessionParams(hostpubkeys, t) + randoms = hex_list_to_bytes(RANDOMS_HEX[:n]) + aux_rand = bytes.fromhex(AUX_RAND_HEX) + assert len(randoms) == len(hostpubkeys) + pstates1 = [] + pmsgs1 = [] + for i in range(len(hostpubkeys)): + state, msg = chilldkg.participant_step1(hostseckeys[i], params, randoms[i]) + pstates1.append(state) + pmsgs1.append(msg) + cstate, cmsg1 = chilldkg.coordinator_step1(pmsgs1, params) + + # build pmsgs2 pool with valid messages at indices [0, 1, ..., n - 1] + pmsgs2 = [] + for i in range(len(hostpubkeys)): + _, msg = chilldkg.participant_step2( + hostseckeys[i], pstates1[i], cmsg1, aux_rand + ) + pmsgs2.append(msg) + cmsg2, cout, crec = chilldkg.coordinator_finalize(cstate, pmsgs2) + pmsg2_pool = [bytes_to_hex(m) for m in pmsgs2] + + valid_cases = [] + error_cases = [] + + # --- Valid test case --- + valid_cases.append( + { + "pmsg2Indices": list(range(len(pmsgs2))), # [0, 1, ..., n - 1] + "expectedOutput": { + "cmsg2": bytes_to_hex(cmsg2), + "dkgOutput": dkg_output_asdict(cout), + "recoveryData": bytes_to_hex(crec), + }, + "comment": "valid coordinator finalize", + } + ) + + # --- Error test case: participant with id 1 has a short signature --- + invalid_pmsgs2_short_sig = copy.deepcopy(pmsgs2) + invalid_pmsgs2_short_sig[1] = invalid_pmsgs2_short_sig[1][ + :63 + ] # truncate sig to 63 bytes + error_case = expect_exception( + lambda: chilldkg.coordinator_finalize(cstate, invalid_pmsgs2_short_sig), + ValueError, + ) + pmsg2_pool.append(bytes_to_hex(invalid_pmsgs2_short_sig[1])) # index n + error_cases.append( + { + "pmsg2Indices": [ + len(pmsg2_pool) - 1 if i == 1 else i for i in range(n) + ], # [0, n, 2, ..., n-1] — index 1 replaced with bad pmsg + "expectedError": error_case, + "comment": "participant with id 1 sent a short signature", + } + ) + + # --- Error test case: invalid pmsgs2 (n-1 entries instead of n) --- + invalid_pmsgs2_short = copy.deepcopy(pmsgs2) + invalid_pmsgs2_short.pop() + error_case = expect_exception( + lambda: chilldkg.coordinator_finalize(cstate, invalid_pmsgs2_short), ValueError + ) + error_cases.append( + { + "pmsg2Indices": list(range(len(pmsgs2) - 1)), # [0, ..., n - 2] + "expectedError": error_case, + "comment": "invalid pmsgs2: fewer entries than participants", + } + ) + + # --- Error test case: invalid pmsgs2 (n+1 entries instead of n) --- + invalid_pmsgs2_long = pmsgs2 + [pmsgs2[0]] + error_case = expect_exception( + lambda: chilldkg.coordinator_finalize(cstate, invalid_pmsgs2_long), ValueError + ) + error_cases.append( + { + "pmsg2Indices": list(range(len(pmsgs2))) + [0], # [0, 1, ..., n-1, 0] + "expectedError": error_case, + "comment": "invalid pmsgs2: more entries than participants", + } + ) + + # --- Error test case: participant with id 1 sent an invalid signature --- + invalid_pmsgs2_sig = copy.deepcopy(pmsgs2) + invalid_pmsgs2_sig[1] = bytes.fromhex( + "09C289578B96E6283AB13E4741FB489FC147FB1A5F446A314BA73C052131EFB04B83247A0BCEDF5205202AD64188B24B0BC5B51A17AEB218BD98DBE000C843B9" + ) # random sig + error_case = expect_faulty_exception( + lambda: chilldkg.coordinator_finalize(cstate, invalid_pmsgs2_sig), + chilldkg.FaultyParticipantError, + 1, + ) + + # add adversarial entry to pool + pmsg2_pool.append(bytes_to_hex(invalid_pmsgs2_sig[1])) # index n + error_cases.append( + { + "pmsg2Indices": [ + len(pmsg2_pool) - 1 if i == 1 else i for i in range(n) + ], # [0, n, 2,..., n - 1] + "expectedError": error_case, + "comment": "participant with id 1 sent an invalid signature", + } + ) + + return { + "params": params_asdict(params), + "pmsgs1": [bytes_to_hex(m) for m in pmsgs1], + "cmsg1": bytes_to_hex(cmsg1), + "pmsg2Pool": pmsg2_pool, + "validTestCases": valid_cases, + "errorTestCases": error_cases, + } + + +def generate_coordinator_finalize_vectors(): + groups = [generate_coordinator_finalize_group(t, n) for t, n in THRESHOLD_CONFIGS] + total_tests = assign_tc_ids(groups) + return { + "description": COORDINATOR_FINALIZE_DESCRIPTION, + "totalTests": total_tests, + "testGroups": groups, + } + + +COORDINATOR_INVESTIGATE_DESCRIPTION = [ + "Test vectors for coordinator_investigate(pmsgs1, params).", + "Generates investigation messages to help participants identify faulty parties.", + "Called when a participant reports UnknownFaultyParticipantOrCoordinatorError.", + "", + "For each valid test case:", + " Call coordinator_investigate(pmsgs1, params).", + " Verify the returned list of investigation messages equals expectedCinvMsgs.", +] + + +def generate_coordinator_investigate_group(t, n): + hostseckeys = hex_list_to_bytes(HOSTSECKEYS_HEX[:n]) + hostpubkeys = [chilldkg.hostpubkey_gen(sk) for sk in hostseckeys] + params = chilldkg.SessionParams(hostpubkeys, t) + randoms = hex_list_to_bytes(RANDOMS_HEX[:n]) + assert len(randoms) == len(hostpubkeys) + + pmsgs1 = [] + for i in range(len(hostpubkeys)): + _, msg = chilldkg.participant_step1(hostseckeys[i], params, randoms[i]) + pmsgs1.append(msg) + cinv_msgs = chilldkg.coordinator_investigate(pmsgs1, params) + + # --- Valid test case --- + valid_cases = [ + { + "expectedCinvMsgs": [bytes_to_hex(m) for m in cinv_msgs], + "comment": "valid coordinator investigate", + } + ] + + return { + "params": params_asdict(params), + "pmsgs1": [bytes_to_hex(m) for m in pmsgs1], + "validTestCases": valid_cases, + "errorTestCases": [], + } + + +def generate_coordinator_investigate_vectors(): + groups = [ + generate_coordinator_investigate_group(t, n) for t, n in THRESHOLD_CONFIGS + ] + total_tests = assign_tc_ids(groups) + return { + "description": COORDINATOR_INVESTIGATE_DESCRIPTION, + "totalTests": total_tests, + "testGroups": groups, + } diff --git a/bip-chilldkg/python/gen_vector_utils/fixtures.py b/bip-chilldkg/python/gen_vector_utils/fixtures.py new file mode 100644 index 0000000000..4884304188 --- /dev/null +++ b/bip-chilldkg/python/gen_vector_utils/fixtures.py @@ -0,0 +1,28 @@ +# Pool of host secret keys (32 bytes each), sized for up to n=4 participants. +# Slice [:n] to get the keys for a given session size. +HOSTSECKEYS_HEX = [ + "ADE179B2C56CB75868D44B333C16C89CB00DFDE378AD79C84D0CCE856E4F9207", + "94BB10C1DE15783C3F3E49167A0951CACD2803F13AAC456C816E88AB4AC76330", + "F129C2D30096C972F14BB6764CC003C97119C0E32831EA4858F0DD0DFB780FAA", + "7F3A1D2E4C5B6A7890ABCDEF1234567890ABCDEF1234567890ABCDEF12345678", +] + +# Per-participant randomness for participant_step1, one entry per participant. +# Slice [:n] to get the randoms for a given session size. +RANDOMS_HEX = [ + "42B53D62E27380D6F7096EDA1C28C57DDB89FCD4CE5B843EDAC220E165B5A7EC", + "FDE223740111491D5E60BEFB447A2D8C0B12D4B1CE1A0D6BF5A16CBA7E420153", + "E5CFC54DA8EE57BA97C389060D00BB840A9DDF6BF1E32AE3D3598373EF384EE7", + "B1C2D3E4F5A6B7C8D9E0F1A2B3C4D5E6F7A8B9C0D1E2F3A4B5C6D7E8F9013579", +] + +# Auxiliary randomness supplied by participant 0 during participant_step2. +AUX_RAND_HEX = "005F5C3A69BB274F4559490AD754F1F5AFFABAED4C71AD5D8ACBAEFC2B491573" + +# (t, n) tuples defining the threshold configurations to test. +THRESHOLD_CONFIGS = [ + (2, 3), + (1, 3), + (3, 3), + (2, 4), +] diff --git a/bip-chilldkg/python/gen_vector_utils/participant.py b/bip-chilldkg/python/gen_vector_utils/participant.py new file mode 100644 index 0000000000..81d031fe98 --- /dev/null +++ b/bip-chilldkg/python/gen_vector_utils/participant.py @@ -0,0 +1,992 @@ +import copy + +from secp256k1lab.secp256k1 import GE, Scalar +from secp256k1lab.util import bytes_from_int + +from chilldkg_ref import chilldkg + +from .fixtures import AUX_RAND_HEX, HOSTSECKEYS_HEX, RANDOMS_HEX, THRESHOLD_CONFIGS +from .util import ( + assign_tc_ids, + bytes_to_hex, + dkg_output_asdict, + expect_exception, + expect_faulty_exception, + hex_list_to_bytes, + params_asdict, +) + +# Arbitrary EC point x-coordinate used as a hardcoded wrong value in test vectors. +ARBITRARY_POINT_X = 0x60C301C1EEC41AD16BF53F55F97B7B6EB842D9E2B8139712BA54695FF7116073 + +PARTICIPANT_STEP1_DESCRIPTION = [ + "Test vectors for participant_step1(hostseckey, params, random).", + "Executes the first round of DKG from a participant's perspective.", + "Takes the participant's host secret key, session parameters, and 32 bytes of fresh randomness.", + "Returns an opaque state object and a participant message (pmsg1) to send to the coordinator.", + "", + "For each valid test case:", + " Call participant_step1(hostseckey, params, random).", + " Verify the returned pmsg1 equals expectedPmsg1.", + "", + "For each error test case:", + " Call participant_step1(hostseckey, params, random).", + " Verify it raises an exception matching expectedError.", +] + + +def generate_participant_step1_group(t, n): + valid_cases = [] + error_cases = [] + + hostseckeys = hex_list_to_bytes(HOSTSECKEYS_HEX[:n]) + hostpubkeys = [chilldkg.hostpubkey_gen(sk) for sk in hostseckeys] + random = bytes.fromhex(RANDOMS_HEX[0]) + + # --- Valid test case --- + params = chilldkg.SessionParams(hostpubkeys, t) + _, expected_pmsg1 = chilldkg.participant_step1(hostseckeys[0], params, random) + valid_cases.append( + { + "hostseckey": bytes_to_hex(hostseckeys[0]), + "params": params_asdict(params), + "random": bytes_to_hex(random), + "expectedPmsg1": bytes_to_hex(expected_pmsg1), + "comment": "valid participant step1", + } + ) + + # --- Error test case: Wrong hostseckey length --- + short_hostseckey = bytes.fromhex("631C047D50A67E45E27ED1FF25FCE179") + assert len(short_hostseckey) == 16 + error = expect_exception( + lambda: chilldkg.participant_step1(short_hostseckey, params, random), + ValueError, + ) + error_cases.append( + { + "hostseckey": bytes_to_hex(short_hostseckey), + "params": params_asdict(params), + "random": bytes_to_hex(random), + "expectedError": error, + "comment": "length of host secret key is not 32 bytes", + } + ) + # --- Error test case: zero hostseckey --- + zero_hostseckey = b"\x00" * 32 + error = expect_exception( + lambda: chilldkg.participant_step1(zero_hostseckey, params, random), + chilldkg.HostSeckeyError, + ) + error_cases.append( + { + "hostseckey": bytes_to_hex(zero_hostseckey), + "params": params_asdict(params), + "random": bytes_to_hex(random), + "expectedError": error, + "comment": "host secret key is zero", + } + ) + # --- Error test case: Out-of-range hostseckey --- + invalid_hostseckey = bytes_from_int(Scalar.SIZE) + error = expect_exception( + lambda: chilldkg.participant_step1(invalid_hostseckey, params, random), + chilldkg.HostSeckeyError, + ) + error_cases.append( + { + "hostseckey": bytes_to_hex(invalid_hostseckey), + "params": params_asdict(params), + "random": bytes_to_hex(random), + "expectedError": error, + "comment": "host secret key is out of range", + } + ) + # --- Error test case: Invalid threshold --- + invalid_params = chilldkg.SessionParams(hostpubkeys, 0) + error = expect_exception( + lambda: chilldkg.participant_step1(hostseckeys[0], invalid_params, random), + chilldkg.ThresholdOrCountError, + ) + error_cases.append( + { + "hostseckey": bytes_to_hex(hostseckeys[0]), + "params": params_asdict(invalid_params), + "random": bytes_to_hex(random), + "expectedError": error, + "comment": "invalid threshold value", + } + ) + # --- Error test case: t > n --- + invalid_params = chilldkg.SessionParams(hostpubkeys, n + 1) + error = expect_exception( + lambda: chilldkg.participant_step1(hostseckeys[0], invalid_params, random), + chilldkg.ThresholdOrCountError, + ) + error_cases.append( + { + "hostseckey": bytes_to_hex(hostseckeys[0]), + "params": params_asdict(invalid_params), + "random": bytes_to_hex(random), + "expectedError": error, + "comment": "threshold exceeds the number of participants", + } + ) + # --- Error test case: hostpubkeys list contains a value with an invalid prefix --- + invalid_hostpubkey = b"\xeb" * 33 # invalid prefix + with_invalid = hostpubkeys[:-1] + [invalid_hostpubkey] + invalid_params = chilldkg.SessionParams(with_invalid, t) + error = expect_exception( + lambda: chilldkg.participant_step1(hostseckeys[0], invalid_params, random), + chilldkg.InvalidHostPubkeyError, + ) + error_cases.append( + { + "hostseckey": bytes_to_hex(hostseckeys[0]), + "params": params_asdict(invalid_params), + "random": bytes_to_hex(random), + "expectedError": error, + "comment": "hostpubkeys list contains an invalid value with invalid prefix", + } + ) + # --- Error test case: hostpubkeys list contains a value with an off-curve x-coordinate --- + invalid_hostpubkey = b"\x03" + 31 * b"\x00" + b"\x05" # invalid x-coordinate + with_invalid = hostpubkeys[:-1] + [invalid_hostpubkey] + invalid_params = chilldkg.SessionParams(with_invalid, t) + error = expect_exception( + lambda: chilldkg.participant_step1(hostseckeys[0], invalid_params, random), + chilldkg.InvalidHostPubkeyError, + ) + error_cases.append( + { + "hostseckey": bytes_to_hex(hostseckeys[0]), + "params": params_asdict(invalid_params), + "random": bytes_to_hex(random), + "expectedError": error, + "comment": "hostpubkeys list contains an invalid value with invalid x-coordinate", + } + ) + # --- Error test case: hostpubkeys list contains an infinite value --- + infinity_hostpubkey = b"\x00" * 33 # infinity + with_infinity = hostpubkeys[:-1] + [infinity_hostpubkey] + invalid_params = chilldkg.SessionParams(with_infinity, t) + error = expect_exception( + lambda: chilldkg.participant_step1(hostseckeys[0], invalid_params, random), + chilldkg.InvalidHostPubkeyError, + ) + error_cases.append( + { + "hostseckey": bytes_to_hex(hostseckeys[0]), + "params": params_asdict(invalid_params), + "random": bytes_to_hex(random), + "expectedError": error, + "comment": "hostpubkeys list contains an infinity point", + } + ) + # --- Error test case: hostpubkeys list contains duplicate values --- + with_duplicate = hostpubkeys[:-1] + [hostpubkeys[0]] + duplicate_params = chilldkg.SessionParams(with_duplicate, t) + error = expect_exception( + lambda: chilldkg.participant_step1(hostseckeys[0], duplicate_params, random), + chilldkg.DuplicateHostPubkeyError, + ) + error_cases.append( + { + "hostseckey": bytes_to_hex(hostseckeys[0]), + "params": params_asdict(duplicate_params), + "random": bytes_to_hex(random), + "expectedError": error, + "comment": "hostpubkeys list contains duplicate values", + } + ) + # --- Error test case: hostseckey doesn't match any hostpubkey --- + rand_hostseckey = bytes.fromhex( + "759DE9306FB02B3D84C455112BF1F3360401DC383ECD1FCEDE59EC809D6F9FE7" + ) + error = expect_exception( + lambda: chilldkg.participant_step1(rand_hostseckey, params, random), + chilldkg.HostSeckeyError, + ) + error_cases.append( + { + "hostseckey": bytes_to_hex(rand_hostseckey), + "params": params_asdict(params), + "random": bytes_to_hex(random), + "expectedError": error, + "comment": "host secret key doesn't match any hostpubkey", + } + ) + # --- Error test case: Wrong randomness length --- + short_random = bytes.fromhex("42B53D62E27380D6F7096EDA1C28C57D") # 16 bytes + assert len(short_random) == 16 + error = expect_exception( + lambda: chilldkg.participant_step1(hostseckeys[0], params, short_random), + ValueError, + ) + error_cases.append( + { + "hostseckey": bytes_to_hex(hostseckeys[0]), + "params": params_asdict(params), + "random": bytes_to_hex(short_random), + "expectedError": error, + "comment": "length of randomness is not 32 bytes", + } + ) + # --- Error test case: Zero randomness --- + zero_random = b"\x00" * 32 + error = expect_exception( + lambda: chilldkg.participant_step1(hostseckeys[0], params, zero_random), + chilldkg.RandomnessError, + ) + error_cases.append( + { + "hostseckey": bytes_to_hex(hostseckeys[0]), + "params": params_asdict(params), + "random": bytes_to_hex(zero_random), + "expectedError": error, + "comment": "randomness is zero", + } + ) + + return { + "validTestCases": valid_cases, + "errorTestCases": error_cases, + } + + +def generate_participant_step1_vectors(): + groups = [generate_participant_step1_group(t, n) for t, n in THRESHOLD_CONFIGS] + total_tests = assign_tc_ids(groups) + return { + "description": PARTICIPANT_STEP1_DESCRIPTION, + "totalTests": total_tests, + "testGroups": groups, + } + + +PARTICIPANT_STEP2_DESCRIPTION = [ + "Test vectors for participant_step2(hostseckey, pstate1, cmsg1, auxRand).", + "Executes the second round of DKG from a participant's perspective.", + "Processes the coordinator's aggregated message (cmsg1) and produces a partial signature (pmsg2).", + "", + "Harness setup (re-derive state from prior round):", + " 1. Call participant_step1(hostseckey, params, random) to obtain (pstate1, pmsg1_out).", + " 2. Assert pmsg1_out == pmsg1 (verifies your step1 implementation before testing step2).", + "", + "For each valid test case:", + " Call participant_step2(hostseckey, pstate1, cmsg1, auxRand).", + " Verify the returned pmsg2 equals expectedPmsg2.", + "", + "For each error test case:", + " Call participant_step2(hostseckey, pstate1, cmsg1, auxRand).", + " Verify it raises an exception matching expectedError.", + " Error objects contain 'type' (exception class name) and optionally:", + " - 'participantId': identifier of the blamed party (for FaultyParticipantOrCoordinatorError)", + " - 'message': human-readable description (informational, not required to match exactly)", +] + + +def generate_participant_step2_group(t, n): + valid_cases = [] + error_cases = [] + + hostseckeys = hex_list_to_bytes(HOSTSECKEYS_HEX[:n]) + hostpubkeys = [chilldkg.hostpubkey_gen(sk) for sk in hostseckeys] + params = chilldkg.SessionParams(hostpubkeys, t) + randoms = hex_list_to_bytes(RANDOMS_HEX[:n]) + assert len(randoms) == len(hostpubkeys) + pstates1 = [] + pmsgs1 = [] + for i in range(len(hostpubkeys)): + state, msg = chilldkg.participant_step1(hostseckeys[i], params, randoms[i]) + pstates1.append(state) + pmsgs1.append(msg) + _, cmsg1 = chilldkg.coordinator_step1(pmsgs1, params) + aux_rand = bytes.fromhex(AUX_RAND_HEX) + + # --- Valid test case --- + _, pmsg2 = chilldkg.participant_step2(hostseckeys[0], pstates1[0], cmsg1, aux_rand) + valid_cases.append( + { + "cmsg1": bytes_to_hex(cmsg1), + "expectedPmsg2": bytes_to_hex(pmsg2), + "comment": "valid participant step2", + } + ) + + cmsg1_parsed = chilldkg.CoordinatorMsg1.from_bytes( + cmsg1, t=params.t, n=len(params.hostpubkeys) + ) + # --- Error test case: Wrong aux randomness length --- + short_aux_rand = bytes.fromhex("42B53D62E27380D6F7096EDA1C28C57D") + error = expect_exception( + lambda: chilldkg.participant_step2( + hostseckeys[0], pstates1[0], cmsg1, short_aux_rand + ), + ValueError, + ) + error_cases.append( + { + "auxRand": bytes_to_hex(short_aux_rand), + "cmsg1": bytes_to_hex(cmsg1), + "expectedError": error, + "comment": "length of aux randomness is not 32 bytes", + } + ) + # --- Error test case: hostseckey does not match the one in state1 --- + mismatched_hostseckey = hostseckeys[1] + error = expect_exception( + lambda: chilldkg.participant_step2( + mismatched_hostseckey, pstates1[0], cmsg1, aux_rand + ), + chilldkg.HostSeckeyError, + ) + error_cases.append( + { + "hostseckey": bytes_to_hex(mismatched_hostseckey), + "cmsg1": bytes_to_hex(cmsg1), + "expectedError": error, + "comment": "hostseckey does not match the one used in participant_step1", + } + ) + # --- Error test case: pubnonces list in cmsg1 has an invalid prefix at index 0 (own pubnonce) --- + invalid_cmsg1_parsed = copy.deepcopy(cmsg1_parsed) + invalid_cmsg1_parsed.enc_cmsg.pubnonces[0] = b"\xeb" * 33 # invalid prefix + invalid_cmsg1 = invalid_cmsg1_parsed.to_bytes() + error = expect_exception( + lambda: chilldkg.participant_step2( + hostseckeys[0], pstates1[0], invalid_cmsg1, aux_rand + ), + chilldkg.FaultyCoordinatorError, + ) + error_cases.append( + { + "cmsg1": bytes_to_hex(invalid_cmsg1), + "expectedError": error, + "comment": "invalid cmsg1: pubnonces list has an invalid prefix at index 0 (own pubnonce)", + } + ) + # --- Error test case: pubnonces list in cmsg1 has an invalid prefix at index 1 --- + invalid_cmsg1_parsed = copy.deepcopy(cmsg1_parsed) + invalid_cmsg1_parsed.enc_cmsg.pubnonces[1] = b"\xeb" * 33 # invalid prefix + invalid_cmsg1 = invalid_cmsg1_parsed.to_bytes() + error = expect_faulty_exception( + lambda: chilldkg.participant_step2( + hostseckeys[0], pstates1[0], invalid_cmsg1, aux_rand + ), + chilldkg.FaultyParticipantOrCoordinatorError, + 1, + ) + error_cases.append( + { + "cmsg1": bytes_to_hex(invalid_cmsg1), + "expectedError": error, + "comment": "invalid cmsg1: pubnonces list has an invalid prefix at index 1", + } + ) + # --- Error test case: pubnonces list in cmsg1 has an off-curve x-coordinate at index 0 (own pubnonce) --- + invalid_cmsg1_parsed = copy.deepcopy(cmsg1_parsed) + invalid_cmsg1_parsed.enc_cmsg.pubnonces[0] = ( + b"\x03" + 31 * b"\x00" + b"\x05" + ) # Invalid x-coordinate + invalid_cmsg1 = invalid_cmsg1_parsed.to_bytes() + error = expect_exception( + lambda: chilldkg.participant_step2( + hostseckeys[0], pstates1[0], invalid_cmsg1, aux_rand + ), + chilldkg.FaultyCoordinatorError, + ) + error_cases.append( + { + "cmsg1": bytes_to_hex(invalid_cmsg1), + "expectedError": error, + "comment": "invalid cmsg1: pubnonces list has an off-curve x-coordinate at index 0 (own pubnonce)", + } + ) + # --- Error test case: pubnonces list in cmsg1 has an off-curve x-coordinate at index 1 --- + invalid_cmsg1_parsed = copy.deepcopy(cmsg1_parsed) + invalid_cmsg1_parsed.enc_cmsg.pubnonces[1] = ( + b"\x03" + 31 * b"\x00" + b"\x05" + ) # Invalid x-coordinate + invalid_cmsg1 = invalid_cmsg1_parsed.to_bytes() + error = expect_faulty_exception( + lambda: chilldkg.participant_step2( + hostseckeys[0], pstates1[0], invalid_cmsg1, aux_rand + ), + chilldkg.FaultyParticipantOrCoordinatorError, + 1, + ) + error_cases.append( + { + "cmsg1": bytes_to_hex(invalid_cmsg1), + "expectedError": error, + "comment": "invalid cmsg1: pubnonces list has an off-curve x-coordinate at index 1", + } + ) + # --- Error test case: pubnonces list in cmsg1 has an arbitrary value at index 0 (own pubnonce) --- + invalid_cmsg1_parsed = copy.deepcopy(cmsg1_parsed) + invalid_cmsg1_parsed.enc_cmsg.pubnonces[0] = GE.lift_x( + ARBITRARY_POINT_X + ).to_bytes_compressed() + invalid_cmsg1 = invalid_cmsg1_parsed.to_bytes() + error = expect_exception( + lambda: chilldkg.participant_step2( + hostseckeys[0], pstates1[0], invalid_cmsg1, aux_rand + ), + chilldkg.FaultyCoordinatorError, + ) + error_cases.append( + { + "cmsg1": bytes_to_hex(invalid_cmsg1), + "expectedError": error, + "comment": "invalid cmsg1: pubnonces list has an arbitrary value at index 0 (own pubnonce)", + } + ) + # --- Error test case: pubnonces list in cmsg1 has an arbitrary value at index 1 --- + invalid_cmsg1_parsed = copy.deepcopy(cmsg1_parsed) + invalid_cmsg1_parsed.enc_cmsg.pubnonces[1] = GE.lift_x( + ARBITRARY_POINT_X + ).to_bytes_compressed() + invalid_cmsg1 = invalid_cmsg1_parsed.to_bytes() + error = expect_exception( + lambda: chilldkg.participant_step2( + hostseckeys[0], pstates1[0], invalid_cmsg1, aux_rand + ), + chilldkg.UnknownFaultyParticipantOrCoordinatorError, + ) + error_cases.append( + { + "cmsg1": bytes_to_hex(invalid_cmsg1), + "expectedError": error, + "comment": "invalid cmsg1: pubnonces list has an arbitrary value at index 1", + } + ) + # --- Error test case: pubnonces list in cmsg1 has an infinite value at index 0 (own pubnonce) --- + invalid_cmsg1_parsed = copy.deepcopy(cmsg1_parsed) + invalid_cmsg1_parsed.enc_cmsg.pubnonces[0] = b"\x00" * 33 # infinity + invalid_cmsg1 = invalid_cmsg1_parsed.to_bytes() + error = expect_exception( + lambda: chilldkg.participant_step2( + hostseckeys[0], pstates1[0], invalid_cmsg1, aux_rand + ), + chilldkg.FaultyCoordinatorError, + ) + error_cases.append( + { + "cmsg1": bytes_to_hex(invalid_cmsg1), + "expectedError": error, + "comment": "invalid cmsg1: pubnonces list has the infinity point at index 0 (own pubnonce)", + } + ) + # --- Error test case: pubnonces list in cmsg1 has an infinite value at index 1 --- + invalid_cmsg1_parsed = copy.deepcopy(cmsg1_parsed) + invalid_cmsg1_parsed.enc_cmsg.pubnonces[1] = b"\x00" * 33 # infinity + invalid_cmsg1 = invalid_cmsg1_parsed.to_bytes() + error = expect_faulty_exception( + lambda: chilldkg.participant_step2( + hostseckeys[0], pstates1[0], invalid_cmsg1, aux_rand + ), + chilldkg.FaultyParticipantOrCoordinatorError, + 1, + ) + error_cases.append( + { + "cmsg1": bytes_to_hex(invalid_cmsg1), + "expectedError": error, + "comment": "invalid cmsg1: pubnonces list has the infinity point at index 1", + } + ) + # --- Error test case: pubnonces list in cmsg1 has duplicate values --- + invalid_cmsg1_parsed = copy.deepcopy(cmsg1_parsed) + invalid_cmsg1_parsed.enc_cmsg.pubnonces[1] = ( + invalid_cmsg1_parsed.enc_cmsg.pubnonces[0] + ) + invalid_cmsg1 = invalid_cmsg1_parsed.to_bytes() + error = expect_exception( + lambda: chilldkg.participant_step2( + hostseckeys[0], pstates1[0], invalid_cmsg1, aux_rand + ), + chilldkg.UnknownFaultyParticipantOrCoordinatorError, + ) + error_cases.append( + { + "cmsg1": bytes_to_hex(invalid_cmsg1), + "expectedError": error, + "comment": "invalid cmsg1: pubnonces list has duplicate values", + } + ) + # --- Error test case: missing encrypted secret shares --- + invalid_cmsg1 = cmsg1[:-1] + error = expect_exception( + lambda: chilldkg.participant_step2( + hostseckeys[0], pstates1[0], invalid_cmsg1, aux_rand + ), + ValueError, + ) + error_cases.append( + { + "cmsg1": bytes_to_hex(invalid_cmsg1), + "expectedError": error, + "comment": "invalid cmsg1: missing encrypted secret shares", + } + ) + # --- Error test case: coms_to_secrets list in cmsg1 has an arbitrary value at index 0 --- + invalid_cmsg1_parsed = copy.deepcopy(cmsg1_parsed) + invalid_cmsg1_parsed.enc_cmsg.simpl_cmsg.coms_to_secrets[0] = GE.lift_x( + ARBITRARY_POINT_X + ) + invalid_cmsg1 = invalid_cmsg1_parsed.to_bytes() + error = expect_exception( + lambda: chilldkg.participant_step2( + hostseckeys[0], pstates1[0], invalid_cmsg1, aux_rand + ), + chilldkg.FaultyCoordinatorError, + ) + error_cases.append( + { + "cmsg1": bytes_to_hex(invalid_cmsg1), + "expectedError": error, + "comment": "invalid cmsg1: coms_to_secrets list has an arbitrary value at index 0", + } + ) + # --- Error test case: coms_to_secrets list in cmsg1 has infinity at index 1 --- + invalid_cmsg1_parsed = copy.deepcopy(cmsg1_parsed) + invalid_cmsg1_parsed.enc_cmsg.simpl_cmsg.coms_to_secrets[1] = GE() # infinity + invalid_cmsg1 = invalid_cmsg1_parsed.to_bytes() + error = expect_faulty_exception( + lambda: chilldkg.participant_step2( + hostseckeys[0], pstates1[0], invalid_cmsg1, aux_rand + ), + chilldkg.FaultyParticipantOrCoordinatorError, + 1, + ) + error_cases.append( + { + "cmsg1": bytes_to_hex(invalid_cmsg1), + "expectedError": error, + "comment": "invalid cmsg1: coms_to_secrets list has infinity at index 1", + } + ) + # --- Error test case: pop list in cmsg1 has an invalid value at index 1 --- + invalid_cmsg1_parsed = copy.deepcopy(cmsg1_parsed) + invalid_cmsg1_parsed.enc_cmsg.simpl_cmsg.pops[1] = bytes.fromhex( + "09C289578B96E6283AB13E4741FB489FC147FB1A5F446A314BA73C052131EFB04B83247A0BCEDF5205202AD64188B24B0BC5B51A17AEB218BD98DBE000C843B9" + ) # random 64 bytes (not a valid signature for any key) + invalid_cmsg1 = invalid_cmsg1_parsed.to_bytes() + error = expect_faulty_exception( + lambda: chilldkg.participant_step2( + hostseckeys[0], pstates1[0], invalid_cmsg1, aux_rand + ), + chilldkg.FaultyParticipantOrCoordinatorError, + 1, + ) + error_cases.append( + { + "cmsg1": bytes_to_hex(invalid_cmsg1), + "expectedError": error, + "comment": "invalid cmsg1: pop list has an invalid value at index 1", + } + ) + if t > 1: + # --- Error test case: sum_coms_to_nonconst_terms has an arbitrary value at index 0 --- + invalid_cmsg1_parsed = copy.deepcopy(cmsg1_parsed) + invalid_cmsg1_parsed.enc_cmsg.simpl_cmsg.sum_coms_to_nonconst_terms[0] = ( + GE.lift_x(ARBITRARY_POINT_X) + ) + invalid_cmsg1 = invalid_cmsg1_parsed.to_bytes() + error = expect_exception( + lambda: chilldkg.participant_step2( + hostseckeys[0], pstates1[0], invalid_cmsg1, aux_rand + ), + chilldkg.UnknownFaultyParticipantOrCoordinatorError, + ) + error_cases.append( + { + "cmsg1": bytes_to_hex(invalid_cmsg1), + "expectedError": error, + "comment": "invalid cmsg1: sum_coms_to_nonconst_terms has an arbitrary value at index 0", + } + ) + # --- Error test case: sum_coms_to_nonconst_terms has an infinite value at index 0 --- + invalid_cmsg1_parsed = copy.deepcopy(cmsg1_parsed) + invalid_cmsg1_parsed.enc_cmsg.simpl_cmsg.sum_coms_to_nonconst_terms[0] = ( + GE() # Infinity + ) + invalid_cmsg1 = invalid_cmsg1_parsed.to_bytes() + error = expect_exception( + lambda: chilldkg.participant_step2( + hostseckeys[0], pstates1[0], invalid_cmsg1, aux_rand + ), + chilldkg.UnknownFaultyParticipantOrCoordinatorError, + ) + error_cases.append( + { + "cmsg1": bytes_to_hex(invalid_cmsg1), + "expectedError": error, + "comment": "invalid cmsg1: sum_coms_to_nonconst_terms has the infinity point at index 0", + } + ) + # --- Error test case: Participant 1 sent an invalid secshare for participant 0 --- + invalid_pmsgs1 = copy.deepcopy(pmsgs1) + pmsgs11_parsed = chilldkg.ParticipantMsg1.from_bytes( + pmsgs1[1], t=params.t, n=len(params.hostpubkeys) + ) + pmsgs11_parsed.enc_pmsg.enc_shares[0] += Scalar(17) + invalid_pmsgs1[1] = pmsgs11_parsed.to_bytes() + _, invalid_cmsg1 = chilldkg.coordinator_step1(invalid_pmsgs1, params) + error = expect_exception( + lambda: chilldkg.participant_step2( + hostseckeys[0], pstates1[0], invalid_cmsg1, aux_rand + ), + chilldkg.UnknownFaultyParticipantOrCoordinatorError, + ) + error_cases.append( + { + "cmsg1": bytes_to_hex(invalid_cmsg1), + "expectedError": error, + "comment": "invalid cmsg1: participant 1 sent an invalid secshare for participant 0", + } + ) + + return { + "params": params_asdict(params), + "hostseckey": bytes_to_hex(hostseckeys[0]), + "random": bytes_to_hex(randoms[0]), + "auxRand": bytes_to_hex(aux_rand), + "pmsg1": bytes_to_hex(pmsgs1[0]), + "validTestCases": valid_cases, + "errorTestCases": error_cases, + } + + +def generate_participant_step2_vectors(): + groups = [generate_participant_step2_group(t, n) for t, n in THRESHOLD_CONFIGS] + total_tests = assign_tc_ids(groups) + return { + "description": PARTICIPANT_STEP2_DESCRIPTION, + "totalTests": total_tests, + "testGroups": groups, + } + + +PARTICIPANT_FINALIZE_DESCRIPTION = [ + "Test vectors for participant_finalize(pstate2, cmsg2).", + "Finalizes the DKG protocol from a participant's perspective.", + "Verifies the coordinator's certificate (cmsg2) and outputs the DKG result and recovery data.", + "", + "Harness setup (re-derive state through two prior rounds):", + " 1. Call participant_step1(hostseckey, params, random) to obtain (pstate1, pmsg1_out).", + " Assert pmsg1_out == pmsg1.", + " 2. Call participant_step2(hostseckey, pstate1, cmsg1, auxRand) to obtain (pstate2, pmsg2_out).", + " Assert pmsg2_out == pmsg2.", + "", + "For each valid test case:", + " Call participant_finalize(pstate2, cmsg2).", + " Verify the result matches expectedOutput (dkgOutput and recoveryData).", + "", + "For each error test case:", + " Call participant_finalize(pstate2, cmsg2).", + " Verify it raises an exception matching expectedError.", +] + + +def generate_participant_finalize_group(t, n): + hostseckeys = hex_list_to_bytes(HOSTSECKEYS_HEX[:n]) + hostpubkeys = [chilldkg.hostpubkey_gen(sk) for sk in hostseckeys] + params = chilldkg.SessionParams(hostpubkeys, t) + randoms = hex_list_to_bytes(RANDOMS_HEX[:n]) + assert len(randoms) == len(hostpubkeys) + aux_rand = bytes.fromhex(AUX_RAND_HEX) + pstates1 = [] + pmsgs1 = [] + for i in range(len(hostpubkeys)): + state, msg = chilldkg.participant_step1(hostseckeys[i], params, randoms[i]) + pstates1.append(state) + pmsgs1.append(msg) + cstate, cmsg1 = chilldkg.coordinator_step1(pmsgs1, params) + + pstates2 = [] + pmsgs2 = [] + for i in range(len(hostpubkeys)): + state, msg = chilldkg.participant_step2( + hostseckeys[i], pstates1[i], cmsg1, aux_rand + ) + pstates2.append(state) + pmsgs2.append(msg) + + valid_cases = [] + error_cases = [] + + vectors = { + "params": params_asdict(params), + "hostseckey": bytes_to_hex(hostseckeys[0]), + "random": bytes_to_hex(randoms[0]), + "auxRand": bytes_to_hex(aux_rand), + "pmsg1": bytes_to_hex(pmsgs1[0]), + "cmsg1": bytes_to_hex(cmsg1), + "pmsg2": bytes_to_hex(pmsgs2[0]), + } + + # --- Valid test case --- + cmsg2, _, _ = chilldkg.coordinator_finalize(cstate, pmsgs2) + pout, prec = chilldkg.participant_finalize(pstates2[0], cmsg2) + + valid_cases.append( + { + "cmsg2": bytes_to_hex(cmsg2), + "expectedOutput": { + "dkgOutput": dkg_output_asdict(pout), + "recoveryData": bytes_to_hex(prec), + }, + "comment": "valid participant finalize", + } + ) + + # --- Error test case: cmsg2 missing the last signature --- + invalid_cmsg2 = chilldkg.CoordinatorMsg2( + cmsg2[:-64] + ).to_bytes() # remove last signature + error = expect_exception( + lambda: chilldkg.participant_finalize(pstates2[0], invalid_cmsg2), + ValueError, + ) + error_cases.append( + { + "cmsg2": bytes_to_hex(invalid_cmsg2), + "expectedError": error, + "comment": "invalid cmsg2: length is invalid (missing last signature)", + } + ) + # --- Error test case: cmsg2 is too long --- + invalid_cmsg2 = chilldkg.CoordinatorMsg2(cmsg2 + bytes(64)).to_bytes() + error = expect_exception( + lambda: chilldkg.participant_finalize(pstates2[0], invalid_cmsg2), + ValueError, + ) + error_cases.append( + { + "cmsg2": bytes_to_hex(invalid_cmsg2), + "expectedError": error, + "comment": "invalid cmsg2: length is invalid (extra data appended)", + } + ) + + # --- Error test case: cmsg2 has invalid last signature --- + random_sig = bytes.fromhex( + "09C289578B96E6283AB13E4741FB489FC147FB1A5F446A314BA73C052131EFB04B83247A0BCEDF5205202AD64188B24B0BC5B51A17AEB218BD98DBE000C843B9" + ) + assert len(random_sig) == 64 + invalid_cmsg2_2 = chilldkg.CoordinatorMsg2(cmsg2[:-64] + random_sig).to_bytes() + error2 = expect_exception( + lambda: chilldkg.participant_finalize(pstates2[0], invalid_cmsg2_2), + chilldkg.FaultyCoordinatorError, + ) + error_cases.append( + { + "cmsg2": bytes_to_hex(invalid_cmsg2_2), + "expectedError": error2, + "comment": "invalid cmsg2: last signature is invalid", + } + ) + + return { + "params": vectors["params"], + "hostseckey": vectors["hostseckey"], + "random": vectors["random"], + "auxRand": vectors["auxRand"], + "pmsg1": vectors["pmsg1"], + "cmsg1": vectors["cmsg1"], + "pmsg2": vectors["pmsg2"], + "validTestCases": valid_cases, + "errorTestCases": error_cases, + } + + +def generate_participant_finalize_vectors(): + groups = [generate_participant_finalize_group(t, n) for t, n in THRESHOLD_CONFIGS] + total_tests = assign_tc_ids(groups) + return { + "description": PARTICIPANT_FINALIZE_DESCRIPTION, + "totalTests": total_tests, + "testGroups": groups, + } + + +PARTICIPANT_INVESTIGATE_DESCRIPTION = [ + "Test vectors for participant_investigate(error, cinv_msg).", + "Narrows down a faulty party after participant_step2 raised UnknownFaultyParticipantOrCoordinatorError.", + "This function always raises an exception (FaultyParticipantOrCoordinatorError or FaultyCoordinatorError).", + "", + "Harness setup:", + " 1. Call participant_step1(hostseckey, params, random) to obtain (pstate1, pmsg1_out).", + " Assert pmsg1_out == pmsg1.", + " 2. Per test case: look up cmsg1 from cmsg1Pool using cmsg1Index.", + " 3. Call participant_step2(hostseckey, pstate1, cmsg1, auxRand).", + " It must raise UnknownFaultyParticipantOrCoordinatorError. Capture that error object.", + " 4. Call participant_investigate(error, cinvMsg) and verify it raises expectedError.", + "", + "All test cases are error cases (this function never returns successfully).", + "Error objects contain 'type' and optionally 'participantId' (identifier of the blamed party).", +] + + +def generate_participant_investigate_group(t, n): + hostseckeys = hex_list_to_bytes(HOSTSECKEYS_HEX[:n]) + hostpubkeys = [chilldkg.hostpubkey_gen(sk) for sk in hostseckeys] + params = chilldkg.SessionParams(hostpubkeys, t) + randoms = hex_list_to_bytes(RANDOMS_HEX[:n]) + assert len(randoms) == len(hostpubkeys) + aux_rand = bytes.fromhex(AUX_RAND_HEX) + pstates1 = [] + pmsgs1 = [] + for i in range(len(hostpubkeys)): + state, msg = chilldkg.participant_step1(hostseckeys[i], params, randoms[i]) + pstates1.append(state) + pmsgs1.append(msg) + _, cmsg1 = chilldkg.coordinator_step1(pmsgs1, params) + + cmsg1_pool = [] + error_cases = [] + + # --- Error test case: Participant 1 sent an invalid secshare for participant 0 --- + invalid_pmsgs1 = copy.deepcopy(pmsgs1) + invalid_pmsg1_parsed = chilldkg.ParticipantMsg1.from_bytes( + invalid_pmsgs1[1], t=params.t, n=len(params.hostpubkeys) + ) + invalid_pmsg1_parsed.enc_pmsg.enc_shares[0] += Scalar(17) + invalid_pmsgs1[1] = invalid_pmsg1_parsed.to_bytes() + _, invalid_cmsg1 = chilldkg.coordinator_step1(invalid_pmsgs1, params) + try: + chilldkg.participant_step2(hostseckeys[0], pstates1[0], invalid_cmsg1, aux_rand) + except chilldkg.UnknownFaultyParticipantOrCoordinatorError as e: + cinv_msgs = chilldkg.coordinator_investigate(invalid_pmsgs1, params) + error = expect_faulty_exception( + lambda e=e: chilldkg.participant_investigate(e, cinv_msgs[0]), + chilldkg.FaultyParticipantOrCoordinatorError, + 1, + ) + else: + assert False, "Expected exception" + + cmsg1_pool.append(bytes_to_hex(invalid_cmsg1)) # index 0 + error_cases.append( + { + "cmsg1Index": 0, + "cinvMsg": bytes_to_hex(cinv_msgs[0]), + "expectedError": error, + "comment": "participant 1 sent an invalid secshare for participant 0", + } + ) + + # --- Error test case: Coordinator tampered with participant 0's encrypted secshare --- + cmsg1_parsed = chilldkg.CoordinatorMsg1.from_bytes( + cmsg1, t=params.t, n=len(params.hostpubkeys) + ) + invalid_cmsg1_parsed = copy.deepcopy(cmsg1_parsed) + invalid_cmsg1_parsed.enc_secshares[0] += Scalar(17) + invalid_cmsg1 = invalid_cmsg1_parsed.to_bytes() + + try: + chilldkg.participant_step2(hostseckeys[0], pstates1[0], invalid_cmsg1, aux_rand) + except chilldkg.UnknownFaultyParticipantOrCoordinatorError as e: + cinv_msgs = chilldkg.coordinator_investigate(pmsgs1, params) + error = expect_exception( + lambda e=e: chilldkg.participant_investigate(e, cinv_msgs[0]), + chilldkg.FaultyCoordinatorError, + ) + else: + assert False, "Expected exception" + + cmsg1_pool.append(bytes_to_hex(invalid_cmsg1)) # index 1 + error_cases.append( + { + "cmsg1Index": 1, + "cinvMsg": bytes_to_hex(cinv_msgs[0]), + "expectedError": error, + "comment": "coordinator tampered with participant 0's encrypted secshare", + } + ) + + # --- Error test case: Coordinator tampered with self-encrypted partial secshare --- + try: + # using the prior invalid_cmsg1 to trigger the error + chilldkg.participant_step2(hostseckeys[0], pstates1[0], invalid_cmsg1, aux_rand) + except chilldkg.UnknownFaultyParticipantOrCoordinatorError as e: + cinv_msgs = chilldkg.coordinator_investigate(pmsgs1, params) + cinv_msg_parsed = chilldkg.CoordinatorInvestigationMsg.from_bytes( + cinv_msgs[0], n=len(params.hostpubkeys) + ) + invalid_cinv_msg0_parsed = copy.deepcopy(cinv_msg_parsed) + invalid_cinv_msg0_parsed.enc_cinv.enc_partial_secshares[0] += Scalar( + 17 + ) # invalid share + invalid_cinv_msg0 = invalid_cinv_msg0_parsed.to_bytes() + error = expect_exception( + lambda e=e: chilldkg.participant_investigate(e, invalid_cinv_msg0), + chilldkg.FaultyCoordinatorError, + ) + else: + assert False, "Expected exception" + + error_cases.append( + { + "cmsg1Index": 1, + "cinvMsg": bytes_to_hex(invalid_cinv_msg0), + "expectedError": error, + "comment": "coordinator tampered with self-encrypted partial secshare (participant 0)", + } + ) + + # --- Error test case: partial pubshares list in cinv_msg has an arbitrary value at index 1 --- + try: + # using the prior invalid_cmsg1 to trigger the error + chilldkg.participant_step2(hostseckeys[0], pstates1[0], invalid_cmsg1, aux_rand) + except chilldkg.UnknownFaultyParticipantOrCoordinatorError as e: + cinv_msgs = chilldkg.coordinator_investigate(pmsgs1, params) + cinv_msg_parsed = chilldkg.CoordinatorInvestigationMsg.from_bytes( + cinv_msgs[0], n=len(params.hostpubkeys) + ) + invalid_cinv_msg0_parsed = copy.deepcopy(cinv_msg_parsed) + invalid_cinv_msg0_parsed.enc_cinv.partial_pubshares[1] = GE.lift_x( + ARBITRARY_POINT_X + ) + invalid_cinv_msg0 = invalid_cinv_msg0_parsed.to_bytes() + error = expect_exception( + lambda e=e: chilldkg.participant_investigate(e, invalid_cinv_msg0), + chilldkg.FaultyCoordinatorError, + ) + else: + assert False, "Expected exception" + + error_cases.append( + { + "cmsg1Index": 1, + "cinvMsg": bytes_to_hex(invalid_cinv_msg0), + "expectedError": error, + "comment": "partial pubshares list in cinv_msg has an arbitrary value at index 1", + } + ) + + # TODO: add runtime_error test case + + return { + "params": params_asdict(params), + "hostseckey": bytes_to_hex(hostseckeys[0]), + "random": bytes_to_hex(randoms[0]), + "auxRand": bytes_to_hex(aux_rand), + "pmsg1": bytes_to_hex(pmsgs1[0]), + "cmsg1Pool": cmsg1_pool, + "errorTestCases": error_cases, + } + + +def generate_participant_investigate_vectors(): + groups = [ + generate_participant_investigate_group(t, n) for t, n in THRESHOLD_CONFIGS + ] + total_tests = assign_tc_ids(groups) + return { + "description": PARTICIPANT_INVESTIGATE_DESCRIPTION, + "totalTests": total_tests, + "testGroups": groups, + } diff --git a/bip-chilldkg/python/gen_vector_utils/session.py b/bip-chilldkg/python/gen_vector_utils/session.py new file mode 100644 index 0000000000..f4389b6d32 --- /dev/null +++ b/bip-chilldkg/python/gen_vector_utils/session.py @@ -0,0 +1,437 @@ +from secp256k1lab.secp256k1 import Scalar +from secp256k1lab.util import bytes_from_int + +from chilldkg_ref import chilldkg + +from .fixtures import AUX_RAND_HEX, HOSTSECKEYS_HEX, RANDOMS_HEX +from .util import ( + assign_tc_ids, + bytes_to_hex, + dkg_output_asdict, + expect_exception, + hex_list_to_bytes, + params_asdict, +) + + +def generate_hostpubkey_vectors(): + description = [ + "Test vectors for hostpubkey_gen(hostseckey).", + "Generates a compressed public key (33 bytes) from a 32-byte host secret key.", + "", + "For each valid test case:", + " Call hostpubkey_gen(hostseckey) and verify the result equals expectedHostpubkey.", + "", + "For each error test case:", + " Call hostpubkey_gen(hostseckey) and verify it raises an exception matching expectedError.", + " The expectedError object contains 'type' (the exception class name).", + ] + valid_cases = [] + error_cases = [] + + # --- Valid test case --- + hostseckey = bytes.fromhex( + "631C047D50A67E45E27ED1FF25FCE179CAF059A2120D346ACD9774C1F2BAB66F" + ) + expected_pubkey = chilldkg.hostpubkey_gen(hostseckey) + valid_cases.append( + { + "hostseckey": bytes_to_hex(hostseckey), + "expectedHostpubkey": bytes_to_hex(expected_pubkey), + "comment": "valid host secret key", + } + ) + + # --- Error test case: Wrong length --- + short_hostseckey = bytes.fromhex("631C047D50A67E45E27ED1FF25FCE179") + assert len(short_hostseckey) == 16 + error = expect_exception( + lambda: chilldkg.hostpubkey_gen(short_hostseckey), ValueError + ) + error_cases.append( + { + "hostseckey": bytes_to_hex(short_hostseckey), + "expectedError": error, + "comment": "length of host secret key is not 32 bytes", + } + ) + # --- Error test case: Out-of-range hostseckey --- + invalid_hostseckey = bytes_from_int(Scalar.SIZE) + error = expect_exception( + lambda: chilldkg.hostpubkey_gen(invalid_hostseckey), chilldkg.HostSeckeyError + ) + error_cases.append( + { + "hostseckey": bytes_to_hex(invalid_hostseckey), + "expectedError": error, + "comment": "host secret key is out of range", + } + ) + # --- Error test case: zeroed hostseckey --- + zeroed_hostseckey = b"\x00" * 32 + error = expect_exception( + lambda: chilldkg.hostpubkey_gen(zeroed_hostseckey), chilldkg.HostSeckeyError + ) + error_cases.append( + { + "hostseckey": bytes_to_hex(zeroed_hostseckey), + "expectedError": error, + "comment": "zeroed host secret key", + } + ) + + groups = [{"validTestCases": valid_cases, "errorTestCases": error_cases}] + total_tests = assign_tc_ids(groups) + return { + "description": description, + "totalTests": total_tests, + "validTestCases": valid_cases, + "errorTestCases": error_cases, + } + + +def generate_params_hash_vectors(): + description = [ + "Test vectors for params_hash(params).", + "Computes a 32-byte hash of the session parameters (hostpubkeys, threshold).", + "", + "For each valid test case:", + " Call params_hash(params) and verify the result equals expectedParamsHash.", + "", + "For each error test case:", + " Call params_hash(params) and verify it raises an exception matching expectedError.", + " The expectedError object contains 'type' (the exception class name).", + " Some errors include 'participantId' (identifier of the blamed participant)", + " or 'participantId1'/'participantId2' (identifiers of participants with duplicate keys).", + ] + valid_cases = [] + error_cases = [] + hostseckeys = hex_list_to_bytes(HOSTSECKEYS_HEX[:3]) + hostpubkeys = [chilldkg.hostpubkey_gen(sk) for sk in hostseckeys] + + # --- Valid test cases --- + cases = [ + {"t": 2, "comment": "standard 2-of-3 threshold"}, + {"t": 1, "comment": "min threshold value"}, + {"t": len(hostpubkeys), "comment": "max threshold value"}, + ] + + for case in cases: + t = case["t"] + params = chilldkg.SessionParams(hostpubkeys, t) + expected_params_hash = chilldkg.params_hash(params) + test_case = { + "params": params_asdict(params), + "expectedParamsHash": bytes_to_hex(expected_params_hash), + "comment": case["comment"], + } + valid_cases.append(test_case) + + # --- Error test case: Invalid threshold --- + t = 0 + invalid_params = chilldkg.SessionParams(hostpubkeys, t) + error = expect_exception( + lambda: chilldkg.params_hash(invalid_params), chilldkg.ThresholdOrCountError + ) + error_cases.append( + { + "params": params_asdict(invalid_params), + "expectedError": error, + "comment": "invalid threshold value", + } + ) + # --- Error test case: hostpubkeys list contains an invalid value --- + invalid_hostpubkey = b"\x03" + 31 * b"\x00" + b"\x05" # Invalid x-coordinate + t = 2 + with_invalid = [hostpubkeys[0], invalid_hostpubkey, hostpubkeys[2]] + invalid_params = chilldkg.SessionParams(with_invalid, t) + error = expect_exception( + lambda: chilldkg.params_hash(invalid_params), chilldkg.InvalidHostPubkeyError + ) + error_cases.append( + { + "params": params_asdict(invalid_params), + "expectedError": error, + "comment": "hostpubkeys list contains an invalid value", + } + ) + # --- Error test case: hostpubkeys list contains duplicate values --- + t = 2 + with_duplicate = [hostpubkeys[0], hostpubkeys[1], hostpubkeys[2], hostpubkeys[1]] + duplicate_params = chilldkg.SessionParams(with_duplicate, t) + error = expect_exception( + lambda: chilldkg.params_hash(duplicate_params), + chilldkg.DuplicateHostPubkeyError, + ) + error_cases.append( + { + "params": params_asdict(duplicate_params), + "expectedError": error, + "comment": "hostpubkeys list contains duplicate values", + } + ) + + groups = [{"validTestCases": valid_cases, "errorTestCases": error_cases}] + total_tests = assign_tc_ids(groups) + return { + "description": description, + "totalTests": total_tests, + "validTestCases": valid_cases, + "errorTestCases": error_cases, + } + + +def generate_recover_vectors(): + description = [ + "Test vectors for participant_recover(hostseckey, recovery_data) and", + "coordinator_recover(recovery_data).", + "Recovers a DKG output and session parameters from serialized recovery data.", + "If hostseckey is null, recovery is performed as coordinator (secshare will be null).", + "If hostseckey is a 32-byte hex string, recovery is performed as the corresponding participant.", + "", + "For each valid test case:", + " Call participant_recover(hostseckey, recoveryData) or coordinator_recover(recoveryData)", + " (if hostseckey is null) and verify the result matches expectedOutput.", + " expectedOutput contains 'dkgOutput' (with secshare, threshPk, pubshares)", + " and 'params' (with hostpubkeys, t).", + "", + "For each error test case:", + " Call participant_recover(hostseckey, recoveryData) or coordinator_recover(recoveryData)", + " (if hostseckey is null) and verify it raises an exception matching expectedError.", + ] + valid_cases = [] + error_cases = [] + + hostseckeys = hex_list_to_bytes(HOSTSECKEYS_HEX[:3]) + hostpubkeys = [chilldkg.hostpubkey_gen(sk) for sk in hostseckeys] + params = chilldkg.SessionParams(hostpubkeys, 2) + randoms = hex_list_to_bytes(RANDOMS_HEX[:3]) + assert len(randoms) == len(hostpubkeys) + pstates1 = [] + pmsgs1 = [] + for i in range(len(hostpubkeys)): + state, msg = chilldkg.participant_step1(hostseckeys[i], params, randoms[i]) + pstates1.append(state) + pmsgs1.append(msg) + cstate, cmsg1 = chilldkg.coordinator_step1(pmsgs1, params) + + aux_rand = bytes.fromhex(AUX_RAND_HEX) + pstates2 = [] + pmsgs2 = [] + for i in range(len(hostpubkeys)): + state, msg = chilldkg.participant_step2( + hostseckeys[i], pstates1[i], cmsg1, aux_rand + ) + pstates2.append(state) + pmsgs2.append(msg) + cmsg2, cout, crec = chilldkg.coordinator_finalize(cstate, pmsgs2) + pout, prec = chilldkg.participant_finalize(pstates2[0], cmsg2) + assert prec == crec + + # --- Valid test case: participant recovery --- + pout_rec, params_rec = chilldkg.participant_recover(hostseckeys[0], prec) + assert pout_rec == pout + assert params_rec == params + valid_cases.append( + { + "hostseckey": bytes_to_hex(hostseckeys[0]), + "recoveryData": bytes_to_hex(prec), + "expectedOutput": { + "dkgOutput": dkg_output_asdict(pout_rec), + "params": params_asdict(params_rec), + }, + "comment": "participant recovery", + } + ) + # --- Valid test case: coordinator recovery --- + cout_rec, params_rec = chilldkg.coordinator_recover(crec) + assert cout_rec == cout + assert params_rec == params + valid_cases.append( + { + "hostseckey": None, + "recoveryData": bytes_to_hex(crec), + "expectedOutput": { + "dkgOutput": dkg_output_asdict(cout_rec), + "params": params_asdict(params_rec), + }, + "comment": "coordinator recovery", + } + ) + + # --- Error test case: recovery data of invalid length --- + invalid_crec = crec[1:] + error = expect_exception( + lambda: chilldkg.coordinator_recover(invalid_crec), chilldkg.RecoveryDataError + ) + error_cases.append( + { + "hostseckey": None, + "recoveryData": bytes_to_hex(invalid_crec), + "expectedError": error, + "comment": "recovery data of invalid length", + } + ) + # --- Error test case: first coefficient of sum_coms is invalid --- + invalid_ge = b"\x03" + 31 * b"\x00" + b"\x05" # Invalid x-coordinate + invalid_crec = crec[:4] + invalid_ge + crec[4 + 33 :] + error = expect_exception( + lambda: chilldkg.coordinator_recover(invalid_crec), chilldkg.RecoveryDataError + ) + error_cases.append( + { + "hostseckey": None, + "recoveryData": bytes_to_hex(invalid_crec), + "expectedError": error, + "comment": "first coefficient of sum_coms is invalid", + } + ) + # --- Error test case: last share in enc_secshare list is out of range --- + n = len(hostpubkeys) + cert_len = chilldkg.certeq_cert_len(n) + invalid_encshare = bytes.fromhex( + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141" + ) + invalid_crec = crec[: -cert_len - 32] + invalid_encshare + crec[-cert_len:] + error = expect_exception( + lambda: chilldkg.coordinator_recover(invalid_crec), chilldkg.RecoveryDataError + ) + error_cases.append( + { + "hostseckey": None, + "recoveryData": bytes_to_hex(invalid_crec), + "expectedError": error, + "comment": "last share in enc_secshare list is invalid", + } + ) + # --- Error test case: invalid threshold --- + t = params.t + invalid_crec = b"\x00" * 4 + crec[4 + 33 * t :] + error = expect_exception( + lambda: chilldkg.coordinator_recover(invalid_crec), chilldkg.RecoveryDataError + ) + error_cases.append( + { + "hostseckey": None, + "recoveryData": bytes_to_hex(invalid_crec), + "expectedError": error, + "comment": "invalid threshold", + } + ) + # --- Error test case: first pubkey in the hostpubkey list is invalid --- + invalid_ge = b"\x03" + 31 * b"\x00" + b"\x05" + invalid_crec = crec[: 4 + 33 * t] + invalid_ge + crec[4 + 33 * t + 33 :] + error = expect_exception( + lambda: chilldkg.coordinator_recover(invalid_crec), chilldkg.RecoveryDataError + ) + error_cases.append( + { + "hostseckey": None, + "recoveryData": bytes_to_hex(invalid_crec), + "expectedError": error, + "comment": "first pubkey in the hostpubkey list is invalid", + } + ) + # --- Error test case: last pubnonce in the pubnonces list was tampered with --- + n = len(hostpubkeys) + cert_len = chilldkg.certeq_cert_len(n) + rand_ge = bytes.fromhex( + "03421F5FC9A21065445C96FDB91C0C1E2F2431741C72713B4B99DDCB316F31E9FC" + ) + invalid_crec = ( + crec[: -cert_len - 32 * n - 33] + rand_ge + crec[-cert_len - 32 * n :] + ) + error = expect_exception( + lambda: chilldkg.coordinator_recover(invalid_crec), chilldkg.RecoveryDataError + ) + error_cases.append( + { + "hostseckey": None, + "recoveryData": bytes_to_hex(invalid_crec), + "expectedError": error, + "comment": "last pubnonce in the pubnonces list was tampered with (doesn't match signed certificate)", + } + ) + # --- Error test case: last signature in the certificate is invalid --- + rand_sig = bytes.fromhex( + "09C289578B96E6283AB13E4741FB489FC147FB1A5F446A314BA73C052131EFB04B83247A0BCEDF5205202AD64188B24B0BC5B51A17AEB218BD98DBE000C843B9" + ) + invalid_crec = crec[:-64] + rand_sig + error = expect_exception( + lambda: chilldkg.coordinator_recover(invalid_crec), chilldkg.RecoveryDataError + ) + error_cases.append( + { + "hostseckey": None, + "recoveryData": bytes_to_hex(invalid_crec), + "expectedError": error, + "comment": "last signature in the certificate is invalid", + } + ) + # --- Error test case: invalid hostseckey --- + short_hostseckey = bytes.fromhex("631C047D50A67E45E27ED1FF25FCE179") + assert len(short_hostseckey) == 16 + error = expect_exception( + lambda: chilldkg.participant_recover(short_hostseckey, crec), ValueError + ) + error_cases.append( + { + "hostseckey": bytes_to_hex(short_hostseckey), + "recoveryData": bytes_to_hex(crec), + "expectedError": error, + "comment": "invalid hostseckey", + } + ) + # --- Error test case: hostseckey doesn't match any hostpubkey --- + rand_hostseckey = bytes.fromhex( + "759DE9306FB02B3D84C455112BF1F3360401DC383ECD1FCEDE59EC809D6F9FE7" + ) + error = expect_exception( + lambda: chilldkg.participant_recover(rand_hostseckey, crec), + chilldkg.HostSeckeyError, + ) + error_cases.append( + { + "hostseckey": bytes_to_hex(rand_hostseckey), + "recoveryData": bytes_to_hex(crec), + "expectedError": error, + "comment": "host secret key doesn't match any hostpubkey", + } + ) + # --- Error test case: zero hostseckey --- + zero_hostseckey = b"\x00" * 32 + error = expect_exception( + lambda: chilldkg.participant_recover(zero_hostseckey, crec), + chilldkg.HostSeckeyError, + ) + error_cases.append( + { + "hostseckey": bytes_to_hex(zero_hostseckey), + "recoveryData": bytes_to_hex(crec), + "expectedError": error, + "comment": "host secret key is zero", + } + ) + # --- Error test case: out-of-range hostseckey --- + overflow_hostseckey = bytes_from_int(Scalar.SIZE) + error = expect_exception( + lambda: chilldkg.participant_recover(overflow_hostseckey, crec), + chilldkg.HostSeckeyError, + ) + error_cases.append( + { + "hostseckey": bytes_to_hex(overflow_hostseckey), + "recoveryData": bytes_to_hex(crec), + "expectedError": error, + "comment": "host secret key is out of range", + } + ) + + groups = [{"validTestCases": valid_cases, "errorTestCases": error_cases}] + total_tests = assign_tc_ids(groups) + return { + "description": description, + "totalTests": total_tests, + "validTestCases": valid_cases, + "errorTestCases": error_cases, + } diff --git a/bip-chilldkg/python/gen_vector_utils/util.py b/bip-chilldkg/python/gen_vector_utils/util.py new file mode 100644 index 0000000000..1cfbac0503 --- /dev/null +++ b/bip-chilldkg/python/gen_vector_utils/util.py @@ -0,0 +1,121 @@ +from __future__ import annotations + +import json +from pathlib import Path +from typing import TypeAlias + +from chilldkg_ref import chilldkg, encpedpop + +ErrorInfo: TypeAlias = "dict[str, int | str | ErrorInfo]" + + +def bytes_to_hex(data: bytes) -> str: + return data.hex().upper() + + +def bytes_list_to_hex(lst: list[bytes]) -> list[str]: + return [l_i.hex().upper() for l_i in lst] + + +def hex_list_to_bytes(lst: list[str]) -> list[bytes]: + return [bytes.fromhex(l_i) for l_i in lst] + + +def write_json(filename: Path, data: dict) -> None: + with open(filename, "w") as f: + json.dump(data, f, indent=4) + + +def exception_asdict(e: Exception) -> dict: + error_info: ErrorInfo = {"type": e.__class__.__name__} + + for key, value in e.__dict__.items(): + if isinstance(value, (str, int)): + error_info[key] = value + elif isinstance(value, bytes): + error_info[key] = bytes_to_hex(value) + elif isinstance(value, encpedpop.ParticipantInvestigationData): + continue + else: + raise NotImplementedError( + f"Conversion for type {type(value).__name__} is not implemented" + ) + + # the last argument might contain the error message + if len(e.args) > 0 and isinstance(e.args[-1], str): + error_info.setdefault("message", e.args[-1]) + + # Update snake case keys into camel case keys to match the JSON vector format + for key in list(error_info.keys()): + if "_" in key: + camel_case_key = "".join( + word.capitalize() if i > 0 else word + for i, word in enumerate(key.split("_")) + ) + error_info[camel_case_key] = error_info.pop(key) + + return error_info + + +def expect_exception(try_fn, expected_exception): + try: + try_fn() + except expected_exception as e: + return exception_asdict(e) + except Exception as e: + raise AssertionError(f"Wrong exception raised: {type(e).__name__}") + else: + raise AssertionError("Expected exception") + + +def expect_faulty_exception(try_fn, expected_exception, expected_participant_id): + error = expect_exception(try_fn, expected_exception) + actual = error.get("participantId") + assert actual == expected_participant_id, ( + f"expected faulty participant {expected_participant_id}, got {actual}" + ) + return error + + +def params_asdict(params: chilldkg.SessionParams) -> dict: + return {"hostpubkeys": bytes_list_to_hex(params.hostpubkeys), "t": params.t} + + +def dkg_output_asdict(dkg_output: chilldkg.DKGOutput) -> dict: + secshare = bytes_to_hex(dkg_output.secshare) if dkg_output.secshare else None + return { + "secshare": secshare, + "threshPk": bytes_to_hex(dkg_output.thresh_pk), + "pubshares": bytes_list_to_hex(dkg_output.pubshares), + } + + +def assign_tc_ids(groups): + tc_id = 1 + for group in groups: + for key in ("validTestCases", "errorTestCases"): + for i, case in enumerate(group.get(key, [])): + assert "tcId" not in case + group[key][i] = {"tcId": tc_id, **case} + tc_id += 1 + return tc_id - 1 + + +# functions below are used to test JSON vectors with chilldkg_ref +# in tests.py + + +def assert_raises(try_fn, expected_error: dict): + try: + try_fn() + except Exception as e: + assert expected_error == exception_asdict(e) + else: + raise AssertionError("Expected exception") + + +def params_from_dict(params: dict) -> chilldkg.SessionParams: + return chilldkg.SessionParams( + hex_list_to_bytes(params["hostpubkeys"]), + params["t"], + ) diff --git a/bip-chilldkg/python/gen_vectors.py b/bip-chilldkg/python/gen_vectors.py new file mode 100755 index 0000000000..e219c3ace9 --- /dev/null +++ b/bip-chilldkg/python/gen_vectors.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python3 + +""" +Driver script for generating test vector JSON files. +Outputs are saved in the "../vectors/" directory. +""" + +from pathlib import Path + +from gen_vector_utils import util +from gen_vector_utils.coordinator import ( + generate_coordinator_finalize_vectors, + generate_coordinator_investigate_vectors, + generate_coordinator_step1_vectors, +) +from gen_vector_utils.participant import ( + generate_participant_finalize_vectors, + generate_participant_investigate_vectors, + generate_participant_step1_vectors, + generate_participant_step2_vectors, +) +from gen_vector_utils.session import ( + generate_hostpubkey_vectors, + generate_params_hash_vectors, + generate_recover_vectors, +) + +output_dir = Path(__file__).parent.parent / "vectors" +output_dir.mkdir(parents=True, exist_ok=True) + +util.write_json( + output_dir / "hostpubkey_gen_vectors.json", generate_hostpubkey_vectors() +) +util.write_json(output_dir / "params_hash_vectors.json", generate_params_hash_vectors()) +util.write_json(output_dir / "recover_vectors.json", generate_recover_vectors()) +util.write_json( + output_dir / "participant_step1_vectors.json", + generate_participant_step1_vectors(), +) +util.write_json( + output_dir / "participant_step2_vectors.json", + generate_participant_step2_vectors(), +) +util.write_json( + output_dir / "participant_finalize_vectors.json", + generate_participant_finalize_vectors(), +) +util.write_json( + output_dir / "participant_investigate_vectors.json", + generate_participant_investigate_vectors(), +) +util.write_json( + output_dir / "coordinator_step1_vectors.json", + generate_coordinator_step1_vectors(), +) +util.write_json( + output_dir / "coordinator_finalize_vectors.json", + generate_coordinator_finalize_vectors(), +) +util.write_json( + output_dir / "coordinator_investigate_vectors.json", + generate_coordinator_investigate_vectors(), +) +print("Generated test vectors successfully") diff --git a/bip-chilldkg/python/mypy.ini b/bip-chilldkg/python/mypy.ini new file mode 100644 index 0000000000..aca9393d71 --- /dev/null +++ b/bip-chilldkg/python/mypy.ini @@ -0,0 +1,4 @@ +[mypy] +# Include path to vendored copy of secp256k1lab, in order to +# avoid "import-not-found" errors in mypy's `--strict` mode +mypy_path = $MYPY_CONFIG_FILE_DIR/secp256k1lab/src diff --git a/bip-chilldkg/python/secp256k1lab/.github/workflows/main.yml b/bip-chilldkg/python/secp256k1lab/.github/workflows/main.yml new file mode 100644 index 0000000000..fb05230b3c --- /dev/null +++ b/bip-chilldkg/python/secp256k1lab/.github/workflows/main.yml @@ -0,0 +1,34 @@ +name: Tests +on: [push, pull_request] +jobs: + ruff: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install the latest version of uv + uses: astral-sh/setup-uv@v5 + - run: uvx ruff check . + mypy: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.11", "3.12", "3.13", "3.14"] + steps: + - uses: actions/checkout@v4 + - name: Install the latest version of uv, setup Python ${{ matrix.python-version }} + uses: astral-sh/setup-uv@v5 + with: + python-version: ${{ matrix.python-version }} + - run: uvx mypy . + unittest: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.11", "3.12", "3.13", "3.14"] + steps: + - uses: actions/checkout@v4 + - name: Setup Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + - run: python3 -m unittest diff --git a/bip-chilldkg/python/secp256k1lab/.gitignore b/bip-chilldkg/python/secp256k1lab/.gitignore new file mode 100644 index 0000000000..505a3b1ca2 --- /dev/null +++ b/bip-chilldkg/python/secp256k1lab/.gitignore @@ -0,0 +1,10 @@ +# Python-generated files +__pycache__/ +*.py[oc] +build/ +dist/ +wheels/ +*.egg-info + +# Virtual environments +.venv diff --git a/bip-chilldkg/python/secp256k1lab/.python-version b/bip-chilldkg/python/secp256k1lab/.python-version new file mode 100644 index 0000000000..2c0733315e --- /dev/null +++ b/bip-chilldkg/python/secp256k1lab/.python-version @@ -0,0 +1 @@ +3.11 diff --git a/bip-chilldkg/python/secp256k1lab/CHANGELOG.md b/bip-chilldkg/python/secp256k1lab/CHANGELOG.md new file mode 100644 index 0000000000..4c756d3695 --- /dev/null +++ b/bip-chilldkg/python/secp256k1lab/CHANGELOG.md @@ -0,0 +1,25 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +#### Added + - Added new methods `Scalar.from_int_nonzero_checked` and `Scalar.from_bytes_nonzero_checked` + that ensure a constructed scalar is in the range `0 < s < N` (i.e. is non-zero and within the + group order) and throw a `ValueError` otherwise. This is e.g. useful for ensuring that newly + generated secret keys or nonces are valid without having to do the non-zero check manually. + The already existing methods `Scalar.from_int_checked` and `Scalar.from_bytes_checked` error + on overflow, but not on zero, i.e. they only ensure `0 <= s < N`. + + - Added a new method `GE.from_bytes_compressed_with_infinity` to parse a compressed + public key (33 bytes) to a group element, where the all-zeros bytestring maps to the + point at infinity. This is the counterpart to the already existing serialization + method `GE.to_bytes_compressed_with_infinity`. + +## [1.0.0] - 2025-03-31 + +Initial release. diff --git a/bip-chilldkg/python/secp256k1lab/COPYING b/bip-chilldkg/python/secp256k1lab/COPYING new file mode 100644 index 0000000000..e8f2163641 --- /dev/null +++ b/bip-chilldkg/python/secp256k1lab/COPYING @@ -0,0 +1,23 @@ +The MIT License (MIT) + +Copyright (c) 2009-2024 The Bitcoin Core developers +Copyright (c) 2009-2024 Bitcoin Developers +Copyright (c) 2025- The secp256k1lab Developers + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/bip-chilldkg/python/secp256k1lab/README.md b/bip-chilldkg/python/secp256k1lab/README.md new file mode 100644 index 0000000000..dbc9dbd04c --- /dev/null +++ b/bip-chilldkg/python/secp256k1lab/README.md @@ -0,0 +1,13 @@ +secp256k1lab +============ + +![Dependencies: None](https://img.shields.io/badge/dependencies-none-success) + +An INSECURE implementation of the secp256k1 elliptic curve and related cryptographic schemes written in Python, intended for prototyping, experimentation and education. + +Features: +* Low-level secp256k1 field and group arithmetic. +* Schnorr signing/verification and key generation according to [BIP-340](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki). +* ECDH key exchange. + +WARNING: The code in this library is slow and trivially vulnerable to side channel attacks. diff --git a/bip-chilldkg/python/secp256k1lab/pyproject.toml b/bip-chilldkg/python/secp256k1lab/pyproject.toml new file mode 100644 index 0000000000..68b927b384 --- /dev/null +++ b/bip-chilldkg/python/secp256k1lab/pyproject.toml @@ -0,0 +1,34 @@ +[project] +name = "secp256k1lab" +version = "1.0.0" +description = "An INSECURE implementation of the secp256k1 elliptic curve and related cryptographic schemes, intended for prototyping, experimentation and education" +readme = "README.md" +authors = [ + { name = "Pieter Wuille", email = "pieter@wuille.net" }, + { name = "Tim Ruffing", email = "me@real-or-random.org" }, + { name = "Jonas Nick", email = "jonasd.nick@gmail.com" }, + { name = "Sebastian Falbesoner", email = "sebastian.falbesoner@gmail.com" } +] +maintainers = [ + { name = "Tim Ruffing", email = "me@real-or-random.org" }, + { name = "Jonas Nick", email = "jonasd.nick@gmail.com" }, + { name = "Sebastian Falbesoner", email = "sebastian.falbesoner@gmail.com" } +] +requires-python = ">=3.11" +license = "MIT" +license-files = ["COPYING"] +keywords = ["secp256k1", "elliptic curves", "cryptography", "Bitcoin"] +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "Intended Audience :: Education", + "Intended Audience :: Science/Research", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python", + "Topic :: Security :: Cryptography", +] +dependencies = [] + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" diff --git a/bip-chilldkg/python/secp256k1lab/src/secp256k1lab/__init__.py b/bip-chilldkg/python/secp256k1lab/src/secp256k1lab/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/bip-chilldkg/python/secp256k1lab/src/secp256k1lab/bip340.py b/bip-chilldkg/python/secp256k1lab/src/secp256k1lab/bip340.py new file mode 100644 index 0000000000..ba839d16e1 --- /dev/null +++ b/bip-chilldkg/python/secp256k1lab/src/secp256k1lab/bip340.py @@ -0,0 +1,73 @@ +# The following functions are based on the BIP 340 reference implementation: +# https://github.com/bitcoin/bips/blob/master/bip-0340/reference.py + +from .secp256k1 import FE, GE, G +from .util import int_from_bytes, bytes_from_int, xor_bytes, tagged_hash + + +def pubkey_gen(seckey: bytes) -> bytes: + d0 = int_from_bytes(seckey) + if not (1 <= d0 <= GE.ORDER - 1): + raise ValueError("The secret key must be an integer in the range 1..n-1.") + P = d0 * G + assert not P.infinity + return P.to_bytes_xonly() + + +def schnorr_sign( + msg: bytes, seckey: bytes, aux_rand: bytes, tag_prefix: str = "BIP0340" +) -> bytes: + d0 = int_from_bytes(seckey) + if not (1 <= d0 <= GE.ORDER - 1): + raise ValueError("The secret key must be an integer in the range 1..n-1.") + if len(aux_rand) != 32: + raise ValueError("aux_rand must be 32 bytes instead of %i." % len(aux_rand)) + P = d0 * G + assert not P.infinity + d = d0 if P.has_even_y() else GE.ORDER - d0 + t = xor_bytes(bytes_from_int(d), tagged_hash(tag_prefix + "/aux", aux_rand)) + k0 = ( + int_from_bytes(tagged_hash(tag_prefix + "/nonce", t + P.to_bytes_xonly() + msg)) + % GE.ORDER + ) + if k0 == 0: + raise RuntimeError("Failure. This happens only with negligible probability.") + R = k0 * G + assert not R.infinity + k = k0 if R.has_even_y() else GE.ORDER - k0 + e = ( + int_from_bytes( + tagged_hash( + tag_prefix + "/challenge", R.to_bytes_xonly() + P.to_bytes_xonly() + msg + ) + ) + % GE.ORDER + ) + sig = R.to_bytes_xonly() + bytes_from_int((k + e * d) % GE.ORDER) + assert schnorr_verify(msg, P.to_bytes_xonly(), sig, tag_prefix=tag_prefix) + return sig + + +def schnorr_verify( + msg: bytes, pubkey: bytes, sig: bytes, tag_prefix: str = "BIP0340" +) -> bool: + if len(pubkey) != 32: + raise ValueError("The public key must be a 32-byte array.") + if len(sig) != 64: + raise ValueError("The signature must be a 64-byte array.") + try: + P = GE.from_bytes_xonly(pubkey) + except ValueError: + return False + r = int_from_bytes(sig[0:32]) + s = int_from_bytes(sig[32:64]) + if (r >= FE.SIZE) or (s >= GE.ORDER): + return False + e = ( + int_from_bytes(tagged_hash(tag_prefix + "/challenge", sig[0:32] + pubkey + msg)) + % GE.ORDER + ) + R = s * G - e * P + if R.infinity or (not R.has_even_y()) or (R.x != r): + return False + return True diff --git a/bip-chilldkg/python/secp256k1lab/src/secp256k1lab/ecdh.py b/bip-chilldkg/python/secp256k1lab/src/secp256k1lab/ecdh.py new file mode 100644 index 0000000000..73f47fa1a7 --- /dev/null +++ b/bip-chilldkg/python/secp256k1lab/src/secp256k1lab/ecdh.py @@ -0,0 +1,16 @@ +import hashlib + +from .secp256k1 import GE, Scalar + + +def ecdh_compressed_in_raw_out(seckey: bytes, pubkey: bytes) -> GE: + """TODO""" + shared_secret = Scalar.from_bytes_checked(seckey) * GE.from_bytes_compressed(pubkey) + assert not shared_secret.infinity # prime-order group + return shared_secret + + +def ecdh_libsecp256k1(seckey: bytes, pubkey: bytes) -> bytes: + """TODO""" + shared_secret = ecdh_compressed_in_raw_out(seckey, pubkey) + return hashlib.sha256(shared_secret.to_bytes_compressed()).digest() diff --git a/bip-chilldkg/python/secp256k1lab/src/secp256k1lab/keys.py b/bip-chilldkg/python/secp256k1lab/src/secp256k1lab/keys.py new file mode 100644 index 0000000000..3e28897e99 --- /dev/null +++ b/bip-chilldkg/python/secp256k1lab/src/secp256k1lab/keys.py @@ -0,0 +1,15 @@ +from .secp256k1 import GE, G +from .util import int_from_bytes + +# The following function is based on the BIP 327 reference implementation +# https://github.com/bitcoin/bips/blob/master/bip-0327/reference.py + + +# Return the plain public key corresponding to a given secret key +def pubkey_gen_plain(seckey: bytes) -> bytes: + d0 = int_from_bytes(seckey) + if not (1 <= d0 <= GE.ORDER - 1): + raise ValueError("The secret key must be an integer in the range 1..n-1.") + P = d0 * G + assert not P.infinity + return P.to_bytes_compressed() diff --git a/bip-chilldkg/python/secp256k1lab/src/secp256k1lab/py.typed b/bip-chilldkg/python/secp256k1lab/src/secp256k1lab/py.typed new file mode 100644 index 0000000000..e69de29bb2 diff --git a/bip-chilldkg/python/secp256k1lab/src/secp256k1lab/secp256k1.py b/bip-chilldkg/python/secp256k1lab/src/secp256k1lab/secp256k1.py new file mode 100644 index 0000000000..0526878d91 --- /dev/null +++ b/bip-chilldkg/python/secp256k1lab/src/secp256k1lab/secp256k1.py @@ -0,0 +1,483 @@ +# Copyright (c) 2022-2023 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +"""Test-only implementation of low-level secp256k1 field and group arithmetic + +It is designed for ease of understanding, not performance. + +WARNING: This code is slow and trivially vulnerable to side channel attacks. Do not use for +anything but tests. + +Exports: +* FE: class for secp256k1 field elements +* GE: class for secp256k1 group elements +* G: the secp256k1 generator point +""" + +from __future__ import annotations +from typing import Self + +# TODO Docstrings of methods still say "field element" +class APrimeFE: + """Objects of this class represent elements of a prime field. + + They are represented internally in numerator / denominator form, in order to delay inversions. + """ + + # The size of the field (also its modulus and characteristic). + SIZE: int + + def __init__(self, a: int | Self = 0, b: int | Self = 1) -> None: + """Initialize a field element a/b; both a and b can be ints or field elements.""" + if isinstance(a, type(self)): + num = a._num + den = a._den + else: + assert isinstance(a, int) + num = a % self.SIZE + den = 1 + if isinstance(b, type(self)): + den = (den * b._num) % self.SIZE + num = (num * b._den) % self.SIZE + else: + assert isinstance(b, int) + den = (den * b) % self.SIZE + assert den != 0 + if num == 0: + den = 1 + self._num: int = num + self._den: int = den + + def __add__(self, a: int | Self) -> Self: + """Compute the sum of two field elements (second may be int).""" + if isinstance(a, type(self)): + return type(self)(self._num * a._den + self._den * a._num, self._den * a._den) + if isinstance(a, int): + return type(self)(self._num + self._den * a, self._den) + return NotImplemented + + def __radd__(self, a: int) -> Self: + """Compute the sum of an integer and a field element.""" + return type(self)(a) + self + + @classmethod + def sum(cls, *es: Self) -> Self: + """Compute the sum of field elements. + + sum(a, b, c, ...) is identical to (0 + a + b + c + ...).""" + return sum(es, start=cls(0)) + + def __sub__(self, a: int | Self) -> Self: + """Compute the difference of two field elements (second may be int).""" + if isinstance(a, type(self)): + return type(self)(self._num * a._den - self._den * a._num, self._den * a._den) + if isinstance(a, int): + return type(self)(self._num - self._den * a, self._den) + return NotImplemented + + def __rsub__(self, a: int) -> Self: + """Compute the difference of an integer and a field element.""" + return type(self)(a) - self + + def __mul__(self, a: int | Self) -> Self: + """Compute the product of two field elements (second may be int).""" + if isinstance(a, type(self)): + return type(self)(self._num * a._num, self._den * a._den) + if isinstance(a, int): + return type(self)(self._num * a, self._den) + return NotImplemented + + def __rmul__(self, a: int) -> Self: + """Compute the product of an integer with a field element.""" + return type(self)(a) * self + + def __truediv__(self, a: int | Self) -> Self: + """Compute the ratio of two field elements (second may be int).""" + if isinstance(a, type(self)) or isinstance(a, int): + return type(self)(self, a) + return NotImplemented + + def __pow__(self, a: int) -> Self: + """Raise a field element to an integer power.""" + return type(self)(pow(self._num, a, self.SIZE), pow(self._den, a, self.SIZE)) + + def __neg__(self) -> Self: + """Negate a field element.""" + return type(self)(-self._num, self._den) + + def __int__(self) -> int: + """Convert a field element to an integer in range 0..SIZE-1. The result is cached.""" + if self._den != 1: + self._num = (self._num * pow(self._den, -1, self.SIZE)) % self.SIZE + self._den = 1 + return self._num + + def sqrt(self) -> Self | None: + """Compute the square root of a field element if it exists (None otherwise).""" + raise NotImplementedError + + def is_square(self) -> bool: + """Determine if this field element has a square root.""" + # A more efficient algorithm is possible here (Jacobi symbol). + return self.sqrt() is not None + + def is_even(self) -> bool: + """Determine whether this field element, represented as integer in 0..SIZE-1, is even.""" + return int(self) & 1 == 0 + + def __eq__(self, a: object) -> bool: + """Check whether two field elements are equal (second may be an int).""" + if isinstance(a, type(self)): + return (self._num * a._den - self._den * a._num) % self.SIZE == 0 + elif isinstance(a, int): + return (self._num - self._den * a) % self.SIZE == 0 + return False # for other types + + def to_bytes(self) -> bytes: + """Convert a field element to a 32-byte array (BE byte order).""" + return int(self).to_bytes(32, 'big') + + @classmethod + def from_int_checked(cls, v: int) -> Self: + """Convert an integer to a field element (no overflow allowed).""" + if v >= cls.SIZE: + raise ValueError + return cls(v) + + @classmethod + def from_int_wrapping(cls, v: int) -> Self: + """Convert an integer to a field element (reduced modulo SIZE).""" + return cls(v % cls.SIZE) + + @classmethod + def from_bytes_checked(cls, b: bytes) -> Self: + """Convert a 32-byte array to a field element (BE byte order, no overflow allowed).""" + v = int.from_bytes(b, 'big') + return cls.from_int_checked(v) + + @classmethod + def from_bytes_wrapping(cls, b: bytes) -> Self: + """Convert a 32-byte array to a field element (BE byte order, reduced modulo SIZE).""" + v = int.from_bytes(b, 'big') + return cls.from_int_wrapping(v) + + def __str__(self) -> str: + """Convert this field element to a 64 character hex string.""" + return f"{int(self):064x}" + + def __repr__(self) -> str: + """Get a string representation of this field element.""" + return f"{type(self).__qualname__}(0x{int(self):x})" + + +class FE(APrimeFE): + SIZE = 2**256 - 2**32 - 977 + + def sqrt(self) -> Self | None: + # Due to the fact that our modulus p is of the form (p % 4) == 3, the Tonelli-Shanks + # algorithm (https://en.wikipedia.org/wiki/Tonelli-Shanks_algorithm) is simply + # raising the argument to the power (p + 1) / 4. + + # To see why: (p-1) % 2 = 0, so 2 divides the order of the multiplicative group, + # and thus only half of the non-zero field elements are squares. An element a is + # a (nonzero) square when Euler's criterion, a^((p-1)/2) = 1 (mod p), holds. We're + # looking for x such that x^2 = a (mod p). Given a^((p-1)/2) = 1, that is equivalent + # to x^2 = a^(1 + (p-1)/2) mod p. As (1 + (p-1)/2) is even, this is equivalent to + # x = a^((1 + (p-1)/2)/2) mod p, or x = a^((p+1)/4) mod p. + v = int(self) + s = pow(v, (self.SIZE + 1) // 4, self.SIZE) + if s**2 % self.SIZE == v: + return type(self)(s) + return None + + +class Scalar(APrimeFE): + """TODO Docstring""" + SIZE = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 + + @classmethod + def from_int_nonzero_checked(cls, v: int) -> Self: + """Convert an integer to a scalar (no zero or overflow allowed).""" + if not (0 < v < cls.SIZE): + raise ValueError + return cls(v) + + @classmethod + def from_bytes_nonzero_checked(cls, b: bytes) -> Self: + """Convert a 32-byte array to a scalar (BE byte order, no zero or overflow allowed).""" + v = int.from_bytes(b, 'big') + return cls.from_int_nonzero_checked(v) + + +class GE: + """Objects of this class represent secp256k1 group elements (curve points or infinity) + + GE objects are immutable. + + Normal points on the curve have fields: + * x: the x coordinate (a field element) + * y: the y coordinate (a field element, satisfying y^2 = x^3 + 7) + * infinity: False + + The point at infinity has field: + * infinity: True + """ + + # TODO The following two class attributes should probably be just getters as + # classmethods to enforce immutability. Unfortunately Python makes it hard + # to create "classproperties". `G` could then also be just a classmethod. + + # Order of the group (number of points on the curve, plus 1 for infinity) + ORDER = Scalar.SIZE + + # Number of valid distinct x coordinates on the curve. + ORDER_HALF = ORDER // 2 + + @property + def infinity(self) -> bool: + """Whether the group element is the point at infinity.""" + return self._infinity + + @property + def x(self) -> FE: + """The x coordinate (a field element) of a non-infinite group element.""" + assert not self.infinity + return self._x + + @property + def y(self) -> FE: + """The y coordinate (a field element) of a non-infinite group element.""" + assert not self.infinity + return self._y + + def __init__(self, x: int | FE | None = None, y: int | FE | None = None) -> None: + """Initialize a group element with specified x and y coordinates, or infinity.""" + if x is None: + # Initialize as infinity. + assert y is None + self._infinity = True + else: + # Initialize as point on the curve (and check that it is). + assert x is not None + assert y is not None + fx = FE(x) + fy = FE(y) + assert fy**2 == fx**3 + 7 + self._infinity = False + self._x = fx + self._y = fy + + def __add__(self, a: GE) -> GE: + """Add two group elements together.""" + # Deal with infinity: a + infinity == infinity + a == a. + if self.infinity: + return a + if a.infinity: + return self + if self.x == a.x: + if self.y != a.y: + # A point added to its own negation is infinity. + assert self.y + a.y == 0 + return GE() + else: + # For identical inputs, use the tangent (doubling formula). + lam = (3 * self.x**2) / (2 * self.y) + else: + # For distinct inputs, use the line through both points (adding formula). + lam = (self.y - a.y) / (self.x - a.x) + # Determine point opposite to the intersection of that line with the curve. + x = lam**2 - (self.x + a.x) + y = lam * (self.x - x) - self.y + return GE(x, y) + + @staticmethod + def sum(*ps: GE) -> GE: + """Compute the sum of group elements. + + GE.sum(a, b, c, ...) is identical to (GE() + a + b + c + ...).""" + return sum(ps, start=GE()) + + @staticmethod + def batch_mul(*aps: tuple[Scalar, GE]) -> GE: + """Compute a (batch) scalar group element multiplication. + + GE.batch_mul((a1, p1), (a2, p2), (a3, p3)) is identical to a1*p1 + a2*p2 + a3*p3, + but more efficient.""" + # Reduce all the scalars modulo order first (so we can deal with negatives etc). + naps = [(int(a), p) for a, p in aps] + # Start with point at infinity. + r = GE() + # Iterate over all bit positions, from high to low. + for i in range(255, -1, -1): + # Double what we have so far. + r = r + r + # Add then add the points for which the corresponding scalar bit is set. + for (a, p) in naps: + if (a >> i) & 1: + r += p + return r + + def __rmul__(self, a: int | Scalar) -> GE: + """Multiply an integer or scalar with a group element.""" + if self == G: + return FAST_G.mul(Scalar(a)) + return GE.batch_mul((Scalar(a), self)) + + def __neg__(self) -> GE: + """Compute the negation of a group element.""" + if self.infinity: + return self + return GE(self.x, -self.y) + + def __sub__(self, a: GE) -> GE: + """Subtract a group element from another.""" + return self + (-a) + + def __eq__(self, a: object) -> bool: + """Check if two group elements are equal.""" + if not isinstance(a, type(self)): + return False + return (self - a).infinity + + def has_even_y(self) -> bool: + """Determine whether a non-infinity group element has an even y coordinate.""" + assert not self.infinity + return self.y.is_even() + + def to_bytes_compressed(self) -> bytes: + """Convert a non-infinite group element to 33-byte compressed encoding.""" + assert not self.infinity + return bytes([3 - self.y.is_even()]) + self.x.to_bytes() + + def to_bytes_compressed_with_infinity(self) -> bytes: + """Convert a group element to 33-byte compressed encoding, mapping infinity to zeros.""" + if self.infinity: + return 33 * b"\x00" + return self.to_bytes_compressed() + + def to_bytes_uncompressed(self) -> bytes: + """Convert a non-infinite group element to 65-byte uncompressed encoding.""" + assert not self.infinity + return b'\x04' + self.x.to_bytes() + self.y.to_bytes() + + def to_bytes_xonly(self) -> bytes: + """Convert (the x coordinate of) a non-infinite group element to 32-byte xonly encoding.""" + assert not self.infinity + return self.x.to_bytes() + + @staticmethod + def lift_x(x: int | FE) -> GE: + """Return group element with specified field element as x coordinate (and even y).""" + y = (FE(x)**3 + 7).sqrt() + if y is None: + raise ValueError + if not y.is_even(): + y = -y + return GE(x, y) + + @staticmethod + def from_bytes_compressed(b: bytes) -> GE: + """Convert a compressed to a group element.""" + assert len(b) == 33 + if b[0] != 2 and b[0] != 3: + raise ValueError + x = FE.from_bytes_checked(b[1:]) + r = GE.lift_x(x) + if b[0] == 3: + r = -r + return r + + @staticmethod + def from_bytes_compressed_with_infinity(b: bytes) -> GE: + """Convert a compressed to a group element, mapping zeros to infinity.""" + if b == 33 * b"\x00": + return GE() + else: + return GE.from_bytes_compressed(b) + + @staticmethod + def from_bytes_uncompressed(b: bytes) -> GE: + """Convert an uncompressed to a group element.""" + assert len(b) == 65 + if b[0] != 4: + raise ValueError + x = FE.from_bytes_checked(b[1:33]) + y = FE.from_bytes_checked(b[33:]) + if y**2 != x**3 + 7: + raise ValueError + return GE(x, y) + + @staticmethod + def from_bytes(b: bytes) -> GE: + """Convert a compressed or uncompressed encoding to a group element.""" + assert len(b) in (33, 65) + if len(b) == 33: + return GE.from_bytes_compressed(b) + else: + return GE.from_bytes_uncompressed(b) + + @staticmethod + def from_bytes_xonly(b: bytes) -> GE: + """Convert a point given in xonly encoding to a group element.""" + assert len(b) == 32 + x = FE.from_bytes_checked(b) + r = GE.lift_x(x) + return r + + @staticmethod + def is_valid_x(x: int | FE) -> bool: + """Determine whether the provided field element is a valid X coordinate.""" + return (FE(x)**3 + 7).is_square() + + def __str__(self) -> str: + """Convert this group element to a string.""" + if self.infinity: + return "(inf)" + return f"({self.x},{self.y})" + + def __repr__(self) -> str: + """Get a string representation for this group element.""" + if self.infinity: + return "GE()" + return f"GE(0x{int(self.x):x},0x{int(self.y):x})" + + def __hash__(self) -> int: + """Compute a non-cryptographic hash of the group element.""" + if self.infinity: + return 0 # 0 is not a valid x coordinate + return int(self.x) + + +# The secp256k1 generator point +G = GE.lift_x(0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798) + + +class FastGEMul: + """Table for fast multiplication with a constant group element. + + Speed up scalar multiplication with a fixed point P by using a precomputed lookup table with + its powers of 2: + + table = [P, 2*P, 4*P, (2^3)*P, (2^4)*P, ..., (2^255)*P] + + During multiplication, the points corresponding to each bit set in the scalar are added up, + i.e. on average ~128 point additions take place. + """ + + def __init__(self, p: GE) -> None: + self.table: list[GE] = [p] # table[i] = (2^i) * p + for _ in range(255): + p = p + p + self.table.append(p) + + def mul(self, a: Scalar | int) -> GE: + result = GE() + a_ = int(a) + for bit in range(a_.bit_length()): + if a_ & (1 << bit): + result += self.table[bit] + return result + +# Precomputed table with multiples of G for fast multiplication +FAST_G = FastGEMul(G) diff --git a/bip-chilldkg/python/secp256k1lab/src/secp256k1lab/util.py b/bip-chilldkg/python/secp256k1lab/src/secp256k1lab/util.py new file mode 100644 index 0000000000..d8c744b795 --- /dev/null +++ b/bip-chilldkg/python/secp256k1lab/src/secp256k1lab/util.py @@ -0,0 +1,24 @@ +import hashlib + + +# This implementation can be sped up by storing the midstate after hashing +# tag_hash instead of rehashing it all the time. +def tagged_hash(tag: str, msg: bytes) -> bytes: + tag_hash = hashlib.sha256(tag.encode()).digest() + return hashlib.sha256(tag_hash + tag_hash + msg).digest() + + +def bytes_from_int(x: int) -> bytes: + return x.to_bytes(32, byteorder="big") + + +def xor_bytes(b0: bytes, b1: bytes) -> bytes: + return bytes(x ^ y for (x, y) in zip(b0, b1)) + + +def int_from_bytes(b: bytes) -> int: + return int.from_bytes(b, byteorder="big") + + +def hash_sha256(b: bytes) -> bytes: + return hashlib.sha256(b).digest() diff --git a/bip-chilldkg/python/secp256k1lab/test/__init__.py b/bip-chilldkg/python/secp256k1lab/test/__init__.py new file mode 100644 index 0000000000..862ed6e21c --- /dev/null +++ b/bip-chilldkg/python/secp256k1lab/test/__init__.py @@ -0,0 +1,5 @@ +from pathlib import Path +import sys + +# Ensure secp256k1lab is found and can be imported directly +sys.path.insert(0, str(Path(__file__).parent / "../src/")) diff --git a/bip-chilldkg/python/secp256k1lab/test/test_bip340.py b/bip-chilldkg/python/secp256k1lab/test/test_bip340.py new file mode 100644 index 0000000000..7fafad54bd --- /dev/null +++ b/bip-chilldkg/python/secp256k1lab/test/test_bip340.py @@ -0,0 +1,51 @@ +import csv +from pathlib import Path +from random import randbytes +import unittest + +from secp256k1lab.bip340 import pubkey_gen, schnorr_sign, schnorr_verify + + +class BIP340Tests(unittest.TestCase): + """Test schnorr signatures (BIP 340).""" + + def test_correctness(self): + seckey = randbytes(32) + pubkey_xonly = pubkey_gen(seckey) + aux_rand = randbytes(32) + message = b'this is some arbitrary message' + signature = schnorr_sign(message, seckey, aux_rand) + success = schnorr_verify(message, pubkey_xonly, signature) + self.assertTrue(success) + + def test_vectors(self): + # Test against vectors from the BIPs repository + # [https://github.com/bitcoin/bips/blob/master/bip-0340/test-vectors.csv] + vectors_file = Path(__file__).parent / "vectors" / "bip340.csv" + with open(vectors_file, encoding='utf8') as csvfile: + reader = csv.DictReader(csvfile) + for row in reader: + with self.subTest(i=int(row['index'])): + self.subtest_vectors_case(row) + + def subtest_vectors_case(self, row): + seckey = bytes.fromhex(row['secret key']) + pubkey_xonly = bytes.fromhex(row['public key']) + aux_rand = bytes.fromhex(row['aux_rand']) + msg = bytes.fromhex(row['message']) + sig = bytes.fromhex(row['signature']) + result_str = row['verification result'] + comment = row['comment'] + + result = result_str == 'TRUE' + assert result or result_str == 'FALSE' + if seckey != b'': + pubkey_xonly_actual = pubkey_gen(seckey) + self.assertEqual(pubkey_xonly.hex(), pubkey_xonly_actual.hex(), f"BIP340 test vector ({comment}): pubkey mismatch") + sig_actual = schnorr_sign(msg, seckey, aux_rand) + self.assertEqual(sig.hex(), sig_actual.hex(), f"BIP340 test vector ({comment}): sig mismatch") + result_actual = schnorr_verify(msg, pubkey_xonly, sig) + if result: + self.assertEqual(result, result_actual, f"BIP340 test vector ({comment}): verification failed unexpectedly") + else: + self.assertEqual(result, result_actual, f"BIP340 test vector ({comment}): verification succeeded unexpectedly") diff --git a/bip-chilldkg/python/secp256k1lab/test/test_ecdh.py b/bip-chilldkg/python/secp256k1lab/test/test_ecdh.py new file mode 100644 index 0000000000..63c9da7a1b --- /dev/null +++ b/bip-chilldkg/python/secp256k1lab/test/test_ecdh.py @@ -0,0 +1,18 @@ +from random import randbytes +import unittest + +from secp256k1lab.ecdh import ecdh_libsecp256k1 +from secp256k1lab.keys import pubkey_gen_plain + + +class ECDHTests(unittest.TestCase): + """Test ECDH module.""" + + def test_correctness(self): + seckey_alice = randbytes(32) + pubkey_alice = pubkey_gen_plain(seckey_alice) + seckey_bob = randbytes(32) + pubkey_bob = pubkey_gen_plain(seckey_bob) + shared_secret1 = ecdh_libsecp256k1(seckey_alice, pubkey_bob) + shared_secret2 = ecdh_libsecp256k1(seckey_bob, pubkey_alice) + self.assertEqual(shared_secret1, shared_secret2) diff --git a/bip-chilldkg/python/secp256k1lab/test/test_secp256k1.py b/bip-chilldkg/python/secp256k1lab/test/test_secp256k1.py new file mode 100644 index 0000000000..c6aee19a0a --- /dev/null +++ b/bip-chilldkg/python/secp256k1lab/test/test_secp256k1.py @@ -0,0 +1,180 @@ +"""Test low-level secp256k1 field and group arithmetic classes.""" +from random import randint +import unittest + +from secp256k1lab.secp256k1 import FE, G, GE, Scalar + + +class PrimeFieldTests(unittest.TestCase): + def test_fe_constructors(self): + P = FE.SIZE + random_fe_valid = randint(0, P-1) + random_fe_overflowing = randint(P, 2**256-1) + + # wrapping constructors + for init_value in [0, P-1, P, P+1, random_fe_valid, random_fe_overflowing]: + fe1 = FE(init_value) + fe2 = FE.from_int_wrapping(init_value) + fe3 = FE.from_bytes_wrapping(init_value.to_bytes(32, 'big')) + reduced_value = init_value % P + self.assertEqual(int(fe1), reduced_value) + self.assertEqual(int(fe1), int(fe2)) + self.assertEqual(int(fe2), int(fe3)) + + # checking constructors (should throw on overflow) + for valid_value in [0, P-1, random_fe_valid]: + fe1 = FE.from_int_checked(valid_value) + fe2 = FE.from_bytes_checked(valid_value.to_bytes(32, 'big')) + self.assertEqual(int(fe1), valid_value) + self.assertEqual(int(fe1), int(fe2)) + + for overflow_value in [P, P+1, random_fe_overflowing]: + with self.assertRaises(ValueError): + _ = FE.from_int_checked(overflow_value) + with self.assertRaises(ValueError): + _ = FE.from_bytes_checked(overflow_value.to_bytes(32, 'big')) + + def test_scalar_constructors(self): + N = Scalar.SIZE + random_scalar_valid = randint(0, N-1) + random_scalar_overflowing = randint(N, 2**256-1) + + # wrapping constructors + for init_value in [0, N-1, N, N+1, random_scalar_valid, random_scalar_overflowing]: + s1 = Scalar(init_value) + s2 = Scalar.from_int_wrapping(init_value) + s3 = Scalar.from_bytes_wrapping(init_value.to_bytes(32, 'big')) + reduced_value = init_value % N + self.assertEqual(int(s1), reduced_value) + self.assertEqual(int(s1), int(s2)) + self.assertEqual(int(s2), int(s3)) + + # checking constructors (should throw on overflow) + for valid_value in [0, N-1, random_scalar_valid]: + s1 = Scalar.from_int_checked(valid_value) + s2 = Scalar.from_bytes_checked(valid_value.to_bytes(32, 'big')) + self.assertEqual(int(s1), valid_value) + self.assertEqual(int(s1), int(s2)) + + for overflow_value in [N, N+1, random_scalar_overflowing]: + with self.assertRaises(ValueError): + _ = Scalar.from_int_checked(overflow_value) + with self.assertRaises(ValueError): + _ = Scalar.from_bytes_checked(overflow_value.to_bytes(32, 'big')) + + # non-zero checking constructors (should throw on zero or overflow, only for Scalar) + random_nonzero_scalar_valid = randint(1, N-1) + for valid_value in [1, N-1, random_nonzero_scalar_valid]: + s1 = Scalar.from_int_nonzero_checked(valid_value) + s2 = Scalar.from_bytes_nonzero_checked(valid_value.to_bytes(32, 'big')) + self.assertEqual(int(s1), valid_value) + self.assertEqual(int(s1), int(s2)) + + for invalid_value in [0, N, random_scalar_overflowing]: + with self.assertRaises(ValueError): + _ = Scalar.from_int_nonzero_checked(invalid_value) + with self.assertRaises(ValueError): + _ = Scalar.from_bytes_nonzero_checked(invalid_value.to_bytes(32, 'big')) + + +class GeSerializationTests(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.point_at_infinity = GE() + cls.group_elements_on_curve = [ + # generator point + G, + # Bitcoin genesis block public key + GE(0x678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb6, + 0x49f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f), + ] + # generate a few random points, to likely cover both even/odd y polarity + cls.group_elements_on_curve.extend([randint(1, Scalar.SIZE-1) * G for _ in range(8)]) + # generate x coordinates that don't have a valid point on the curve + # (note that ~50% of all x coordinates are valid, so finding one needs two loop iterations on average) + cls.x_coords_not_on_curve = [] + while len(cls.x_coords_not_on_curve) < 8: + x = randint(0, FE.SIZE-1) + if not GE.is_valid_x(x): + cls.x_coords_not_on_curve.append(x) + + cls.group_elements = [cls.point_at_infinity] + cls.group_elements_on_curve + + def test_infinity_raises(self): + with self.assertRaises(AssertionError): + _ = self.point_at_infinity.to_bytes_uncompressed() + with self.assertRaises(AssertionError): + _ = self.point_at_infinity.to_bytes_compressed() + with self.assertRaises(AssertionError): + _ = self.point_at_infinity.to_bytes_xonly() + + def test_not_on_curve_raises(self): + # for compressed and x-only GE deserialization, test with invalid x coordinate + for x in self.x_coords_not_on_curve: + x_bytes = x.to_bytes(32, 'big') + with self.assertRaises(ValueError): + _ = GE.from_bytes_compressed(b'\x02' + x_bytes) + with self.assertRaises(ValueError): + _ = GE.from_bytes_compressed(b'\x03' + x_bytes) + with self.assertRaises(ValueError): + _ = GE.from_bytes_compressed_with_infinity(b'\x02' + x_bytes) + with self.assertRaises(ValueError): + _ = GE.from_bytes_compressed_with_infinity(b'\x03' + x_bytes) + with self.assertRaises(ValueError): + _ = GE.from_bytes_xonly(x_bytes) + + # for uncompressed GE serialization, test by invalidating either coordinate + for ge in self.group_elements_on_curve: + valid_x = ge.x + valid_y = ge.y + invalid_x = ge.x + 1 + invalid_y = ge.y + 1 + + # valid cases (if point (x,y) is on the curve, then point(x,-y) is on the curve as well) + _ = GE.from_bytes_uncompressed(b'\x04' + valid_x.to_bytes() + valid_y.to_bytes()) + _ = GE.from_bytes_uncompressed(b'\x04' + valid_x.to_bytes() + (-valid_y).to_bytes()) + # invalid cases (curve equation y**2 = x**3 + 7 doesn't hold) + self.assertNotEqual(invalid_y**2, valid_x**3 + 7) + with self.assertRaises(ValueError): + _ = GE.from_bytes_uncompressed(b'\x04' + valid_x.to_bytes() + invalid_y.to_bytes()) + self.assertNotEqual(valid_y**2, invalid_x**3 + 7) + with self.assertRaises(ValueError): + _ = GE.from_bytes_uncompressed(b'\x04' + invalid_x.to_bytes() + valid_y.to_bytes()) + + def test_affine(self): + # GE serialization and parsing round-trip (variants that only support serializing points on the curve) + for ge_orig in self.group_elements_on_curve: + # uncompressed serialization: 65 bytes, starts with 0x04 + ge_ser = ge_orig.to_bytes_uncompressed() + self.assertEqual(len(ge_ser), 65) + self.assertEqual(ge_ser[0], 0x04) + ge_deser = GE.from_bytes_uncompressed(ge_ser) + self.assertEqual(ge_deser, ge_orig) + + # compressed serialization: 33 bytes, starts with 0x02 (if y is even) or 0x03 (if y is odd) + ge_ser = ge_orig.to_bytes_compressed() + self.assertEqual(len(ge_ser), 33) + self.assertEqual(ge_ser[0], 0x02 if ge_orig.has_even_y() else 0x03) + ge_deser = GE.from_bytes_compressed(ge_ser) + self.assertEqual(ge_deser, ge_orig) + + # x-only serialization: 32 bytes + ge_ser = ge_orig.to_bytes_xonly() + self.assertEqual(len(ge_ser), 32) + ge_deser = GE.from_bytes_xonly(ge_ser) + if not ge_orig.has_even_y(): # x-only implies even y, so flip if necessary + ge_deser = -ge_deser + self.assertEqual(ge_deser, ge_orig) + + def test_affine_with_infinity(self): + # GE serialization and parsing round-trip (variants that also support serializing the point at infinity) + for ge_orig in self.group_elements: + # compressed serialization: 33 bytes, all-zeros for point at infinity + ge_ser = ge_orig.to_bytes_compressed_with_infinity() + self.assertEqual(len(ge_ser), 33) + if ge_orig.infinity: + self.assertEqual(ge_ser, b'\x00'*33) + else: + self.assertEqual(ge_ser[0], 0x02 if ge_orig.has_even_y() else 0x03) + ge_deser = GE.from_bytes_compressed_with_infinity(ge_ser) + self.assertEqual(ge_deser, ge_orig) diff --git a/bip-chilldkg/python/secp256k1lab/test/vectors/bip340.csv b/bip-chilldkg/python/secp256k1lab/test/vectors/bip340.csv new file mode 100644 index 0000000000..aa317a3b3d --- /dev/null +++ b/bip-chilldkg/python/secp256k1lab/test/vectors/bip340.csv @@ -0,0 +1,20 @@ +index,secret key,public key,aux_rand,message,signature,verification result,comment +0,0000000000000000000000000000000000000000000000000000000000000003,F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9,0000000000000000000000000000000000000000000000000000000000000000,0000000000000000000000000000000000000000000000000000000000000000,E907831F80848D1069A5371B402410364BDF1C5F8307B0084C55F1CE2DCA821525F66A4A85EA8B71E482A74F382D2CE5EBEEE8FDB2172F477DF4900D310536C0,TRUE, +1,B7E151628AED2A6ABF7158809CF4F3C762E7160F38B4DA56A784D9045190CFEF,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,0000000000000000000000000000000000000000000000000000000000000001,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,6896BD60EEAE296DB48A229FF71DFE071BDE413E6D43F917DC8DCF8C78DE33418906D11AC976ABCCB20B091292BFF4EA897EFCB639EA871CFA95F6DE339E4B0A,TRUE, +2,C90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B14E5C9,DD308AFEC5777E13121FA72B9CC1B7CC0139715309B086C960E18FD969774EB8,C87AA53824B4D7AE2EB035A2B5BBBCCC080E76CDC6D1692C4B0B62D798E6D906,7E2D58D8B3BCDF1ABADEC7829054F90DDA9805AAB56C77333024B9D0A508B75C,5831AAEED7B44BB74E5EAB94BA9D4294C49BCF2A60728D8B4C200F50DD313C1BAB745879A5AD954A72C45A91C3A51D3C7ADEA98D82F8481E0E1E03674A6F3FB7,TRUE, +3,0B432B2677937381AEF05BB02A66ECD012773062CF3FA2549E44F58ED2401710,25D1DFF95105F5253C4022F628A996AD3A0D95FBF21D468A1B33F8C160D8F517,FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF,FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF,7EB0509757E246F19449885651611CB965ECC1A187DD51B64FDA1EDC9637D5EC97582B9CB13DB3933705B32BA982AF5AF25FD78881EBB32771FC5922EFC66EA3,TRUE,test fails if msg is reduced modulo p or n +4,,D69C3509BB99E412E68B0FE8544E72837DFA30746D8BE2AA65975F29D22DC7B9,,4DF3C3F68FCC83B27E9D42C90431A72499F17875C81A599B566C9889B9696703,00000000000000000000003B78CE563F89A0ED9414F5AA28AD0D96D6795F9C6376AFB1548AF603B3EB45C9F8207DEE1060CB71C04E80F593060B07D28308D7F4,TRUE, +5,,EEFDEA4CDB677750A420FEE807EACF21EB9898AE79B9768766E4FAA04A2D4A34,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E17776969E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B,FALSE,public key not on the curve +6,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,FFF97BD5755EEEA420453A14355235D382F6472F8568A18B2F057A14602975563CC27944640AC607CD107AE10923D9EF7A73C643E166BE5EBEAFA34B1AC553E2,FALSE,has_even_y(R) is false +7,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,1FA62E331EDBC21C394792D2AB1100A7B432B013DF3F6FF4F99FCB33E0E1515F28890B3EDB6E7189B630448B515CE4F8622A954CFE545735AAEA5134FCCDB2BD,FALSE,negated message +8,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E177769961764B3AA9B2FFCB6EF947B6887A226E8D7C93E00C5ED0C1834FF0D0C2E6DA6,FALSE,negated s value +9,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,0000000000000000000000000000000000000000000000000000000000000000123DDA8328AF9C23A94C1FEECFD123BA4FB73476F0D594DCB65C6425BD186051,FALSE,sG - eP is infinite. Test fails in single verification if has_even_y(inf) is defined as true and x(inf) as 0 +10,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,00000000000000000000000000000000000000000000000000000000000000017615FBAF5AE28864013C099742DEADB4DBA87F11AC6754F93780D5A1837CF197,FALSE,sG - eP is infinite. Test fails in single verification if has_even_y(inf) is defined as true and x(inf) as 1 +11,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,4A298DACAE57395A15D0795DDBFD1DCB564DA82B0F269BC70A74F8220429BA1D69E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B,FALSE,sig[0:32] is not an X coordinate on the curve +12,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F69E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B,FALSE,sig[0:32] is equal to field size +13,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E177769FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141,FALSE,sig[32:64] is equal to curve order +14,,FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC30,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E17776969E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B,FALSE,public key is not a valid X coordinate because it exceeds the field size +15,0340034003400340034003400340034003400340034003400340034003400340,778CAA53B4393AC467774D09497A87224BF9FAB6F6E68B23086497324D6FD117,0000000000000000000000000000000000000000000000000000000000000000,,71535DB165ECD9FBBC046E5FFAEA61186BB6AD436732FCCC25291A55895464CF6069CE26BF03466228F19A3A62DB8A649F2D560FAC652827D1AF0574E427AB63,TRUE,message of size 0 (added 2022-12) +16,0340034003400340034003400340034003400340034003400340034003400340,778CAA53B4393AC467774D09497A87224BF9FAB6F6E68B23086497324D6FD117,0000000000000000000000000000000000000000000000000000000000000000,11,08A20A0AFEF64124649232E0693C583AB1B9934AE63B4C3511F3AE1134C6A303EA3173BFEA6683BD101FA5AA5DBC1996FE7CACFC5A577D33EC14564CEC2BACBF,TRUE,message of size 1 (added 2022-12) +17,0340034003400340034003400340034003400340034003400340034003400340,778CAA53B4393AC467774D09497A87224BF9FAB6F6E68B23086497324D6FD117,0000000000000000000000000000000000000000000000000000000000000000,0102030405060708090A0B0C0D0E0F1011,5130F39A4059B43BC7CAC09A19ECE52B5D8699D1A71E3C52DA9AFDB6B50AC370C4A482B77BF960F8681540E25B6771ECE1E5A37FD80E5A51897C5566A97EA5A5,TRUE,message of size 17 (added 2022-12) +18,0340034003400340034003400340034003400340034003400340034003400340,778CAA53B4393AC467774D09497A87224BF9FAB6F6E68B23086497324D6FD117,0000000000000000000000000000000000000000000000000000000000000000,99999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999,403B12B0D8555A344175EA7EC746566303321E5DBFA8BE6F091635163ECA79A8585ED3E3170807E7C03B720FC54C7B23897FCBA0E9D0B4A06894CFD249F22367,TRUE,message of size 100 (added 2022-12) diff --git a/bip-chilldkg/python/tests.py b/bip-chilldkg/python/tests.py new file mode 100755 index 0000000000..aeaa7b39a6 --- /dev/null +++ b/bip-chilldkg/python/tests.py @@ -0,0 +1,914 @@ +#!/usr/bin/env python3 + +"""Tests for ChillDKG reference implementation""" + +import json +from itertools import combinations +from pathlib import Path +from random import randint +from secrets import token_bytes as random_bytes + +from chilldkg_ref import chilldkg, encpedpop, simplpedpop +from chilldkg_ref.util import ( + FaultyCoordinatorError, + FaultyParticipantOrCoordinatorError, + UnknownFaultyParticipantOrCoordinatorError, + tagged_hash_bip_dkg, +) +from chilldkg_ref.vss import VSS, Polynomial, VSSCommitment +from example import simulate_chilldkg_full as simulate_chilldkg_full_example +from gen_vector_utils.util import ( + assert_raises, + dkg_output_asdict, + params_asdict, + params_from_dict, +) + +# Import from secp256k1lab after the chilldkg_ref imports because the latter +# modifies sys.path to make sure the vendored copy of secp256k1lab is found. +# +# isort: split +from secp256k1lab.keys import pubkey_gen_plain +from secp256k1lab.secp256k1 import GE, G, Scalar + + +def test_chilldkg_params_validate(): + hostseckeys = [random_bytes(32) for _ in range(3)] + hostpubkeys = [chilldkg.hostpubkey_gen(hostseckey) for hostseckey in hostseckeys] + + with_duplicate = [hostpubkeys[0], hostpubkeys[1], hostpubkeys[2], hostpubkeys[1]] + params_with_duplicate = chilldkg.SessionParams(with_duplicate, 2) + try: + _ = chilldkg.params_hash(params_with_duplicate) + except chilldkg.DuplicateHostPubkeyError as e: + assert {e.participant_id1, e.participant_id2} == {1, 3} + else: + assert False, "Expected exception" + + invalid_hostpubkey = b"\x03" + 31 * b"\x00" + b"\x05" # Invalid x-coordinate + params_with_invalid = chilldkg.SessionParams( + [hostpubkeys[1], invalid_hostpubkey, hostpubkeys[2]], 1 + ) + try: + _ = chilldkg.params_hash(params_with_invalid) + except chilldkg.InvalidHostPubkeyError as e: + assert e.participant_id == 1 + else: + assert False, "Expected exception" + + try: + _ = chilldkg.params_hash( + chilldkg.SessionParams(hostpubkeys, len(hostpubkeys) + 1) + ) + except chilldkg.ThresholdOrCountError: + pass + else: + assert False, "Expected exception" + + try: + _ = chilldkg.params_hash(chilldkg.SessionParams(hostpubkeys, -2)) + except chilldkg.ThresholdOrCountError: + pass + else: + assert False, "Expected exception" + + +def test_vss_correctness(): + def rand_polynomial(t): + return Polynomial([randint(1, GE.ORDER - 1) for _ in range(1, t + 1)]) + + for t in range(1, 3): + for n in range(t, 2 * t + 1): + f = rand_polynomial(t) + vss = VSS(f) + secshares = vss.secshares(n) + assert len(secshares) == n + assert all( + VSSCommitment.verify_secshare(secshares[i], vss.commit().pubshare(i)) + for i in range(n) + ) + + vssc_tweaked, tweak, pubtweak = vss.commit().invalid_taproot_commit() + assert VSSCommitment.verify_secshare( + vss.secret() + tweak, vss.commit().commitment_to_secret() + pubtweak + ) + assert all( + VSSCommitment.verify_secshare( + secshares[i] + tweak, vssc_tweaked.pubshare(i) + ) + for i in range(n) + ) + + +def simulate_simplpedpop( + seeds, t, investigation: bool +) -> list[tuple[simplpedpop.DKGOutput, bytes]] | None: + n = len(seeds) + prets = [] + for i in range(n): + random = random_bytes(32) + prets += [simplpedpop.participant_step1(seeds[i], t, n, i, random)] + + pstates = [pstate for (pstate, _, _) in prets] + pmsgs = [pmsg for (_, pmsg, _) in prets] + + cmsg, cout, ceq = simplpedpop.coordinator_step(pmsgs, t, n) + pre_finalize_rets = [(cout, ceq)] + for i in range(n): + partial_secshares = [ + partial_secshares_for[i] for (_, _, partial_secshares_for) in prets + ] + if investigation: + # Let a random participant send incorrect shares to participant i. + faulty_id = randint(0, n - 1) + partial_secshares[faulty_id] += Scalar(17) + + secshare = simplpedpop.participant_step2_prepare_secshare(partial_secshares) + try: + pre_finalize_rets += [ + simplpedpop.participant_step2(pstates[i], cmsg, secshare) + ] + except UnknownFaultyParticipantOrCoordinatorError as e: + if not investigation: + raise + inv_msgs = simplpedpop.coordinator_investigate(pmsgs, t) + assert len(inv_msgs) == len(pmsgs) + try: + simplpedpop.participant_investigate(e, inv_msgs[i], partial_secshares) + # If we're not faulty, we should blame the faulty party. + except FaultyParticipantOrCoordinatorError as e: + assert i != faulty_id + assert e.participant_id == faulty_id + # If we're faulty, we'll blame the coordinator. + except FaultyCoordinatorError: + assert i == faulty_id + return None + return pre_finalize_rets + + +def encpedpop_keys(seed: bytes) -> tuple[bytes, bytes]: + deckey = tagged_hash_bip_dkg("encpedpop deckey", seed) + enckey = pubkey_gen_plain(deckey) + return deckey, enckey + + +def simulate_encpedpop( + seeds, t, investigation: bool +) -> list[tuple[simplpedpop.DKGOutput, bytes]] | None: + n = len(seeds) + enc_prets0 = [] + enc_prets1 = [] + for i in range(n): + enc_prets0 += [encpedpop_keys(seeds[i])] + + enckeys = [pret[1] for pret in enc_prets0] + for i in range(n): + deckey = enc_prets0[i][0] + random = random_bytes(32) + enc_prets1 += [ + encpedpop.participant_step1(seeds[i], deckey, enckeys, t, i, random) + ] + + pstates = [pstate for (pstate, _) in enc_prets1] + pmsgs = [pmsg for (_, pmsg) in enc_prets1] + if investigation: + faulty_id: list[int] = [] + for i in range(n): + # Let a random participant faulty_id[i] send incorrect shares to + # participant i. + faulty_id[i:] = [randint(0, n - 1)] + faulty_pmsg = encpedpop.ParticipantMsg.from_bytes( + pmsgs[faulty_id[i]], t=t, n=n + ) + faulty_pmsg.enc_shares[i] += Scalar(17) + pmsgs[faulty_id[i]] = faulty_pmsg.to_bytes() + + cmsg, cout, ceq, enc_secshares = encpedpop.coordinator_step(pmsgs, t, enckeys) + pre_finalize_rets = [(cout, ceq)] + for i in range(n): + deckey = enc_prets0[i][0] + try: + pre_finalize_rets += [ + encpedpop.participant_step2(pstates[i], deckey, cmsg, enc_secshares[i]) + ] + except UnknownFaultyParticipantOrCoordinatorError as e: + if not investigation: + raise + inv_msgs = encpedpop.coordinator_investigate(pmsgs, t) + assert len(inv_msgs) == len(pmsgs) + try: + encpedpop.participant_investigate(e, inv_msgs[i]) + # If we're not faulty, we should blame the faulty party. + except FaultyParticipantOrCoordinatorError as e: + assert i != faulty_id[i] + assert e.participant_id == faulty_id[i] + # If we're faulty, we'll blame the coordinator. + except FaultyCoordinatorError: + assert i == faulty_id[i] + return None + return pre_finalize_rets + + +def simulate_chilldkg( + hostseckeys, t, investigation: bool +) -> list[tuple[chilldkg.DKGOutput, chilldkg.RecoveryData]] | None: + n = len(hostseckeys) + + hostpubkeys = [] + for i in range(n): + hostpubkeys += [chilldkg.hostpubkey_gen(hostseckeys[i])] + + params = chilldkg.SessionParams(hostpubkeys, t) + + prets1 = [] + for i in range(n): + random = random_bytes(32) + prets1 += [chilldkg.participant_step1(hostseckeys[i], params, random)] + + pstates1 = [pret[0] for pret in prets1] + pmsgs = [pret[1] for pret in prets1] + if investigation: + faulty_id: list[int] = [] + for i in range(n): + # Let a random participant faulty_id[i] send incorrect shares + # to participant i. + faulty_id[i:] = [randint(0, n - 1)] + faulty_pmsg = chilldkg.ParticipantMsg1.from_bytes( + pmsgs[faulty_id[i]], t=t, n=n + ) + faulty_pmsg.enc_pmsg.enc_shares[i] += Scalar(17) + pmsgs[faulty_id[i]] = faulty_pmsg.to_bytes() + + cstate, cmsg1 = chilldkg.coordinator_step1(pmsgs, params) + + prets2 = [] + for i in range(n): + try: + random = random_bytes(32) + prets2 += [ + chilldkg.participant_step2(hostseckeys[i], pstates1[i], cmsg1, random) + ] + except UnknownFaultyParticipantOrCoordinatorError as e: + if not investigation: + raise + inv_msgs = chilldkg.coordinator_investigate(pmsgs, params) + assert len(inv_msgs) == len(pmsgs) + try: + chilldkg.participant_investigate(e, inv_msgs[i]) + # If we're not faulty, we should blame the faulty party. + except FaultyParticipantOrCoordinatorError as e: + assert i != faulty_id[i] + assert e.participant_id == faulty_id[i] + # If we're faulty, we'll blame the coordinator. + except FaultyCoordinatorError: + assert i == faulty_id[i] + return None + + cmsg2, cout, crec = chilldkg.coordinator_finalize( + cstate, [pret[1] for pret in prets2] + ) + outputs = [(cout, crec)] + for i in range(n): + out = chilldkg.participant_finalize(prets2[i][0], cmsg2) + assert out is not None + outputs += [out] + + return outputs + + +def simulate_chilldkg_full( + hostseckeys, + t, + investigation: bool, +) -> list[tuple[chilldkg.DKGOutput, chilldkg.RecoveryData] | None]: + # Investigating is not supported by this wrapper + assert not investigation + + hostpubkeys = [] + for i in range(n): + hostpubkeys += [chilldkg.hostpubkey_gen(hostseckeys[i])] + params = chilldkg.SessionParams(hostpubkeys, t) + return simulate_chilldkg_full_example(hostseckeys, params, faulty_id=None) + + +def derive_interpolating_value(L, x_i): + assert x_i in L + assert all(L.count(x_j) <= 1 for x_j in L) + lam = Scalar(1) + for x_j in L: + x_j = Scalar(x_j) + x_i = Scalar(x_i) + if x_j == x_i: + continue + lam *= x_j / (x_j - x_i) + return lam + + +def recover_secret(participant_ids, shares) -> Scalar: + interpolated_shares = [] + t = len(shares) + assert len(participant_ids) == t + for i in range(t): + lam = derive_interpolating_value(participant_ids, participant_ids[i]) + interpolated_shares += [(lam * shares[i])] + recovered_secret = Scalar.sum(*interpolated_shares) + return recovered_secret + + +def test_recover_secret(): + f = Polynomial([23, 42]) + shares = [f(i) for i in [1, 2, 3]] + assert recover_secret([1, 2], [shares[0], shares[1]]) == f.coeffs[0] + assert recover_secret([1, 3], [shares[0], shares[2]]) == f.coeffs[0] + assert recover_secret([2, 3], [shares[1], shares[2]]) == f.coeffs[0] + + +def test_correctness_dkg_output(t, n, dkg_outputs: list[simplpedpop.DKGOutput]): + assert len(dkg_outputs) == n + 1 + secshares = [out[0] for out in dkg_outputs] + thresh_pks = [out[1] for out in dkg_outputs] + pubshares = [out[2] for out in dkg_outputs] + + # Check that the threshold pubkey and pubshares are the same for the + # coordinator (at [0]) and all participants (at [1:n + 1]). + for i in range(n + 1): + assert thresh_pks[0] == thresh_pks[i] + assert len(pubshares[i]) == n + assert pubshares[0] == pubshares[i] + thresh_pk = thresh_pks[0] + + # Check that the coordinator has no secret share + assert secshares[0] is None + + # Check that each secshare matches the corresponding pubshare + secshares_scalar = [ + None if secshare is None else Scalar.from_bytes_checked(secshare) + for secshare in secshares + ] + for i in range(1, n + 1): + s = secshares_scalar[i] + assert s is not None + assert s * G == GE.from_bytes_compressed(pubshares[0][i - 1]) + + # Check that all combinations of t participants can recover the threshold pubkey + for tsubset in combinations(range(1, n + 1), t): + recovered = recover_secret(tsubset, [secshares_scalar[i] for i in tsubset]) + assert recovered * G == GE.from_bytes_compressed(thresh_pk) + + +def test_correctness(t, n, simulate_dkg, recovery=False, investigation=False): + seeds = [None] + [random_bytes(32) for _ in range(n)] + + rets = simulate_dkg(seeds[1:], t, investigation=investigation) + if investigation: + assert rets is None + # The session has failed correctly, so there's nothing further to check. + return + + # rets[0] are the return values from the coordinator + # rets[1 : n + 1] are from the participants + assert len(rets) == n + 1 + dkg_outputs = [ret[0] for ret in rets] + test_correctness_dkg_output(t, n, dkg_outputs) + + eqs_or_recs = [ret[1] for ret in rets] + for i in range(1, n + 1): + assert eqs_or_recs[0] == eqs_or_recs[i] + + if recovery: + rec = eqs_or_recs[0] + # Check correctness of chilldkg.participant_recover / + # chilldkg.coordinator_recover + for i in range(n + 1): + if seeds[i] is None: + (secshare, thresh_pk, pubshares), _ = chilldkg.coordinator_recover(rec) + else: + (secshare, thresh_pk, pubshares), _ = chilldkg.participant_recover( + seeds[i], rec + ) + assert secshare == dkg_outputs[i][0] + assert thresh_pk == dkg_outputs[i][1] + assert pubshares == dkg_outputs[i][2] + + +VECTORS_DIR = Path(__file__).parent.parent / "vectors" + + +def test_hostpubkey_gen_vectors(): + input_file = VECTORS_DIR / "hostpubkey_gen_vectors.json" + with open(input_file) as f: + test_data = json.load(f) + + valid_test_cases = test_data["validTestCases"] + error_test_cases = test_data["errorTestCases"] + assert test_data["totalTests"] == len(valid_test_cases) + len(error_test_cases) + + for test_case in valid_test_cases: + hostseckey = bytes.fromhex(test_case["hostseckey"]) + expected_hostpubkey = bytes.fromhex(test_case["expectedHostpubkey"]) + assert expected_hostpubkey == chilldkg.hostpubkey_gen(hostseckey) + + for test_case in error_test_cases: + hostseckey = bytes.fromhex(test_case["hostseckey"]) + expected_error = test_case["expectedError"] + assert_raises(lambda: chilldkg.hostpubkey_gen(hostseckey), expected_error) + + +def test_params_hash_vectors(): + input_file = VECTORS_DIR / "params_hash_vectors.json" + with open(input_file) as f: + test_data = json.load(f) + + valid_test_cases = test_data["validTestCases"] + error_test_cases = test_data["errorTestCases"] + assert test_data["totalTests"] == len(valid_test_cases) + len(error_test_cases) + + for test_case in valid_test_cases: + params = params_from_dict(test_case["params"]) + expected_hash = bytes.fromhex(test_case["expectedParamsHash"]) + assert expected_hash == chilldkg.params_hash(params) + + for test_case in error_test_cases: + params = params_from_dict(test_case["params"]) + expected_error = test_case["expectedError"] + assert_raises(lambda: chilldkg.params_hash(params), expected_error) + + +def test_participant_step1_vectors(): + input_file = VECTORS_DIR / "participant_step1_vectors.json" + with open(input_file) as f: + test_data = json.load(f) + + total_cases = 0 + for group in test_data["testGroups"]: + for test_case in group["validTestCases"]: + hostseckey = bytes.fromhex(test_case["hostseckey"]) + params = params_from_dict(test_case["params"]) + random = bytes.fromhex(test_case["random"]) + expected_pmsg1 = bytes.fromhex(test_case["expectedPmsg1"]) + _, pmsg1 = chilldkg.participant_step1(hostseckey, params, random) + assert expected_pmsg1 == pmsg1 + total_cases += 1 + assert test_case["tcId"] == total_cases + + for test_case in group["errorTestCases"]: + hostseckey = bytes.fromhex(test_case["hostseckey"]) + params = params_from_dict(test_case["params"]) + random = bytes.fromhex(test_case["random"]) + expected_error = test_case["expectedError"] + assert_raises( + lambda: chilldkg.participant_step1(hostseckey, params, random), + expected_error, + ) + total_cases += 1 + assert test_case["tcId"] == total_cases + + assert test_data["totalTests"] == total_cases + + +def test_participant_step2_vectors(): + input_file = VECTORS_DIR / "participant_step2_vectors.json" + with open(input_file) as f: + test_data = json.load(f) + + total_cases = 0 + for group in test_data["testGroups"]: + # common fields for all test cases + params = params_from_dict(group["params"]) + hostseckey = bytes.fromhex(group["hostseckey"]) + random = bytes.fromhex(group["random"]) + aux_rand = bytes.fromhex(group["auxRand"]) + + state1, pmsg1 = chilldkg.participant_step1(hostseckey, params, random) + assert bytes.fromhex(group["pmsg1"]) == pmsg1 # checkpoint + + for test_case in group["validTestCases"]: + cmsg1 = bytes.fromhex(test_case["cmsg1"]) + expected_pmsg2 = bytes.fromhex(test_case["expectedPmsg2"]) + _, pmsg2 = chilldkg.participant_step2(hostseckey, state1, cmsg1, aux_rand) + assert expected_pmsg2 == pmsg2 + total_cases += 1 + assert test_case["tcId"] == total_cases + + for test_case in group["errorTestCases"]: + case_hostseckey = bytes.fromhex( + test_case.get("hostseckey", group["hostseckey"]) + ) + case_aux_rand = bytes.fromhex(test_case.get("auxRand", group["auxRand"])) + cmsg1 = bytes.fromhex(test_case["cmsg1"]) + expected_error = test_case["expectedError"] + assert_raises( + lambda: chilldkg.participant_step2( + case_hostseckey, state1, cmsg1, case_aux_rand + ), + expected_error, + ) + total_cases += 1 + assert test_case["tcId"] == total_cases + + assert test_data["totalTests"] == total_cases + + +def test_participant_finalize_vectors(): + input_file = VECTORS_DIR / "participant_finalize_vectors.json" + with open(input_file) as f: + test_data = json.load(f) + + total_cases = 0 + for group in test_data["testGroups"]: + # common fields for all test cases + params = params_from_dict(group["params"]) + hostseckey = bytes.fromhex(group["hostseckey"]) + random = bytes.fromhex(group["random"]) + aux_rand = bytes.fromhex(group["auxRand"]) + + # compute state1 and assert pmsg1 + state1, pmsg1 = chilldkg.participant_step1(hostseckey, params, random) + assert bytes.fromhex(group["pmsg1"]) == pmsg1 + # compute state2 and assert pmsg2 + cmsg1 = bytes.fromhex(group["cmsg1"]) + state2, pmsg2 = chilldkg.participant_step2(hostseckey, state1, cmsg1, aux_rand) + assert bytes.fromhex(group["pmsg2"]) == pmsg2 + + for test_case in group["validTestCases"]: + cmsg2 = bytes.fromhex(test_case["cmsg2"]) + pout, prec = chilldkg.participant_finalize(state2, cmsg2) + expected_pout = test_case["expectedOutput"]["dkgOutput"] + expected_prec = bytes.fromhex(test_case["expectedOutput"]["recoveryData"]) + assert expected_pout == dkg_output_asdict(pout) + assert expected_prec == prec + total_cases += 1 + assert test_case["tcId"] == total_cases + + for test_case in group["errorTestCases"]: + cmsg2 = bytes.fromhex(test_case["cmsg2"]) + expected_error = test_case["expectedError"] + assert_raises( + lambda: chilldkg.participant_finalize(state2, cmsg2), expected_error + ) + total_cases += 1 + assert test_case["tcId"] == total_cases + + assert test_data["totalTests"] == total_cases + + +def test_participant_investigate_vectors(): + input_file = VECTORS_DIR / "participant_investigate_vectors.json" + with open(input_file) as f: + test_data = json.load(f) + + total_cases = 0 + for group in test_data["testGroups"]: + # common fields for all test cases + params = params_from_dict(group["params"]) + hostseckey = bytes.fromhex(group["hostseckey"]) + random = bytes.fromhex(group["random"]) + aux_rand = bytes.fromhex(group["auxRand"]) + cmsg1_pool = group["cmsg1Pool"] + + # Re-derive state1 + state1, pmsg1 = chilldkg.participant_step1(hostseckey, params, random) + assert bytes.fromhex(group["pmsg1"]) == pmsg1 + + for test_case in group["errorTestCases"]: + cmsg1 = bytes.fromhex(cmsg1_pool[test_case["cmsg1Index"]]) + cinv_msg = bytes.fromhex(test_case["cinvMsg"]) + expected_error = test_case["expectedError"] + try: + chilldkg.participant_step2(hostseckey, state1, cmsg1, aux_rand) + except UnknownFaultyParticipantOrCoordinatorError as e: + assert_raises( + lambda e=e: chilldkg.participant_investigate(e, cinv_msg), + expected_error, + ) + except Exception as e: + raise AssertionError(f"Wrong exception raised: {type(e).__name__}") + else: + raise AssertionError("Expected exception") + total_cases += 1 + assert test_case["tcId"] == total_cases + + assert test_data["totalTests"] == total_cases + + +def test_coordinator_step1_vectors(): + input_file = VECTORS_DIR / "coordinator_step1_vectors.json" + with open(input_file) as f: + test_data = json.load(f) + + total_cases = 0 + for group in test_data["testGroups"]: + pmsg1_pool = group["pmsg1Pool"] + + for test_case in group["validTestCases"]: + pmsgs1 = [bytes.fromhex(pmsg1_pool[i]) for i in test_case["pmsg1Indices"]] + params = params_from_dict(test_case["params"]) + expected_cmsg1 = test_case["expectedCmsg1"] + _, cmsg1 = chilldkg.coordinator_step1(pmsgs1, params) + assert bytes.fromhex(expected_cmsg1) == cmsg1 + total_cases += 1 + assert test_case["tcId"] == total_cases + + for test_case in group["errorTestCases"]: + pmsgs1 = [bytes.fromhex(pmsg1_pool[i]) for i in test_case["pmsg1Indices"]] + params = params_from_dict(test_case["params"]) + expected_error = test_case["expectedError"] + assert_raises( + lambda: chilldkg.coordinator_step1(pmsgs1, params), expected_error + ) + total_cases += 1 + assert test_case["tcId"] == total_cases + + assert test_data["totalTests"] == total_cases + + +def test_coordinator_finalize_vectors(): + input_file = VECTORS_DIR / "coordinator_finalize_vectors.json" + with open(input_file) as f: + test_data = json.load(f) + + total_cases = 0 + + for group in test_data["testGroups"]: + params = params_from_dict(group["params"]) + pmsgs1 = [bytes.fromhex(m) for m in group["pmsgs1"]] + pmsg2_pool = group["pmsg2Pool"] + + state, cmsg1 = chilldkg.coordinator_step1(pmsgs1, params) + assert bytes.fromhex(group["cmsg1"]) == cmsg1 + + for test_case in group["validTestCases"]: + pmsgs2 = [bytes.fromhex(pmsg2_pool[i]) for i in test_case["pmsg2Indices"]] + cmsg2, cout, crec = chilldkg.coordinator_finalize(state, pmsgs2) + expected_cmsg2 = test_case["expectedOutput"]["cmsg2"] + expected_cout = test_case["expectedOutput"]["dkgOutput"] + expected_crec = test_case["expectedOutput"]["recoveryData"] + assert bytes.fromhex(expected_cmsg2) == cmsg2 + assert expected_cout == dkg_output_asdict(cout) + assert bytes.fromhex(expected_crec) == crec + total_cases += 1 + assert test_case["tcId"] == total_cases + + for test_case in group["errorTestCases"]: + pmsgs2 = [bytes.fromhex(pmsg2_pool[i]) for i in test_case["pmsg2Indices"]] + expected_error = test_case["expectedError"] + assert_raises( + lambda: chilldkg.coordinator_finalize(state, pmsgs2), expected_error + ) + total_cases += 1 + assert test_case["tcId"] == total_cases + + assert test_data["totalTests"] == total_cases + + +def test_coordinator_investigate_vectors(): + input_file = VECTORS_DIR / "coordinator_investigate_vectors.json" + with open(input_file) as f: + test_data = json.load(f) + + total_cases = 0 + + for group in test_data["testGroups"]: + params = params_from_dict(group["params"]) + pmsgs1 = [bytes.fromhex(m) for m in group["pmsgs1"]] + + for test_case in group["validTestCases"]: + cinv_msgs = chilldkg.coordinator_investigate(pmsgs1, params) + expected_cinv_msgs = test_case["expectedCinvMsgs"] + assert [bytes.fromhex(m) for m in expected_cinv_msgs] == cinv_msgs + total_cases += 1 + assert test_case["tcId"] == total_cases + + assert test_data["totalTests"] == total_cases + + +def test_recover_vectors(): + input_file = VECTORS_DIR / "recover_vectors.json" + with open(input_file) as f: + test_data = json.load(f) + + valid_test_cases = test_data["validTestCases"] + error_test_cases = test_data["errorTestCases"] + assert test_data["totalTests"] == len(valid_test_cases) + len(error_test_cases) + + for test_case in valid_test_cases: + hostseckey = ( + bytes.fromhex(test_case["hostseckey"]) if test_case["hostseckey"] else None + ) + recovery_data = bytes.fromhex(test_case["recoveryData"]) + if hostseckey is None: + out, params = chilldkg.coordinator_recover(recovery_data) + else: + out, params = chilldkg.participant_recover(hostseckey, recovery_data) + expected_out = test_case["expectedOutput"]["dkgOutput"] + expected_params = test_case["expectedOutput"]["params"] + assert expected_out == dkg_output_asdict(out) + assert expected_params == params_asdict(params) + + for test_case in error_test_cases: + hostseckey = ( + bytes.fromhex(test_case["hostseckey"]) if test_case["hostseckey"] else None + ) + recovery_data = bytes.fromhex(test_case["recoveryData"]) + expected_error = test_case["expectedError"] + if hostseckey is None: + assert_raises( + lambda: chilldkg.coordinator_recover(recovery_data), expected_error + ) + else: + assert_raises( + lambda: chilldkg.participant_recover(hostseckey, recovery_data), + expected_error, + ) + + +def test_recovery_acknowledgment(): + t, n = 2, 3 + hostseckeys = [random_bytes(32) for _ in range(n)] + hostpubkeys = [chilldkg.hostpubkey_gen(hostseckey) for hostseckey in hostseckeys] + params = chilldkg.SessionParams(hostpubkeys, t) + + results = simulate_chilldkg(hostseckeys, t, investigation=False) + recovery_data = results[1][1] # First participant's recovery data + + ack_sigs = [] + for i in range(n): + ack_sig = chilldkg.participant_recovery_ack_sign( + hostseckeys[i], recovery_data, params, random_bytes(32) + ) + assert len(ack_sig) == 64 + ack_sigs.append(ack_sig) + + chilldkg.participant_recovery_acks_verify(recovery_data, params, ack_sigs) + + # Wrong hostseckey length + try: + chilldkg.participant_recovery_ack_sign( + random_bytes(16), recovery_data, params, random_bytes(32) + ) + except ValueError: + pass + else: + assert False, "Expected exception" + + # Invalid hostpubkey in params + invalid_hostpubkey = b"\x03" + 31 * b"\x00" + b"\x05" + invalid_params = chilldkg.SessionParams([hostpubkeys[0], invalid_hostpubkey], t) + try: + chilldkg.participant_recovery_ack_sign( + hostseckeys[0], recovery_data, invalid_params, random_bytes(32) + ) + except chilldkg.InvalidHostPubkeyError: + pass + else: + assert False, "Expected exception" + + try: + chilldkg.participant_recovery_acks_verify( + recovery_data, invalid_params, ack_sigs + ) + except chilldkg.InvalidHostPubkeyError: + pass + else: + assert False, "Expected exception" + + # Duplicate hostpubkey in params + invalid_params = chilldkg.SessionParams([hostpubkeys[0], hostpubkeys[0]], t) + try: + chilldkg.participant_recovery_ack_sign( + hostseckeys[0], recovery_data, invalid_params, random_bytes(32) + ) + except chilldkg.DuplicateHostPubkeyError: + pass + else: + assert False, "Expected exception" + + # Invalid threshold in params + invalid_params = chilldkg.SessionParams(hostpubkeys, n + 1) + try: + chilldkg.participant_recovery_ack_sign( + hostseckeys[0], recovery_data, invalid_params, random_bytes(32) + ) + except chilldkg.ThresholdOrCountError: + pass + else: + assert False, "Expected exception" + + # Wrong hostseckey + try: + chilldkg.participant_recovery_ack_sign( + random_bytes(32), recovery_data, params, random_bytes(32) + ) + except chilldkg.HostSeckeyError: + pass + else: + assert False, "Expected exception" + + # Invalid randomness length + try: + chilldkg.participant_recovery_ack_sign( + hostseckeys[0], recovery_data, params, random_bytes(16) + ) + except ValueError: + pass + else: + assert False, "Expected exception" + + # Mismatched params + invalid_params = chilldkg.SessionParams(hostpubkeys, t + 1) + try: + chilldkg.participant_recovery_ack_sign( + hostseckeys[0], recovery_data, invalid_params, random_bytes(32) + ) + except chilldkg.RecoveryDataError: + pass + else: + assert False, "Expected exception" + + try: + chilldkg.participant_recovery_acks_verify( + recovery_data, invalid_params, ack_sigs + ) + except chilldkg.RecoveryDataError: + pass + else: + assert False, "Expected exception" + + # Corrupted recovery data + corrupted_recovery_data = random_bytes(len(recovery_data)) + try: + chilldkg.participant_recovery_ack_sign( + hostseckeys[0], corrupted_recovery_data, params, random_bytes(32) + ) + except chilldkg.RecoveryDataError: + pass + else: + assert False, "Expected exception" + + try: + chilldkg.participant_recovery_acks_verify( + corrupted_recovery_data, params, ack_sigs + ) + except chilldkg.RecoveryDataError: + pass + else: + assert False, "Expected exception" + + # Invalid signature + invalid_ack_sigs = ack_sigs[:] + invalid_ack_sigs[1] = random_bytes(64) + try: + chilldkg.participant_recovery_acks_verify( + recovery_data, params, invalid_ack_sigs + ) + except chilldkg.InvalidRecoveryAckError as e: + assert e.participant_id == 1 + else: + assert False, "Expected exception" + + # Wrong signature length + wrong_length_sigs = ack_sigs[:] + wrong_length_sigs[0] = random_bytes(32) + try: + chilldkg.participant_recovery_acks_verify( + recovery_data, params, wrong_length_sigs + ) + except ValueError: + pass + else: + assert False, "Expected exception" + + # Wrong number of signatures + wrong_count_sigs = ack_sigs[:-1] # n-1 instead of n + try: + chilldkg.participant_recovery_acks_verify( + recovery_data, params, wrong_count_sigs + ) + except ValueError: + pass + else: + assert False, "Expected exception" + + +test_chilldkg_params_validate() +test_vss_correctness() +test_recover_secret() +test_recovery_acknowledgment() +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_simplpedpop, investigation=True) + test_correctness(t, n, simulate_encpedpop) + test_correctness(t, n, simulate_encpedpop, investigation=True) + test_correctness(t, n, simulate_chilldkg, recovery=True) + test_correctness(t, n, simulate_chilldkg, recovery=True, investigation=True) + test_correctness(t, n, simulate_chilldkg_full, recovery=True) +test_hostpubkey_gen_vectors() +test_params_hash_vectors() +test_participant_step1_vectors() +test_participant_step2_vectors() +test_participant_finalize_vectors() +test_participant_investigate_vectors() +test_coordinator_step1_vectors() +test_coordinator_finalize_vectors() +test_coordinator_investigate_vectors() +test_recover_vectors() diff --git a/bip-chilldkg/python/tests.sh b/bip-chilldkg/python/tests.sh new file mode 100755 index 0000000000..bed8a4ef7a --- /dev/null +++ b/bip-chilldkg/python/tests.sh @@ -0,0 +1,36 @@ +#!/bin/sh + +# Check that mypy is available +check_availability() { + command -v "$1" > /dev/null 2>&1 || { + echo >&2 "$1 is required but it's not installed. Aborting." + exit 1 + } +} + +check_availability mypy +check_availability ruff + +cd "$(dirname "$0")" || exit 1 + +status=0 + +# Run a command but do not exit on failure. Record that something failed. +run_keep() { + "$@" + rc=$? + if [ "$rc" -ne 0 ]; then + status=1 + fi + return 0 +} + +run_keep ruff check --quiet +run_keep ruff format --diff --quiet +run_keep mypy --no-error-summary . +run_keep mypy --no-error-summary --strict --untyped-calls-exclude=secp256k1lab -p chilldkg_ref --follow-imports=silent + +run_keep python3 gen_vectors.py +run_keep python3 tests.py + +exit $status diff --git a/bip-chilldkg/vectors/COPYING b/bip-chilldkg/vectors/COPYING new file mode 100644 index 0000000000..9b8919dc0c --- /dev/null +++ b/bip-chilldkg/vectors/COPYING @@ -0,0 +1,9 @@ +MIT OR CC0-1.0 + +The files in this directory are provided under one of the following sets of +terms, at your choice: + + - The MIT License + https://opensource.org/license/MIT (see also ../COPYING) + - CC0 1.0 Universal + https://creativecommons.org/publicdomain/zero/1.0/ diff --git a/bip-chilldkg/vectors/coordinator_finalize_vectors.json b/bip-chilldkg/vectors/coordinator_finalize_vectors.json new file mode 100644 index 0000000000..691ead01dc --- /dev/null +++ b/bip-chilldkg/vectors/coordinator_finalize_vectors.json @@ -0,0 +1,429 @@ +{ + "description": [ + "Test vectors for coordinator_finalize(cstate, pmsgs2).", + "Collects participant round-2 signatures and produces the final certificate (cmsg2).", + "", + "Harness setup:", + " 1. Call coordinator_step1(pmsgs1, params) to obtain (cstate, cmsg1_out).", + " Assert cmsg1_out == cmsg1.", + "", + "Assemble the pmsgs2 list from pmsg2Pool using pmsg2Indices:", + " pmsgs2 = [pmsg2Pool[i] for i in pmsg2Indices]", + "", + "For each valid test case:", + " Call coordinator_finalize(cstate, pmsgs2).", + " Verify the result matches expectedOutput (cmsg2, dkgOutput, recoveryData).", + "", + "For each error test case:", + " Call coordinator_finalize(cstate, pmsgs2).", + " Verify it raises an exception matching expectedError." + ], + "totalTests": 20, + "testGroups": [ + { + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3" + ], + "t": 2 + }, + "pmsgs1": [ + "036BEE122EF60CBBB5DFC3572FE6D3A9AC0B35BB427E250F42C38F7C8229889CAB03E99E34AA2A0646563DF90F2407D0D0E345EAE2CF7F07F9F6561302F724EFA74708AE5A94BBDD3FC53924623E0D6764E089D679048F699118D7807884AD13CF484866D8210BCB76DAEB103AD6BA1CED0DBFE78F1E8A44F26FC69D5464BF23324D0260537C74BC2B79676CDE217FFA7D48236D4A309954153DEB39A31AADF6B369CCC6C144287B356EF70378A7F1054FF00354A01352587DC076554A82CD324B66A17006774A86C116C19F2C021793EAB87DF19EE3A978C60F757BA192DA23BAE5F56A3612D0720F35828FCFBA9DFEA790883B185A8B75A27449AF23169C919B47C7", + "037C42E200B601CD439CDEAA12FB2BD8949C566E59096AA9207165C6FF0602B1D6020584BF7D8C710F6A4988C6B16848C4BBA45A819107B2C49190E2D6FB666011AE53EF7BE0C965DBBA2418E1FAA16FB9CA5FC990337E0E1858F263A8B88AE081D053CB4BCA7D90B63225ABA7C40F4B86B1D2E91D0EEFC52657B70695BE96FEEF37022E7B7DD7A0B4A72097703E60648B615A4DDDF2D715A5E0992379537A44C13AD17A5C832E385A79C54BB5E400CF3FA2DF8A747E7F362AA3345CB0097DAF2C715FAA97DCA9CF742C4A54D5805B0705378C68129809D8D22A753A3F8F92FE61D7D14D27B6E1FE9BCEE26C96CD0B2CB18AE6A5D80C5792EA1FA2F09011B3F2596EE7", + "026F714517277FB154C47E487FBE415C25C0D0F79466F1479B31A8D78319978D8A0203E26DE13A90DC4465BF155DCE35DFED355FE1902F39E9B4139F038F12C04DDF68EFCB547A2ED10F9D35C10E8FFE3A5A7F049FF7FFBF5F7400586830FBAC2B6908C2742DE20520065E89CF8CF7DBD279063967C0E1E77A97297BFDDF28B58C1A03D1FD5A186167E5DE84B11C0F56E1FC6808D2DB35E6E3587243CF59DE605CB806A124DC237A739AD28D8624726DD0277B3B1D7D030019F0FBBA9EE12D6007FEE360BC33A121886C897AC86F4F99CD493743B05216D0408C88A32D90A109837B6547CAC26451623B282A4FC67A2E454E27C7437BA3F620C687F061E9B264652DD6" + ], + "cmsg1": "036BEE122EF60CBBB5DFC3572FE6D3A9AC0B35BB427E250F42C38F7C8229889CAB037C42E200B601CD439CDEAA12FB2BD8949C566E59096AA9207165C6FF0602B1D6026F714517277FB154C47E487FBE415C25C0D0F79466F1479B31A8D78319978D8A03B9C3F15E5C0E086EDE931D69BABD36167BE6CCA8705378F8849A4F00EDA2941308AE5A94BBDD3FC53924623E0D6764E089D679048F699118D7807884AD13CF484866D8210BCB76DAEB103AD6BA1CED0DBFE78F1E8A44F26FC69D5464BF23324D53EF7BE0C965DBBA2418E1FAA16FB9CA5FC990337E0E1858F263A8B88AE081D053CB4BCA7D90B63225ABA7C40F4B86B1D2E91D0EEFC52657B70695BE96FEEF3768EFCB547A2ED10F9D35C10E8FFE3A5A7F049FF7FFBF5F7400586830FBAC2B6908C2742DE20520065E89CF8CF7DBD279063967C0E1E77A97297BFDDF28B58C1A0260537C74BC2B79676CDE217FFA7D48236D4A309954153DEB39A31AADF6B369CC022E7B7DD7A0B4A72097703E60648B615A4DDDF2D715A5E0992379537A44C13AD103D1FD5A186167E5DE84B11C0F56E1FC6808D2DB35E6E3587243CF59DE605CB806E242A37A2E03838EDCB4B064425FBA5F5F8331EDDF79B46AACC70EEB714995A27B5A879577BDAF956EC9F1C234BD3942E2B2F0E372902637993C54815B69F7EAFF288C16C20D3F8D26B64E23599E6996A833E286FEAD5A7490151202E859E484", + "pmsg2Pool": [ + "A6C2DE535556E47C8F3427B9716CFC4A66ADD0F2788AB793E2FBFF7D81DA06315C3F62CAA2B85E369922E68A94113EB839B5E3E424A4F4BF2E8D2908F517E8BE", + "3B1B96C40D7F9A378F2A61FACF8469447344B5280F5E7B3C66B560709A6FA4D7E083E650F3C109995ACB96EEA2EE4EF252257E1BF92FFF121CE916DE2D7C4B54", + "79195E7CBE22A8B53C41012ABEA37F0B59579E43EDC6477E8CC5C7EB9F3C8BF7AEC850C63A659FADD91C9063908054FC4D193594E132ADFBE1F24BE74C8BEA7A", + "3B1B96C40D7F9A378F2A61FACF8469447344B5280F5E7B3C66B560709A6FA4D7E083E650F3C109995ACB96EEA2EE4EF252257E1BF92FFF121CE916DE2D7C4B", + "09C289578B96E6283AB13E4741FB489FC147FB1A5F446A314BA73C052131EFB04B83247A0BCEDF5205202AD64188B24B0BC5B51A17AEB218BD98DBE000C843B9" + ], + "validTestCases": [ + { + "tcId": 1, + "pmsg2Indices": [ + 0, + 1, + 2 + ], + "expectedOutput": { + "cmsg2": "A6C2DE535556E47C8F3427B9716CFC4A66ADD0F2788AB793E2FBFF7D81DA06315C3F62CAA2B85E369922E68A94113EB839B5E3E424A4F4BF2E8D2908F517E8BE3B1B96C40D7F9A378F2A61FACF8469447344B5280F5E7B3C66B560709A6FA4D7E083E650F3C109995ACB96EEA2EE4EF252257E1BF92FFF121CE916DE2D7C4B5479195E7CBE22A8B53C41012ABEA37F0B59579E43EDC6477E8CC5C7EB9F3C8BF7AEC850C63A659FADD91C9063908054FC4D193594E132ADFBE1F24BE74C8BEA7A", + "dkgOutput": { + "secshare": null, + "threshPk": "03DF2E2C605ACE90BFAAE275614FDA6D6233B1438EE6D8CE1EA74111887E3110F7", + "pubshares": [ + "025C8EC2A3D153823BD2820F6943FD4010B9A9C48D779A6845D2CDDB0EFE36E1EC", + "03397F3E8A16CCF5BD71E084163A9370C290F2915BC4BF80FDEA1C0E713B91F9C3", + "037709A9E38D16A7C57821BF5D97DEBA9588BB0C3DAC8B29BB732D088AB70FCFB6" + ] + }, + "recoveryData": "0000000203B74C6FD38288DDF94300E887B12DD40F655791144A07C32487FBD81CC228C07903B9C3F15E5C0E086EDE931D69BABD36167BE6CCA8705378F8849A4F00EDA2941303AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F303AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A30260537C74BC2B79676CDE217FFA7D48236D4A309954153DEB39A31AADF6B369CC022E7B7DD7A0B4A72097703E60648B615A4DDDF2D715A5E0992379537A44C13AD103D1FD5A186167E5DE84B11C0F56E1FC6808D2DB35E6E3587243CF59DE605CB806E242A37A2E03838EDCB4B064425FBA5F5F8331EDDF79B46AACC70EEB714995A27B5A879577BDAF956EC9F1C234BD3942E2B2F0E372902637993C54815B69F7EAFF288C16C20D3F8D26B64E23599E6996A833E286FEAD5A7490151202E859E484A6C2DE535556E47C8F3427B9716CFC4A66ADD0F2788AB793E2FBFF7D81DA06315C3F62CAA2B85E369922E68A94113EB839B5E3E424A4F4BF2E8D2908F517E8BE3B1B96C40D7F9A378F2A61FACF8469447344B5280F5E7B3C66B560709A6FA4D7E083E650F3C109995ACB96EEA2EE4EF252257E1BF92FFF121CE916DE2D7C4B5479195E7CBE22A8B53C41012ABEA37F0B59579E43EDC6477E8CC5C7EB9F3C8BF7AEC850C63A659FADD91C9063908054FC4D193594E132ADFBE1F24BE74C8BEA7A" + }, + "comment": "valid coordinator finalize" + } + ], + "errorTestCases": [ + { + "tcId": 2, + "pmsg2Indices": [ + 0, + 3, + 2 + ], + "expectedError": { + "type": "ValueError" + }, + "comment": "participant with id 1 sent a short signature" + }, + { + "tcId": 3, + "pmsg2Indices": [ + 0, + 1 + ], + "expectedError": { + "type": "ValueError" + }, + "comment": "invalid pmsgs2: fewer entries than participants" + }, + { + "tcId": 4, + "pmsg2Indices": [ + 0, + 1, + 2, + 0 + ], + "expectedError": { + "type": "ValueError" + }, + "comment": "invalid pmsgs2: more entries than participants" + }, + { + "tcId": 5, + "pmsg2Indices": [ + 0, + 4, + 2 + ], + "expectedError": { + "type": "FaultyParticipantError", + "message": "Participant has provided an invalid signature for the certificate", + "participantId": 1 + }, + "comment": "participant with id 1 sent an invalid signature" + } + ] + }, + { + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3" + ], + "t": 1 + }, + "pmsgs1": [ + "0216C95A7FA9079C73184E8646721F59BB0361A839FD6DB9B8CF53CA1EA5B9E1C6558B3C0BFEA2180B7449D7EF101A57D8CF9FC0E32D77FAFDC712A3897007419D6CE9F8FEA08EC9935120955BDFD2519F7B29AD3E0248DA19A0E7D3900FE14CEA02BC88DD206C8AD8C1B52F93AD147695802206D0CA6AB756BD51E5FF5BE858FD219827C7BD6ECCE374663262B90B8D2944C94F9AB1E83F38DBD12823DD7D58B8B3CF1C0AB792C1AF61FA3866CCC4221CAC3CB9D35F7F5358990AB02D7E246BCEDD2AA0622F84004D5AED9B4FC855F20AE22CE260B6E79F56DE5BC2F5B73D137DE3", + "02DA601E3FC8D21A247C5677D9CACFFE0BB86860BC1C233BECA0D97776AAE309DA832AE406B679DEE20F859D361B3C2A1A9EEED646F1B5B6761D1370889D2C0F4EFFFFB7792DD5B2EF16A87A3868280F745D4FAA4EB032695FF0E9285DB7155B8303F216E714F96E6AAE00E3FAAF5188FF6F66C71B932E10B17AA8403B17DE9A8F52F4CDA3696B4B7701D9CA898B23E0211D9A3BB64CD130C329BDFC826BCBFCA13D563259BE0136A48FE852FC559B1EA6DE9EB24A43FBE2B42E3F1AE85AB03BB99B54825042D24EFCD6B3C4E2F29DFA11B55973C08DEADB120BA5B90EF12D74353B", + "02FB246B4866569A7A495CFFB1133DAF9096FE4FA22EDA6AF9D7A8F236F9C222B68A8C833B35FF0445F94744CBDE8FFC29CBBBFD3B7BA7F3B00A56E96EFA24F6C582EA017BA6A173AA31E4E9F2F5F74D1C4ABA2BBA5B9FACE5F3042B5B2323AB81038AA8962D7EBBF847CE551B1EF8D0D5E8A051B7FD6A29127709F35A6C18CED300EE7CAEA466336EF62D77586E88F1689219851C1473C34AA555FB08E2A7BD90977A1AB2888E05E016EFE2799D43300A3A576497D23F32CFBABE18A069512E2A96826AFB726FF50193F6F50E4CA0A630DEC81B68554EEFDA5331C04E7E7FFE7C1A" + ], + "cmsg1": "0216C95A7FA9079C73184E8646721F59BB0361A839FD6DB9B8CF53CA1EA5B9E1C602DA601E3FC8D21A247C5677D9CACFFE0BB86860BC1C233BECA0D97776AAE309DA02FB246B4866569A7A495CFFB1133DAF9096FE4FA22EDA6AF9D7A8F236F9C222B6558B3C0BFEA2180B7449D7EF101A57D8CF9FC0E32D77FAFDC712A3897007419D6CE9F8FEA08EC9935120955BDFD2519F7B29AD3E0248DA19A0E7D3900FE14CEA832AE406B679DEE20F859D361B3C2A1A9EEED646F1B5B6761D1370889D2C0F4EFFFFB7792DD5B2EF16A87A3868280F745D4FAA4EB032695FF0E9285DB7155B838A8C833B35FF0445F94744CBDE8FFC29CBBBFD3B7BA7F3B00A56E96EFA24F6C582EA017BA6A173AA31E4E9F2F5F74D1C4ABA2BBA5B9FACE5F3042B5B2323AB8102BC88DD206C8AD8C1B52F93AD147695802206D0CA6AB756BD51E5FF5BE858FD2103F216E714F96E6AAE00E3FAAF5188FF6F66C71B932E10B17AA8403B17DE9A8F52038AA8962D7EBBF847CE551B1EF8D0D5E8A051B7FD6A29127709F35A6C18CED3007B7219CB404BC96C6D7444B2B85EB2F707B2B345CEA20633657AF21250A668059F6916FE21FE3408D26DDCBFA270CDC67821D88F0B203C46481157B5559F71CD018DADE4C6444BC59855410794924D7793C2ACB37221A3017369F49A1A4FEDF7", + "pmsg2Pool": [ + "82E33ED511440D5609481CB77FDE1E385865F4F00C2E84ACF8DED01EC3532C714C28D13DD572C5808AF82272C1A782B3C0C720EC9527A7AFFE2535688D29AC06", + "B2FDC730FF8396ED094040BA12EA54F3D0B53DBD4613DCDF4AE0C89A6C7A9AC6A973EB2157189E3CBFB144FAE1F421F42450F49A48FB9027C8FDA37BAFE334EE", + "A9B4EADAC901B2D6A24ADFF77FBFA1A948F8846CA445E7A7E970D26EA440370F1AD8302DDB489191F5536CA4B102EF5535406BBB1D4003AAA6AEF8E83F7D2AD6", + "B2FDC730FF8396ED094040BA12EA54F3D0B53DBD4613DCDF4AE0C89A6C7A9AC6A973EB2157189E3CBFB144FAE1F421F42450F49A48FB9027C8FDA37BAFE334", + "09C289578B96E6283AB13E4741FB489FC147FB1A5F446A314BA73C052131EFB04B83247A0BCEDF5205202AD64188B24B0BC5B51A17AEB218BD98DBE000C843B9" + ], + "validTestCases": [ + { + "tcId": 6, + "pmsg2Indices": [ + 0, + 1, + 2 + ], + "expectedOutput": { + "cmsg2": "82E33ED511440D5609481CB77FDE1E385865F4F00C2E84ACF8DED01EC3532C714C28D13DD572C5808AF82272C1A782B3C0C720EC9527A7AFFE2535688D29AC06B2FDC730FF8396ED094040BA12EA54F3D0B53DBD4613DCDF4AE0C89A6C7A9AC6A973EB2157189E3CBFB144FAE1F421F42450F49A48FB9027C8FDA37BAFE334EEA9B4EADAC901B2D6A24ADFF77FBFA1A948F8846CA445E7A7E970D26EA440370F1AD8302DDB489191F5536CA4B102EF5535406BBB1D4003AAA6AEF8E83F7D2AD6", + "dkgOutput": { + "secshare": null, + "threshPk": "0285488475157EAD0313E90209EB43F010ED843D9397DC427E029352DEF8402F38", + "pubshares": [ + "0285488475157EAD0313E90209EB43F010ED843D9397DC427E029352DEF8402F38", + "0285488475157EAD0313E90209EB43F010ED843D9397DC427E029352DEF8402F38", + "0285488475157EAD0313E90209EB43F010ED843D9397DC427E029352DEF8402F38" + ] + }, + "recoveryData": "0000000103D944E06ECEC686276C06903F999B55124292AC186A8CFF91C4B5812869F43C0103AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F303AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A302BC88DD206C8AD8C1B52F93AD147695802206D0CA6AB756BD51E5FF5BE858FD2103F216E714F96E6AAE00E3FAAF5188FF6F66C71B932E10B17AA8403B17DE9A8F52038AA8962D7EBBF847CE551B1EF8D0D5E8A051B7FD6A29127709F35A6C18CED3007B7219CB404BC96C6D7444B2B85EB2F707B2B345CEA20633657AF21250A668059F6916FE21FE3408D26DDCBFA270CDC67821D88F0B203C46481157B5559F71CD018DADE4C6444BC59855410794924D7793C2ACB37221A3017369F49A1A4FEDF782E33ED511440D5609481CB77FDE1E385865F4F00C2E84ACF8DED01EC3532C714C28D13DD572C5808AF82272C1A782B3C0C720EC9527A7AFFE2535688D29AC06B2FDC730FF8396ED094040BA12EA54F3D0B53DBD4613DCDF4AE0C89A6C7A9AC6A973EB2157189E3CBFB144FAE1F421F42450F49A48FB9027C8FDA37BAFE334EEA9B4EADAC901B2D6A24ADFF77FBFA1A948F8846CA445E7A7E970D26EA440370F1AD8302DDB489191F5536CA4B102EF5535406BBB1D4003AAA6AEF8E83F7D2AD6" + }, + "comment": "valid coordinator finalize" + } + ], + "errorTestCases": [ + { + "tcId": 7, + "pmsg2Indices": [ + 0, + 3, + 2 + ], + "expectedError": { + "type": "ValueError" + }, + "comment": "participant with id 1 sent a short signature" + }, + { + "tcId": 8, + "pmsg2Indices": [ + 0, + 1 + ], + "expectedError": { + "type": "ValueError" + }, + "comment": "invalid pmsgs2: fewer entries than participants" + }, + { + "tcId": 9, + "pmsg2Indices": [ + 0, + 1, + 2, + 0 + ], + "expectedError": { + "type": "ValueError" + }, + "comment": "invalid pmsgs2: more entries than participants" + }, + { + "tcId": 10, + "pmsg2Indices": [ + 0, + 4, + 2 + ], + "expectedError": { + "type": "FaultyParticipantError", + "message": "Participant has provided an invalid signature for the certificate", + "participantId": 1 + }, + "comment": "participant with id 1 sent an invalid signature" + } + ] + }, + { + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3" + ], + "t": 3 + }, + "pmsgs1": [ + "02F659A9668C1E7FB2BF4B457CA91C9925B3AB9F3F029C489C53603EF6AF90199B03BB0D7426739F2FAB24C6A0B2693273BBC92E03D3CB1A252956D372E4484F3EFE030C43B612EC5E536EF7853FCC8744BF77AF272485349484BDAA6FB6F2222FDD5A92AE219E3FE6B306490A7510AF6D9C981983DE7E4A83127F350451986EB2AF829A8C8BF483ADD195B5C7B08073D9548FFCDFB5626F0DA6A26468AD9BE54D5C83026C6EBBC55EDC86ABA62BA55A3375A940E731228F214937BF8A2FC58B29AE8F087C47A6836F30317C4B914E33F10B72CA6EDD116A3DF9E8BFE997045F9F957D31A43BB05F6162602B96E5D8C0AE3A07C8228E41DE772B0ACD96937B6CD0115ADE0ACB3150F08EA8668C262DC091C6ACA77674E8C749023FA6FB5D6938BEC73CD8", + "034619FE6A2FF09FE393CACB98B0708545255926B9CB33103567AAF2EE4D23B05403D16A8AFDD68449C6C00FF346EC9D29EC8D3D681F3B2EC04616F0A75693940C2402BF150900B4B78A40CF51C1429F0C796B7EC6A4B9A6337B346895DAD6E7F4108D18AB016E948B6EE152EEC9B6AD07ADC102177C6060C5FA6AB774DE136D59657C62D596F9C9477FF09A14654D48AC94B9CF381221ED27DC26B0F224C70039771F0334032916F989086FB5C62722C096F0EC6A85808DB2BFA270227A72245BD7AC1BB9BC513A6059135E04745D8BA961BA2B73A8F109469632CACD2DEA19DD9F985D03F3FCD86C88731C94E5588CA37E072DBF0A968AF541994B5A5D3E6011C9D92660F6A85166FBC97DE076DD7F81CB81CF07262D7296453D47065FD31A952CA8C9", + "0250A0832ED8FECB459561A2BFE1103A29CD4B23D53B27B707376FE0E67562F200020F17B8706ED73D63DEA8F15898E59E062E761C8E04DC3BDD78092C006A73440F022DF27B11574AE811C767A2E2A8645E9D63910CFD74F52F65488A5A64133EFBDAACBC3B4D52DD7A762F752250E41218F5295C3DDFCFF43350759D74A62B695352CE3C302E8718BA21C85BED769AEF3846A88E691AE945D6EBA09F51ACB0143D1002522C52C88A6B0B1E9CF8943960E87722C1D5307069723C25B151939A58B08C10E0165D9516387D7C6A7EDE123BB40683A6037628D3853322CF37FE31A36960DABA80A39126B2817F0E441AFFB94ECB9C9133C9C377EDDC7BE2BB97A0A43675102F91CB07BEC0D3422EA4ADB05AB0264DFCBAA0D94EBB93D825110A8816C8D1F6" + ], + "cmsg1": "02F659A9668C1E7FB2BF4B457CA91C9925B3AB9F3F029C489C53603EF6AF90199B034619FE6A2FF09FE393CACB98B0708545255926B9CB33103567AAF2EE4D23B0540250A0832ED8FECB459561A2BFE1103A29CD4B23D53B27B707376FE0E67562F20003D2BAB55842CB855189CF690BA0E2C588AEFCD985F6E6B60C3CFCBDBF42FC68F702D17C1ACB4697C8032830CFA0099BE475C8DBDC63E2CE294795C8AAAF69EF908792AE219E3FE6B306490A7510AF6D9C981983DE7E4A83127F350451986EB2AF829A8C8BF483ADD195B5C7B08073D9548FFCDFB5626F0DA6A26468AD9BE54D5C8318AB016E948B6EE152EEC9B6AD07ADC102177C6060C5FA6AB774DE136D59657C62D596F9C9477FF09A14654D48AC94B9CF381221ED27DC26B0F224C70039771FACBC3B4D52DD7A762F752250E41218F5295C3DDFCFF43350759D74A62B695352CE3C302E8718BA21C85BED769AEF3846A88E691AE945D6EBA09F51ACB0143D10026C6EBBC55EDC86ABA62BA55A3375A940E731228F214937BF8A2FC58B29AE8F080334032916F989086FB5C62722C096F0EC6A85808DB2BFA270227A72245BD7AC1B02522C52C88A6B0B1E9CF8943960E87722C1D5307069723C25B151939A58B08C10161A5552E5C1C256BA8489D1D621337C132BBECEF9840E3606582F918031F3E662B050C8F49D54C73A0F4C4D0B06DA93B81DC5463511E05913D9F2E0B5DB67D39B53A4AA164B45269B41B8F06E4254C47A55B7132E0310C626CE46DB6ABCB797", + "pmsg2Pool": [ + "BFD15AA9C0A086B4BC83CC1FBE2864519E95360101E038B56CC6F58A13BDCBCDA5310629AD258C8FEC60DEB8ED49A2749E2EDD2204257F41713CF3181FB4F89B", + "0017D390C1E89DA9D738F5933E1A35B70C10DED152768C9FAB8593190E7417754AB67C0D15E873A2594A7EA490CC0EF1643A153A5B7467232FAE413CC8A8D8E7", + "E27651C1E2907EDFA9CA3310332C9BE64A22B78FF873C307632E855E9E24E86C6F9E10F7D135A06F5B6839DFAA2F78973DAD89179131016D55D4E7B7FF2837F5", + "0017D390C1E89DA9D738F5933E1A35B70C10DED152768C9FAB8593190E7417754AB67C0D15E873A2594A7EA490CC0EF1643A153A5B7467232FAE413CC8A8D8", + "09C289578B96E6283AB13E4741FB489FC147FB1A5F446A314BA73C052131EFB04B83247A0BCEDF5205202AD64188B24B0BC5B51A17AEB218BD98DBE000C843B9" + ], + "validTestCases": [ + { + "tcId": 11, + "pmsg2Indices": [ + 0, + 1, + 2 + ], + "expectedOutput": { + "cmsg2": "BFD15AA9C0A086B4BC83CC1FBE2864519E95360101E038B56CC6F58A13BDCBCDA5310629AD258C8FEC60DEB8ED49A2749E2EDD2204257F41713CF3181FB4F89B0017D390C1E89DA9D738F5933E1A35B70C10DED152768C9FAB8593190E7417754AB67C0D15E873A2594A7EA490CC0EF1643A153A5B7467232FAE413CC8A8D8E7E27651C1E2907EDFA9CA3310332C9BE64A22B78FF873C307632E855E9E24E86C6F9E10F7D135A06F5B6839DFAA2F78973DAD89179131016D55D4E7B7FF2837F5", + "dkgOutput": { + "secshare": null, + "threshPk": "0387DF054E5992F51C6C9DF60EB956DFAC73F12B218FF559159FA1A14945BAEA97", + "pubshares": [ + "0338FF57248C1B692D92C39D9FF0834CE09DC84136D45A74F59A34A0DE8F82AF7C", + "03FEA503453A344A1FB374B6478A51D3220B6A7C881911FAA7656EB268E389B2E7", + "0324C87B62E0EA24C7F619DDED9EDCB8A42568EBC07FF4AD21A578C8DF3615365C" + ] + }, + "recoveryData": "000000030251F6B0ED5F9AF13FBB1A2EB117C1DC50EA0CEEA17A3C97C4B9BC88D14C8D59F903D2BAB55842CB855189CF690BA0E2C588AEFCD985F6E6B60C3CFCBDBF42FC68F702D17C1ACB4697C8032830CFA0099BE475C8DBDC63E2CE294795C8AAAF69EF908703AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F303AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3026C6EBBC55EDC86ABA62BA55A3375A940E731228F214937BF8A2FC58B29AE8F080334032916F989086FB5C62722C096F0EC6A85808DB2BFA270227A72245BD7AC1B02522C52C88A6B0B1E9CF8943960E87722C1D5307069723C25B151939A58B08C10161A5552E5C1C256BA8489D1D621337C132BBECEF9840E3606582F918031F3E662B050C8F49D54C73A0F4C4D0B06DA93B81DC5463511E05913D9F2E0B5DB67D39B53A4AA164B45269B41B8F06E4254C47A55B7132E0310C626CE46DB6ABCB797BFD15AA9C0A086B4BC83CC1FBE2864519E95360101E038B56CC6F58A13BDCBCDA5310629AD258C8FEC60DEB8ED49A2749E2EDD2204257F41713CF3181FB4F89B0017D390C1E89DA9D738F5933E1A35B70C10DED152768C9FAB8593190E7417754AB67C0D15E873A2594A7EA490CC0EF1643A153A5B7467232FAE413CC8A8D8E7E27651C1E2907EDFA9CA3310332C9BE64A22B78FF873C307632E855E9E24E86C6F9E10F7D135A06F5B6839DFAA2F78973DAD89179131016D55D4E7B7FF2837F5" + }, + "comment": "valid coordinator finalize" + } + ], + "errorTestCases": [ + { + "tcId": 12, + "pmsg2Indices": [ + 0, + 3, + 2 + ], + "expectedError": { + "type": "ValueError" + }, + "comment": "participant with id 1 sent a short signature" + }, + { + "tcId": 13, + "pmsg2Indices": [ + 0, + 1 + ], + "expectedError": { + "type": "ValueError" + }, + "comment": "invalid pmsgs2: fewer entries than participants" + }, + { + "tcId": 14, + "pmsg2Indices": [ + 0, + 1, + 2, + 0 + ], + "expectedError": { + "type": "ValueError" + }, + "comment": "invalid pmsgs2: more entries than participants" + }, + { + "tcId": 15, + "pmsg2Indices": [ + 0, + 4, + 2 + ], + "expectedError": { + "type": "FaultyParticipantError", + "message": "Participant has provided an invalid signature for the certificate", + "participantId": 1 + }, + "comment": "participant with id 1 sent an invalid signature" + } + ] + }, + { + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3", + "02A480743556B0DC66F3F25262A0120AC231DEB6936D4BF8FA37ECA83C395DC8A8" + ], + "t": 2 + }, + "pmsgs1": [ + "037ACDA3FDC4E9098EA3364B92CD6DD02654141995CFD3500E1C33D098F618458D024F105837D6A403410B38453D712F306673B4B4DAC9F38C8FCF92A2D7BBDB2CE2082BB8A48B5B6088DC14C4E231236B929B68DBDA2F43842D8CCD7812812EF26C6490104A19A7CF603530A4A96E5F9A52E58A22C61B2B5B93C453BB11509CEF460266D2D26EAA94E40ECCF9C6BB24C1DDE27957FE8A82F2389EA6BBFD4B265ACAD5C2BC4B8326FF7015CEC59F160D74C4911E39F1C703FE64ACC5D0258A814EEAE235412ABEFA95191200CDA1F3D9F14976F2401C387A1B5FEDCEE9AE829D8DB005B6584DD5D478D2B1CEE49B2B8FB20EFCDC2BB9DD7B7F75B13ACBF819369AC43D4D8A4D5D1E3E00CB20D4CEFB1FD632C252F5D8048F295A5C5BD62B83145D9135", + "034D3BEF06BFB2780C527B2A16CC2819160C8569EB94AF89F16B4F81C604E09417033E8D05176C0F89D48D8FFA1F6E218B129B0DB5D47572443FB7AB8574F7B09C4662DADAF9B9E9DC0E28322D2FB7F459D3F76D8739CE5E48667B9831813B90B9EB4F0825157039DE1B1C0C9DB2F52C4F51E112A113E2F436FF61CF94254B3E2A26036DEF7297E97F0D229BBB7B004AABEC90F7E4A769934376ED220FD78D79C3095CBE24CC80C556589A0B8E155C7DA2429FD3BE69817C8E237B911EDB37605569C237D0D39F4904C0294A6F38AA1ED43C3769BD1DC4372201CEC2A36D328F613A85CDE8E4E2F05D5A97365681A84FC3074416AFF8C91FDEF197C73047E0D3B43C76FDCCC6A8DC55BA01C9EF8EB155ADE1051E5EA74FDC1B150FEA588BBD377C3565", + "0246EC025D2E096D23F52B755D1DED61C58608B23C67087E90555F52C428AFA61B0330B5D6D8F21A3E1B47E225C6B837C6D5E74B7DD52B67482C6D3D386A5C19369B0BCEDABBF632561F24A4BECEBE1F22F9260C830E17EFB80D88EA5919C98871B78AA91A74C8F2B20F83FEB22B9599E9B416D49706181998EC04E3FDF0AAEB789F0251B5F3A899402AAE586113B18A5B2D1F12A49330AEDD5AC2314FF07EB151A9B28BB736697045FBC5CC366FA156F25C21760942BE0ED2FE35C8AE3B24C70E8392E71F4DD29880FB7C913D3BC68F1A488EE93FBF92D99639C927F56330C180DCEB705221AADCA90841FF0FD21EAE2C72D95DBB6867CF7AD1441BEF0F7264E25CC6A8A3503F93E535C36E3DED76843ECFDFD3A1246B7CC8C806A1F6E86C5D05DD95", + "0244EEC31E9105EB71FDDD1F777889003AD61D6C3116EAACF48B23F885AA617F130259C4EEE9CA0E391339ABE9010023AFA0A50FD403CBDDDCDEB801F406DE707B819777334881108C5C19F548963F3BF8F5E693969A30B2F749498262D8AC956E0D91EB42FB355114D3F0589AFBF10289A015EF05D1DF365BD1A0468582C0706760024461A885A565BD85A57B3047CBEE3D70C7E9C400C5FB2C0146F02944AB36C42E0A7D6A1780AB7000EC43EAD753D49C5A5DD08236F37B43ED82D40C97D03E8B4823339CBF4313CD658B22977779325944D49B9E928138E470B4E90C34F4D761778EB690F7F7EC769034FE7FFFB06D4EBFBABA9E4D442925D4BE8046D0BEACD19D4DE6699AA11B5C59E23743B81327C68D6E73B5770B834FACEC47E5CF3B5FACE4" + ], + "cmsg1": "037ACDA3FDC4E9098EA3364B92CD6DD02654141995CFD3500E1C33D098F618458D034D3BEF06BFB2780C527B2A16CC2819160C8569EB94AF89F16B4F81C604E094170246EC025D2E096D23F52B755D1DED61C58608B23C67087E90555F52C428AFA61B0244EEC31E9105EB71FDDD1F777889003AD61D6C3116EAACF48B23F885AA617F13037B0F4758018C2AC0DC96FF1A6F6DB042B302717D1911B65B4419CAE29D79EBD9082BB8A48B5B6088DC14C4E231236B929B68DBDA2F43842D8CCD7812812EF26C6490104A19A7CF603530A4A96E5F9A52E58A22C61B2B5B93C453BB11509CEF4662DADAF9B9E9DC0E28322D2FB7F459D3F76D8739CE5E48667B9831813B90B9EB4F0825157039DE1B1C0C9DB2F52C4F51E112A113E2F436FF61CF94254B3E2A260BCEDABBF632561F24A4BECEBE1F22F9260C830E17EFB80D88EA5919C98871B78AA91A74C8F2B20F83FEB22B9599E9B416D49706181998EC04E3FDF0AAEB789F9777334881108C5C19F548963F3BF8F5E693969A30B2F749498262D8AC956E0D91EB42FB355114D3F0589AFBF10289A015EF05D1DF365BD1A0468582C07067600266D2D26EAA94E40ECCF9C6BB24C1DDE27957FE8A82F2389EA6BBFD4B265ACAD5036DEF7297E97F0D229BBB7B004AABEC90F7E4A769934376ED220FD78D79C3095C0251B5F3A899402AAE586113B18A5B2D1F12A49330AEDD5AC2314FF07EB151A9B2024461A885A565BD85A57B3047CBEE3D70C7E9C400C5FB2C0146F02944AB36C42E1715B884DD47347692CE0EEB35DDFFAF50746670244989D422CC8B64D884E0FC7764E8F01F2EA21D679CADDC011227835F29BB3B5CC3DFBAAE992C8E1310E7AB8349E55B996BAC1B39496EF23E0ED7DC95F3FF8E50711DEA5CC6D9238D71AC9441E0CDE02F944CEA3B398EDB0CEAAA373E0B9F6994FF46A854C8C86243D2CE91", + "pmsg2Pool": [ + "C40687CB309D263B5D52C4D782F9A66411B57627FD56FD0ABE3C87CB051CE248E19A95E68212599B8DAD04ADF7CE37DC809BBC04F27B019D5543F35223FC53E6", + "978415455495D12CF1AC203BC4E87CC6628B0C582EDAB40DD8C9137AC8DF637BD8D2F1FA2930AEFC25B33179986E54644F3E7C2401327E5EA10394B93B2933DF", + "8F0607BCEB003FF44C179B0C731904607CDBB40E2CD31A3BAB2D7ABCE592DB9D61CABF2104E3E3B3E0EB66104DC56ADE39741C844E03966E0C9CCB629DA1EE48", + "5AD6B8D354A6D89F067A859AB1941E403E8C72413D61E11EC49588C977831CCB37224C44A482EC53A2373F5BD2B38041797C77C85CC14AB25E4F6226FFA6CAA5", + "978415455495D12CF1AC203BC4E87CC6628B0C582EDAB40DD8C9137AC8DF637BD8D2F1FA2930AEFC25B33179986E54644F3E7C2401327E5EA10394B93B2933", + "09C289578B96E6283AB13E4741FB489FC147FB1A5F446A314BA73C052131EFB04B83247A0BCEDF5205202AD64188B24B0BC5B51A17AEB218BD98DBE000C843B9" + ], + "validTestCases": [ + { + "tcId": 16, + "pmsg2Indices": [ + 0, + 1, + 2, + 3 + ], + "expectedOutput": { + "cmsg2": "C40687CB309D263B5D52C4D782F9A66411B57627FD56FD0ABE3C87CB051CE248E19A95E68212599B8DAD04ADF7CE37DC809BBC04F27B019D5543F35223FC53E6978415455495D12CF1AC203BC4E87CC6628B0C582EDAB40DD8C9137AC8DF637BD8D2F1FA2930AEFC25B33179986E54644F3E7C2401327E5EA10394B93B2933DF8F0607BCEB003FF44C179B0C731904607CDBB40E2CD31A3BAB2D7ABCE592DB9D61CABF2104E3E3B3E0EB66104DC56ADE39741C844E03966E0C9CCB629DA1EE485AD6B8D354A6D89F067A859AB1941E403E8C72413D61E11EC49588C977831CCB37224C44A482EC53A2373F5BD2B38041797C77C85CC14AB25E4F6226FFA6CAA5", + "dkgOutput": { + "secshare": null, + "threshPk": "029D2139223BE442F3ED257971596919143AC6A21737C3742CB17C962C4DEBE73D", + "pubshares": [ + "02C5623B00B3CFCED42F7AFD45E43A34E06075B97534563934D1755FDBA8D073E1", + "0272117A2B890D887E3419E0465424EE71B647BF35A84F26D8B40427A5D4A97B43", + "03F4468332D5B10B2CEB460DE83199A8A3E5E0340CEAFAA713896DFA11521DCB6E", + "02C35519941E85AE04FD42AA98D03D4F38AFD7C5E7350B50553DD14E22CE259B68" + ] + }, + "recoveryData": "0000000202C0D0DC96E9C331610E67BD7C8C71A978477E34DAB3BE929694022C997D19338B037B0F4758018C2AC0DC96FF1A6F6DB042B302717D1911B65B4419CAE29D79EBD903AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F303AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A302A480743556B0DC66F3F25262A0120AC231DEB6936D4BF8FA37ECA83C395DC8A80266D2D26EAA94E40ECCF9C6BB24C1DDE27957FE8A82F2389EA6BBFD4B265ACAD5036DEF7297E97F0D229BBB7B004AABEC90F7E4A769934376ED220FD78D79C3095C0251B5F3A899402AAE586113B18A5B2D1F12A49330AEDD5AC2314FF07EB151A9B2024461A885A565BD85A57B3047CBEE3D70C7E9C400C5FB2C0146F02944AB36C42E1715B884DD47347692CE0EEB35DDFFAF50746670244989D422CC8B64D884E0FC7764E8F01F2EA21D679CADDC011227835F29BB3B5CC3DFBAAE992C8E1310E7AB8349E55B996BAC1B39496EF23E0ED7DC95F3FF8E50711DEA5CC6D9238D71AC9441E0CDE02F944CEA3B398EDB0CEAAA373E0B9F6994FF46A854C8C86243D2CE91C40687CB309D263B5D52C4D782F9A66411B57627FD56FD0ABE3C87CB051CE248E19A95E68212599B8DAD04ADF7CE37DC809BBC04F27B019D5543F35223FC53E6978415455495D12CF1AC203BC4E87CC6628B0C582EDAB40DD8C9137AC8DF637BD8D2F1FA2930AEFC25B33179986E54644F3E7C2401327E5EA10394B93B2933DF8F0607BCEB003FF44C179B0C731904607CDBB40E2CD31A3BAB2D7ABCE592DB9D61CABF2104E3E3B3E0EB66104DC56ADE39741C844E03966E0C9CCB629DA1EE485AD6B8D354A6D89F067A859AB1941E403E8C72413D61E11EC49588C977831CCB37224C44A482EC53A2373F5BD2B38041797C77C85CC14AB25E4F6226FFA6CAA5" + }, + "comment": "valid coordinator finalize" + } + ], + "errorTestCases": [ + { + "tcId": 17, + "pmsg2Indices": [ + 0, + 4, + 2, + 3 + ], + "expectedError": { + "type": "ValueError" + }, + "comment": "participant with id 1 sent a short signature" + }, + { + "tcId": 18, + "pmsg2Indices": [ + 0, + 1, + 2 + ], + "expectedError": { + "type": "ValueError" + }, + "comment": "invalid pmsgs2: fewer entries than participants" + }, + { + "tcId": 19, + "pmsg2Indices": [ + 0, + 1, + 2, + 3, + 0 + ], + "expectedError": { + "type": "ValueError" + }, + "comment": "invalid pmsgs2: more entries than participants" + }, + { + "tcId": 20, + "pmsg2Indices": [ + 0, + 5, + 2, + 3 + ], + "expectedError": { + "type": "FaultyParticipantError", + "message": "Participant has provided an invalid signature for the certificate", + "participantId": 1 + }, + "comment": "participant with id 1 sent an invalid signature" + } + ] + } + ] +} \ No newline at end of file diff --git a/bip-chilldkg/vectors/coordinator_investigate_vectors.json b/bip-chilldkg/vectors/coordinator_investigate_vectors.json new file mode 100644 index 0000000000..6e7b9c3e18 --- /dev/null +++ b/bip-chilldkg/vectors/coordinator_investigate_vectors.json @@ -0,0 +1,125 @@ +{ + "description": [ + "Test vectors for coordinator_investigate(pmsgs1, params).", + "Generates investigation messages to help participants identify faulty parties.", + "Called when a participant reports UnknownFaultyParticipantOrCoordinatorError.", + "", + "For each valid test case:", + " Call coordinator_investigate(pmsgs1, params).", + " Verify the returned list of investigation messages equals expectedCinvMsgs." + ], + "totalTests": 4, + "testGroups": [ + { + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3" + ], + "t": 2 + }, + "pmsgs1": [ + "036BEE122EF60CBBB5DFC3572FE6D3A9AC0B35BB427E250F42C38F7C8229889CAB03E99E34AA2A0646563DF90F2407D0D0E345EAE2CF7F07F9F6561302F724EFA74708AE5A94BBDD3FC53924623E0D6764E089D679048F699118D7807884AD13CF484866D8210BCB76DAEB103AD6BA1CED0DBFE78F1E8A44F26FC69D5464BF23324D0260537C74BC2B79676CDE217FFA7D48236D4A309954153DEB39A31AADF6B369CCC6C144287B356EF70378A7F1054FF00354A01352587DC076554A82CD324B66A17006774A86C116C19F2C021793EAB87DF19EE3A978C60F757BA192DA23BAE5F56A3612D0720F35828FCFBA9DFEA790883B185A8B75A27449AF23169C919B47C7", + "037C42E200B601CD439CDEAA12FB2BD8949C566E59096AA9207165C6FF0602B1D6020584BF7D8C710F6A4988C6B16848C4BBA45A819107B2C49190E2D6FB666011AE53EF7BE0C965DBBA2418E1FAA16FB9CA5FC990337E0E1858F263A8B88AE081D053CB4BCA7D90B63225ABA7C40F4B86B1D2E91D0EEFC52657B70695BE96FEEF37022E7B7DD7A0B4A72097703E60648B615A4DDDF2D715A5E0992379537A44C13AD17A5C832E385A79C54BB5E400CF3FA2DF8A747E7F362AA3345CB0097DAF2C715FAA97DCA9CF742C4A54D5805B0705378C68129809D8D22A753A3F8F92FE61D7D14D27B6E1FE9BCEE26C96CD0B2CB18AE6A5D80C5792EA1FA2F09011B3F2596EE7", + "026F714517277FB154C47E487FBE415C25C0D0F79466F1479B31A8D78319978D8A0203E26DE13A90DC4465BF155DCE35DFED355FE1902F39E9B4139F038F12C04DDF68EFCB547A2ED10F9D35C10E8FFE3A5A7F049FF7FFBF5F7400586830FBAC2B6908C2742DE20520065E89CF8CF7DBD279063967C0E1E77A97297BFDDF28B58C1A03D1FD5A186167E5DE84B11C0F56E1FC6808D2DB35E6E3587243CF59DE605CB806A124DC237A739AD28D8624726DD0277B3B1D7D030019F0FBBA9EE12D6007FEE360BC33A121886C897AC86F4F99CD493743B05216D0408C88A32D90A109837B6547CAC26451623B282A4FC67A2E454E27C7437BA3F620C687F061E9B264652DD6" + ], + "validTestCases": [ + { + "tcId": 1, + "expectedCinvMsgs": [ + "C6C144287B356EF70378A7F1054FF00354A01352587DC076554A82CD324B66A17A5C832E385A79C54BB5E400CF3FA2DF8A747E7F362AA3345CB0097DAF2C715FA124DC237A739AD28D8624726DD0277B3B1D7D030019F0FBBA9EE12D6007FEE3036D44D81C8FC25D67A6E1E125246D1FF9BE76A1658F97C07CE39940D0B43E52E303BF0AF5C64B79B08F41BCFABC9FCECC5ADDA3F12CFA6AA1154E02DA1647FE8B0702092454A24C75464D046869DCD67E790172FB1F12A149E50DAC5E6C4D8BFAAE2F", + "7006774A86C116C19F2C021793EAB87DF19EE3A978C60F757BA192DA23BAE5F5AA97DCA9CF742C4A54D5805B0705378C68129809D8D22A753A3F8F92FE61D7D160BC33A121886C897AC86F4F99CD493743B05216D0408C88A32D90A109837B6502EA90FA9C43676FF0CDA703288937981FB16BF7110FF7D933D8B3D5E0786634E102142133ED95D812D7EB519931786DEBBDB80AE1BD30E2249C479EFCDDB31F716802648B7305132F7274A68D258C1EDF07E5EA8FF6EB0EB06498FA7EC760B810789B", + "6A3612D0720F35828FCFBA9DFEA790883B185A8B75A27449AF23169C919B47C74D27B6E1FE9BCEE26C96CD0B2CB18AE6A5D80C5792EA1FA2F09011B3F2596EE747CAC26451623B282A4FC67A2E454E27C7437BA3F620C687F061E9B264652DD602C9E43C8D052AFB93A707067131A402553779F074A972E4AEE18E75AC1974BFB9021D05E0EB5FEC76D50949DC78D002E6F3C785BABE7D0942D058D0F83D6C3646F203E3E0604C66D5348257F16C8B1892F4A105218F452C25BE0DF746D069F2B9F28C" + ], + "comment": "valid coordinator investigate" + } + ], + "errorTestCases": [] + }, + { + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3" + ], + "t": 1 + }, + "pmsgs1": [ + "0216C95A7FA9079C73184E8646721F59BB0361A839FD6DB9B8CF53CA1EA5B9E1C6558B3C0BFEA2180B7449D7EF101A57D8CF9FC0E32D77FAFDC712A3897007419D6CE9F8FEA08EC9935120955BDFD2519F7B29AD3E0248DA19A0E7D3900FE14CEA02BC88DD206C8AD8C1B52F93AD147695802206D0CA6AB756BD51E5FF5BE858FD219827C7BD6ECCE374663262B90B8D2944C94F9AB1E83F38DBD12823DD7D58B8B3CF1C0AB792C1AF61FA3866CCC4221CAC3CB9D35F7F5358990AB02D7E246BCEDD2AA0622F84004D5AED9B4FC855F20AE22CE260B6E79F56DE5BC2F5B73D137DE3", + "02DA601E3FC8D21A247C5677D9CACFFE0BB86860BC1C233BECA0D97776AAE309DA832AE406B679DEE20F859D361B3C2A1A9EEED646F1B5B6761D1370889D2C0F4EFFFFB7792DD5B2EF16A87A3868280F745D4FAA4EB032695FF0E9285DB7155B8303F216E714F96E6AAE00E3FAAF5188FF6F66C71B932E10B17AA8403B17DE9A8F52F4CDA3696B4B7701D9CA898B23E0211D9A3BB64CD130C329BDFC826BCBFCA13D563259BE0136A48FE852FC559B1EA6DE9EB24A43FBE2B42E3F1AE85AB03BB99B54825042D24EFCD6B3C4E2F29DFA11B55973C08DEADB120BA5B90EF12D74353B", + "02FB246B4866569A7A495CFFB1133DAF9096FE4FA22EDA6AF9D7A8F236F9C222B68A8C833B35FF0445F94744CBDE8FFC29CBBBFD3B7BA7F3B00A56E96EFA24F6C582EA017BA6A173AA31E4E9F2F5F74D1C4ABA2BBA5B9FACE5F3042B5B2323AB81038AA8962D7EBBF847CE551B1EF8D0D5E8A051B7FD6A29127709F35A6C18CED300EE7CAEA466336EF62D77586E88F1689219851C1473C34AA555FB08E2A7BD90977A1AB2888E05E016EFE2799D43300A3A576497D23F32CFBABE18A069512E2A96826AFB726FF50193F6F50E4CA0A630DEC81B68554EEFDA5331C04E7E7FFE7C1A" + ], + "validTestCases": [ + { + "tcId": 2, + "expectedCinvMsgs": [ + "9827C7BD6ECCE374663262B90B8D2944C94F9AB1E83F38DBD12823DD7D58B8B3F4CDA3696B4B7701D9CA898B23E0211D9A3BB64CD130C329BDFC826BCBFCA13DEE7CAEA466336EF62D77586E88F1689219851C1473C34AA555FB08E2A7BD90970216C95A7FA9079C73184E8646721F59BB0361A839FD6DB9B8CF53CA1EA5B9E1C602DA601E3FC8D21A247C5677D9CACFFE0BB86860BC1C233BECA0D97776AAE309DA02FB246B4866569A7A495CFFB1133DAF9096FE4FA22EDA6AF9D7A8F236F9C222B6", + "CF1C0AB792C1AF61FA3866CCC4221CAC3CB9D35F7F5358990AB02D7E246BCEDD563259BE0136A48FE852FC559B1EA6DE9EB24A43FBE2B42E3F1AE85AB03BB99B7A1AB2888E05E016EFE2799D43300A3A576497D23F32CFBABE18A069512E2A960216C95A7FA9079C73184E8646721F59BB0361A839FD6DB9B8CF53CA1EA5B9E1C602DA601E3FC8D21A247C5677D9CACFFE0BB86860BC1C233BECA0D97776AAE309DA02FB246B4866569A7A495CFFB1133DAF9096FE4FA22EDA6AF9D7A8F236F9C222B6", + "2AA0622F84004D5AED9B4FC855F20AE22CE260B6E79F56DE5BC2F5B73D137DE354825042D24EFCD6B3C4E2F29DFA11B55973C08DEADB120BA5B90EF12D74353B826AFB726FF50193F6F50E4CA0A630DEC81B68554EEFDA5331C04E7E7FFE7C1A0216C95A7FA9079C73184E8646721F59BB0361A839FD6DB9B8CF53CA1EA5B9E1C602DA601E3FC8D21A247C5677D9CACFFE0BB86860BC1C233BECA0D97776AAE309DA02FB246B4866569A7A495CFFB1133DAF9096FE4FA22EDA6AF9D7A8F236F9C222B6" + ], + "comment": "valid coordinator investigate" + } + ], + "errorTestCases": [] + }, + { + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3" + ], + "t": 3 + }, + "pmsgs1": [ + "02F659A9668C1E7FB2BF4B457CA91C9925B3AB9F3F029C489C53603EF6AF90199B03BB0D7426739F2FAB24C6A0B2693273BBC92E03D3CB1A252956D372E4484F3EFE030C43B612EC5E536EF7853FCC8744BF77AF272485349484BDAA6FB6F2222FDD5A92AE219E3FE6B306490A7510AF6D9C981983DE7E4A83127F350451986EB2AF829A8C8BF483ADD195B5C7B08073D9548FFCDFB5626F0DA6A26468AD9BE54D5C83026C6EBBC55EDC86ABA62BA55A3375A940E731228F214937BF8A2FC58B29AE8F087C47A6836F30317C4B914E33F10B72CA6EDD116A3DF9E8BFE997045F9F957D31A43BB05F6162602B96E5D8C0AE3A07C8228E41DE772B0ACD96937B6CD0115ADE0ACB3150F08EA8668C262DC091C6ACA77674E8C749023FA6FB5D6938BEC73CD8", + "034619FE6A2FF09FE393CACB98B0708545255926B9CB33103567AAF2EE4D23B05403D16A8AFDD68449C6C00FF346EC9D29EC8D3D681F3B2EC04616F0A75693940C2402BF150900B4B78A40CF51C1429F0C796B7EC6A4B9A6337B346895DAD6E7F4108D18AB016E948B6EE152EEC9B6AD07ADC102177C6060C5FA6AB774DE136D59657C62D596F9C9477FF09A14654D48AC94B9CF381221ED27DC26B0F224C70039771F0334032916F989086FB5C62722C096F0EC6A85808DB2BFA270227A72245BD7AC1BB9BC513A6059135E04745D8BA961BA2B73A8F109469632CACD2DEA19DD9F985D03F3FCD86C88731C94E5588CA37E072DBF0A968AF541994B5A5D3E6011C9D92660F6A85166FBC97DE076DD7F81CB81CF07262D7296453D47065FD31A952CA8C9", + "0250A0832ED8FECB459561A2BFE1103A29CD4B23D53B27B707376FE0E67562F200020F17B8706ED73D63DEA8F15898E59E062E761C8E04DC3BDD78092C006A73440F022DF27B11574AE811C767A2E2A8645E9D63910CFD74F52F65488A5A64133EFBDAACBC3B4D52DD7A762F752250E41218F5295C3DDFCFF43350759D74A62B695352CE3C302E8718BA21C85BED769AEF3846A88E691AE945D6EBA09F51ACB0143D1002522C52C88A6B0B1E9CF8943960E87722C1D5307069723C25B151939A58B08C10E0165D9516387D7C6A7EDE123BB40683A6037628D3853322CF37FE31A36960DABA80A39126B2817F0E441AFFB94ECB9C9133C9C377EDDC7BE2BB97A0A43675102F91CB07BEC0D3422EA4ADB05AB0264DFCBAA0D94EBB93D825110A8816C8D1F6" + ], + "validTestCases": [ + { + "tcId": 3, + "expectedCinvMsgs": [ + "7C47A6836F30317C4B914E33F10B72CA6EDD116A3DF9E8BFE997045F9F957D31B9BC513A6059135E04745D8BA961BA2B73A8F109469632CACD2DEA19DD9F985DE0165D9516387D7C6A7EDE123BB40683A6037628D3853322CF37FE31A36960DA02EB2C6840DF67A868BD870B7FB28C6166EE7175F26E1709EE6055C30BEDBED4440211DAB363110221D0DDC7A94DC8C125FADE58F7183C5C24C606572914B327D6E503FE94E345B4190A99EB1B6EE05449AE2F638456DFB603C74083387CB8F3ADCB12", + "A43BB05F6162602B96E5D8C0AE3A07C8228E41DE772B0ACD96937B6CD0115ADE03F3FCD86C88731C94E5588CA37E072DBF0A968AF541994B5A5D3E6011C9D926BA80A39126B2817F0E441AFFB94ECB9C9133C9C377EDDC7BE2BB97A0A436751003A7ECF826CB9AABC89EC5613CB2619AA6F820483A3C5E78CC8CB5012DE5D00C37022195F03AD5C5EDA4F066F2AE3C478B4A154DD771DE009DFBCDF753507C7A925F03886A9FF796EEFD88B956B0D5BA0F7C50E3591219D4D38F7513D737A6E5E07B48", + "0ACB3150F08EA8668C262DC091C6ACA77674E8C749023FA6FB5D6938BEC73CD860F6A85166FBC97DE076DD7F81CB81CF07262D7296453D47065FD31A952CA8C92F91CB07BEC0D3422EA4ADB05AB0264DFCBAA0D94EBB93D825110A8816C8D1F602B6AAA356721893AD6D81FE281705E1642A1CD24AE790A58EB6B5CFDBC8AB2B6502740EDFAFC9F5A891E9B7333404B41C6DC047D43EB648DA5F89DBB3E7E9EE6B7F03E4D4EF71978155829EBB6594F073020C4D88CA2F556930054415C47D254952C7" + ], + "comment": "valid coordinator investigate" + } + ], + "errorTestCases": [] + }, + { + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3", + "02A480743556B0DC66F3F25262A0120AC231DEB6936D4BF8FA37ECA83C395DC8A8" + ], + "t": 2 + }, + "pmsgs1": [ + "037ACDA3FDC4E9098EA3364B92CD6DD02654141995CFD3500E1C33D098F618458D024F105837D6A403410B38453D712F306673B4B4DAC9F38C8FCF92A2D7BBDB2CE2082BB8A48B5B6088DC14C4E231236B929B68DBDA2F43842D8CCD7812812EF26C6490104A19A7CF603530A4A96E5F9A52E58A22C61B2B5B93C453BB11509CEF460266D2D26EAA94E40ECCF9C6BB24C1DDE27957FE8A82F2389EA6BBFD4B265ACAD5C2BC4B8326FF7015CEC59F160D74C4911E39F1C703FE64ACC5D0258A814EEAE235412ABEFA95191200CDA1F3D9F14976F2401C387A1B5FEDCEE9AE829D8DB005B6584DD5D478D2B1CEE49B2B8FB20EFCDC2BB9DD7B7F75B13ACBF819369AC43D4D8A4D5D1E3E00CB20D4CEFB1FD632C252F5D8048F295A5C5BD62B83145D9135", + "034D3BEF06BFB2780C527B2A16CC2819160C8569EB94AF89F16B4F81C604E09417033E8D05176C0F89D48D8FFA1F6E218B129B0DB5D47572443FB7AB8574F7B09C4662DADAF9B9E9DC0E28322D2FB7F459D3F76D8739CE5E48667B9831813B90B9EB4F0825157039DE1B1C0C9DB2F52C4F51E112A113E2F436FF61CF94254B3E2A26036DEF7297E97F0D229BBB7B004AABEC90F7E4A769934376ED220FD78D79C3095CBE24CC80C556589A0B8E155C7DA2429FD3BE69817C8E237B911EDB37605569C237D0D39F4904C0294A6F38AA1ED43C3769BD1DC4372201CEC2A36D328F613A85CDE8E4E2F05D5A97365681A84FC3074416AFF8C91FDEF197C73047E0D3B43C76FDCCC6A8DC55BA01C9EF8EB155ADE1051E5EA74FDC1B150FEA588BBD377C3565", + "0246EC025D2E096D23F52B755D1DED61C58608B23C67087E90555F52C428AFA61B0330B5D6D8F21A3E1B47E225C6B837C6D5E74B7DD52B67482C6D3D386A5C19369B0BCEDABBF632561F24A4BECEBE1F22F9260C830E17EFB80D88EA5919C98871B78AA91A74C8F2B20F83FEB22B9599E9B416D49706181998EC04E3FDF0AAEB789F0251B5F3A899402AAE586113B18A5B2D1F12A49330AEDD5AC2314FF07EB151A9B28BB736697045FBC5CC366FA156F25C21760942BE0ED2FE35C8AE3B24C70E8392E71F4DD29880FB7C913D3BC68F1A488EE93FBF92D99639C927F56330C180DCEB705221AADCA90841FF0FD21EAE2C72D95DBB6867CF7AD1441BEF0F7264E25CC6A8A3503F93E535C36E3DED76843ECFDFD3A1246B7CC8C806A1F6E86C5D05DD95", + "0244EEC31E9105EB71FDDD1F777889003AD61D6C3116EAACF48B23F885AA617F130259C4EEE9CA0E391339ABE9010023AFA0A50FD403CBDDDCDEB801F406DE707B819777334881108C5C19F548963F3BF8F5E693969A30B2F749498262D8AC956E0D91EB42FB355114D3F0589AFBF10289A015EF05D1DF365BD1A0468582C0706760024461A885A565BD85A57B3047CBEE3D70C7E9C400C5FB2C0146F02944AB36C42E0A7D6A1780AB7000EC43EAD753D49C5A5DD08236F37B43ED82D40C97D03E8B4823339CBF4313CD658B22977779325944D49B9E928138E470B4E90C34F4D761778EB690F7F7EC769034FE7FFFB06D4EBFBABA9E4D442925D4BE8046D0BEACD19D4DE6699AA11B5C59E23743B81327C68D6E73B5770B834FACEC47E5CF3B5FACE4" + ], + "validTestCases": [ + { + "tcId": 4, + "expectedCinvMsgs": [ + "C2BC4B8326FF7015CEC59F160D74C4911E39F1C703FE64ACC5D0258A814EEAE2BE24CC80C556589A0B8E155C7DA2429FD3BE69817C8E237B911EDB37605569C28BB736697045FBC5CC366FA156F25C21760942BE0ED2FE35C8AE3B24C70E83920A7D6A1780AB7000EC43EAD753D49C5A5DD08236F37B43ED82D40C97D03E8B4803AE9A37E7CABAFAFA2B0C0E1C758F754208966D0EE3AF2817E09A4BCD52E6D96302ECB1B982603A8BA43EA6D9C8E070EB917BBD40B932F28B6B8BF84B81CD2C2241039CF320073B4B23D5F155924FC598AC82C74527C8CFB64B7459A876C8F4643D100380A91AEAD1DF43C3BDE89CFEC504708560DC53DFE7BD736CBB3EA9D06654E7AB", + "35412ABEFA95191200CDA1F3D9F14976F2401C387A1B5FEDCEE9AE829D8DB00537D0D39F4904C0294A6F38AA1ED43C3769BD1DC4372201CEC2A36D328F613A85E71F4DD29880FB7C913D3BC68F1A488EE93FBF92D99639C927F56330C180DCEB23339CBF4313CD658B22977779325944D49B9E928138E470B4E90C34F4D7617703589D80C93930ED933D3C3F62676567D24EB423E6C15A8E9147570EB4DEEEA408022B77F452E0AA889BBD72755D850C2636D748F28A877F9C6467559D97FC75B28F02E563214350B4D8354426CF9D73BDA8BA94CD80A827DC84C5CFE5930CB10BEA1703D506F406064E59BD0232DAED1D0865E71F119EFFD212A820022F913279655DD1", + "B6584DD5D478D2B1CEE49B2B8FB20EFCDC2BB9DD7B7F75B13ACBF819369AC43DCDE8E4E2F05D5A97365681A84FC3074416AFF8C91FDEF197C73047E0D3B43C76705221AADCA90841FF0FD21EAE2C72D95DBB6867CF7AD1441BEF0F7264E25CC68EB690F7F7EC769034FE7FFFB06D4EBFBABA9E4D442925D4BE8046D0BEACD19D0271D93FFD68D893C051F332D74E8C0DDF2F61A3CC75051D57B55DAB056E329DFD03BA71BA7CBBC1C29077049846C470CC3C9DD47CECA8CFFD2CB621EC87BB51AD98034123402967C7F8511987D603A1FE319A723DED8D6766F957CB1843BBDFE3726002E413326B9B5985F71D856949A2C478FFCD27B0E2FEFA4F1BDC9B442067858132", + "4D8A4D5D1E3E00CB20D4CEFB1FD632C252F5D8048F295A5C5BD62B83145D9135FDCCC6A8DC55BA01C9EF8EB155ADE1051E5EA74FDC1B150FEA588BBD377C3565A8A3503F93E535C36E3DED76843ECFDFD3A1246B7CC8C806A1F6E86C5D05DD954DE6699AA11B5C59E23743B81327C68D6E73B5770B834FACEC47E5CF3B5FACE40302CECC942977DF6AED8F54EC9A9382F97B1BD15A9A7320CF86C9451A416D05920309C412A395AD3C3C5F03A49672DCFDBAA88752943CE516183D8DA2F23B68853402EE8F4A3CC0E6516D5CAA35A9441F173CE114070A4D0BFA9CC51D536446D519400322AD82877DAEE5C16B4AC0CF4FDCA15FE91F78CCA97FA996CBF7CA6AF68946F9" + ], + "comment": "valid coordinator investigate" + } + ], + "errorTestCases": [] + } + ] +} \ No newline at end of file diff --git a/bip-chilldkg/vectors/coordinator_step1_vectors.json b/bip-chilldkg/vectors/coordinator_step1_vectors.json new file mode 100644 index 0000000000..3ed672a37a --- /dev/null +++ b/bip-chilldkg/vectors/coordinator_step1_vectors.json @@ -0,0 +1,989 @@ +{ + "description": [ + "Test vectors for coordinator_step1(pmsgs1, params).", + "Aggregates participant round-1 messages and produces the coordinator's broadcast message (cmsg1).", + "", + "Assemble the pmsgs1 list from pmsg1Pool using pmsg1Indices:", + " pmsgs1 = [pmsg1Pool[i] for i in pmsg1Indices]", + " Pool entries at indices 0..n-1 are well-formed messages; higher indices may be malformed.", + "", + "For each valid test case:", + " Call coordinator_step1(pmsgs1, params).", + " Verify the returned cmsg1 equals expectedCmsg1.", + "", + "For each error test case:", + " Call coordinator_step1(pmsgs1, params).", + " Verify it raises an exception matching expectedError.", + " Error objects may include 'participantId' (identifier of the blamed participant)." + ], + "totalTests": 44, + "testGroups": [ + { + "pmsg1Pool": [ + "036BEE122EF60CBBB5DFC3572FE6D3A9AC0B35BB427E250F42C38F7C8229889CAB03E99E34AA2A0646563DF90F2407D0D0E345EAE2CF7F07F9F6561302F724EFA74708AE5A94BBDD3FC53924623E0D6764E089D679048F699118D7807884AD13CF484866D8210BCB76DAEB103AD6BA1CED0DBFE78F1E8A44F26FC69D5464BF23324D0260537C74BC2B79676CDE217FFA7D48236D4A309954153DEB39A31AADF6B369CCC6C144287B356EF70378A7F1054FF00354A01352587DC076554A82CD324B66A17006774A86C116C19F2C021793EAB87DF19EE3A978C60F757BA192DA23BAE5F56A3612D0720F35828FCFBA9DFEA790883B185A8B75A27449AF23169C919B47C7", + "037C42E200B601CD439CDEAA12FB2BD8949C566E59096AA9207165C6FF0602B1D6020584BF7D8C710F6A4988C6B16848C4BBA45A819107B2C49190E2D6FB666011AE53EF7BE0C965DBBA2418E1FAA16FB9CA5FC990337E0E1858F263A8B88AE081D053CB4BCA7D90B63225ABA7C40F4B86B1D2E91D0EEFC52657B70695BE96FEEF37022E7B7DD7A0B4A72097703E60648B615A4DDDF2D715A5E0992379537A44C13AD17A5C832E385A79C54BB5E400CF3FA2DF8A747E7F362AA3345CB0097DAF2C715FAA97DCA9CF742C4A54D5805B0705378C68129809D8D22A753A3F8F92FE61D7D14D27B6E1FE9BCEE26C96CD0B2CB18AE6A5D80C5792EA1FA2F09011B3F2596EE7", + "026F714517277FB154C47E487FBE415C25C0D0F79466F1479B31A8D78319978D8A0203E26DE13A90DC4465BF155DCE35DFED355FE1902F39E9B4139F038F12C04DDF68EFCB547A2ED10F9D35C10E8FFE3A5A7F049FF7FFBF5F7400586830FBAC2B6908C2742DE20520065E89CF8CF7DBD279063967C0E1E77A97297BFDDF28B58C1A03D1FD5A186167E5DE84B11C0F56E1FC6808D2DB35E6E3587243CF59DE605CB806A124DC237A739AD28D8624726DD0277B3B1D7D030019F0FBBA9EE12D6007FEE360BC33A121886C897AC86F4F99CD493743B05216D0408C88A32D90A109837B6547CAC26451623B282A4FC67A2E454E27C7437BA3F620C687F061E9B264652DD6", + "037C42E200B601CD439CDEAA12FB2BD8949C566E59096AA9207165C6FF0602B1D6020584BF7D8C710F6A4988C6B16848C4BBA45A819107B2C49190E2D6FB666011AE53EF7BE0C965DBBA2418E1FAA16FB9CA5FC990337E0E1858F263A8B88AE081D053CB4BCA7D90B63225ABA7C40F4B86B1D2E91D0EEFC52657B70695BE96FEEF37022E7B7DD7A0B4A72097703E60648B615A4DDDF2D715A5E0992379537A44C13AD17A5C832E385A79C54BB5E400CF3FA2DF8A747E7F362AA3345CB0097DAF2C715FAA97DCA9CF742C4A54D5805B0705378C68129809D8D22A753A3F8F92FE61D7D1", + "", + "037C42E200B601CD439CDEAA12FB2BD8949C566E59096AA9207165C6FF0602B1D6020584BF7D8C710F6A4988C6B16848C4BBA45A819107B2C49190E2D6FB666011AE53EF7BE0C965DBBA2418E1FAA16FB9CA5FC990337E0E1858F263A8B88AE081D053CB4BCA7D90B63225ABA7C40F4B86B1D2E91D0EEFC52657B70695BE96FEEF37" + ], + "validTestCases": [ + { + "tcId": 1, + "pmsg1Indices": [ + 0, + 1, + 2 + ], + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3" + ], + "t": 2 + }, + "expectedCmsg1": "036BEE122EF60CBBB5DFC3572FE6D3A9AC0B35BB427E250F42C38F7C8229889CAB037C42E200B601CD439CDEAA12FB2BD8949C566E59096AA9207165C6FF0602B1D6026F714517277FB154C47E487FBE415C25C0D0F79466F1479B31A8D78319978D8A03B9C3F15E5C0E086EDE931D69BABD36167BE6CCA8705378F8849A4F00EDA2941308AE5A94BBDD3FC53924623E0D6764E089D679048F699118D7807884AD13CF484866D8210BCB76DAEB103AD6BA1CED0DBFE78F1E8A44F26FC69D5464BF23324D53EF7BE0C965DBBA2418E1FAA16FB9CA5FC990337E0E1858F263A8B88AE081D053CB4BCA7D90B63225ABA7C40F4B86B1D2E91D0EEFC52657B70695BE96FEEF3768EFCB547A2ED10F9D35C10E8FFE3A5A7F049FF7FFBF5F7400586830FBAC2B6908C2742DE20520065E89CF8CF7DBD279063967C0E1E77A97297BFDDF28B58C1A0260537C74BC2B79676CDE217FFA7D48236D4A309954153DEB39A31AADF6B369CC022E7B7DD7A0B4A72097703E60648B615A4DDDF2D715A5E0992379537A44C13AD103D1FD5A186167E5DE84B11C0F56E1FC6808D2DB35E6E3587243CF59DE605CB806E242A37A2E03838EDCB4B064425FBA5F5F8331EDDF79B46AACC70EEB714995A27B5A879577BDAF956EC9F1C234BD3942E2B2F0E372902637993C54815B69F7EAFF288C16C20D3F8D26B64E23599E6996A833E286FEAD5A7490151202E859E484", + "comment": "valid coordinator step1" + } + ], + "errorTestCases": [ + { + "tcId": 2, + "pmsg1Indices": [ + 0, + 1, + 2 + ], + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3" + ], + "t": 0 + }, + "expectedError": { + "type": "ThresholdOrCountError" + }, + "comment": "invalid threshold value" + }, + { + "tcId": 3, + "pmsg1Indices": [ + 0, + 1, + 2 + ], + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3" + ], + "t": 4 + }, + "expectedError": { + "type": "ThresholdOrCountError" + }, + "comment": "invalid threshold value t > n" + }, + { + "tcId": 4, + "pmsg1Indices": [ + 0, + 1, + 2 + ], + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "030000000000000000000000000000000000000000000000000000000000000005" + ], + "t": 2 + }, + "expectedError": { + "type": "InvalidHostPubkeyError", + "participantId": 2 + }, + "comment": "hostpubkeys list contains an invalid value" + }, + { + "tcId": 5, + "pmsg1Indices": [ + 0, + 1, + 2 + ], + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3" + ], + "t": 2 + }, + "expectedError": { + "type": "DuplicateHostPubkeyError", + "participantId1": 0, + "participantId2": 2 + }, + "comment": "hostpubkeys list contains duplicate values" + }, + { + "tcId": 6, + "pmsg1Indices": [ + 0, + 1, + 2 + ], + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "000000000000000000000000000000000000000000000000000000000000000000" + ], + "t": 2 + }, + "expectedError": { + "type": "InvalidHostPubkeyError", + "participantId": 2 + }, + "comment": "hostpubkeys list contains an infinity point" + }, + { + "tcId": 7, + "pmsg1Indices": [ + 0, + 1 + ], + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3" + ], + "t": 2 + }, + "expectedError": { + "type": "ValueError" + }, + "comment": "invalid pmsgs1: fewer entries than participants" + }, + { + "tcId": 8, + "pmsg1Indices": [ + 0, + 1, + 2, + 0 + ], + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3" + ], + "t": 2 + }, + "expectedError": { + "type": "ValueError" + }, + "comment": "invalid pmsgs1: more entries than participants" + }, + { + "tcId": 9, + "pmsg1Indices": [ + 0, + 3, + 2 + ], + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3" + ], + "t": 2 + }, + "expectedError": { + "type": "ValueError" + }, + "comment": "participant (id 1) message has an enc_shares list of invalid length" + }, + { + "tcId": 10, + "pmsg1Indices": [ + 4, + 1, + 2 + ], + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3" + ], + "t": 2 + }, + "expectedError": { + "type": "ValueError" + }, + "comment": "missing simplpedpop participant message at index 0" + }, + { + "tcId": 11, + "pmsg1Indices": [ + 0, + 5, + 2 + ], + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3" + ], + "t": 2 + }, + "expectedError": { + "type": "ValueError" + }, + "comment": "missing public nonce in pmsg1 (index 1)" + } + ] + }, + { + "pmsg1Pool": [ + "0216C95A7FA9079C73184E8646721F59BB0361A839FD6DB9B8CF53CA1EA5B9E1C6558B3C0BFEA2180B7449D7EF101A57D8CF9FC0E32D77FAFDC712A3897007419D6CE9F8FEA08EC9935120955BDFD2519F7B29AD3E0248DA19A0E7D3900FE14CEA02BC88DD206C8AD8C1B52F93AD147695802206D0CA6AB756BD51E5FF5BE858FD219827C7BD6ECCE374663262B90B8D2944C94F9AB1E83F38DBD12823DD7D58B8B3CF1C0AB792C1AF61FA3866CCC4221CAC3CB9D35F7F5358990AB02D7E246BCEDD2AA0622F84004D5AED9B4FC855F20AE22CE260B6E79F56DE5BC2F5B73D137DE3", + "02DA601E3FC8D21A247C5677D9CACFFE0BB86860BC1C233BECA0D97776AAE309DA832AE406B679DEE20F859D361B3C2A1A9EEED646F1B5B6761D1370889D2C0F4EFFFFB7792DD5B2EF16A87A3868280F745D4FAA4EB032695FF0E9285DB7155B8303F216E714F96E6AAE00E3FAAF5188FF6F66C71B932E10B17AA8403B17DE9A8F52F4CDA3696B4B7701D9CA898B23E0211D9A3BB64CD130C329BDFC826BCBFCA13D563259BE0136A48FE852FC559B1EA6DE9EB24A43FBE2B42E3F1AE85AB03BB99B54825042D24EFCD6B3C4E2F29DFA11B55973C08DEADB120BA5B90EF12D74353B", + "02FB246B4866569A7A495CFFB1133DAF9096FE4FA22EDA6AF9D7A8F236F9C222B68A8C833B35FF0445F94744CBDE8FFC29CBBBFD3B7BA7F3B00A56E96EFA24F6C582EA017BA6A173AA31E4E9F2F5F74D1C4ABA2BBA5B9FACE5F3042B5B2323AB81038AA8962D7EBBF847CE551B1EF8D0D5E8A051B7FD6A29127709F35A6C18CED300EE7CAEA466336EF62D77586E88F1689219851C1473C34AA555FB08E2A7BD90977A1AB2888E05E016EFE2799D43300A3A576497D23F32CFBABE18A069512E2A96826AFB726FF50193F6F50E4CA0A630DEC81B68554EEFDA5331C04E7E7FFE7C1A", + "02DA601E3FC8D21A247C5677D9CACFFE0BB86860BC1C233BECA0D97776AAE309DA832AE406B679DEE20F859D361B3C2A1A9EEED646F1B5B6761D1370889D2C0F4EFFFFB7792DD5B2EF16A87A3868280F745D4FAA4EB032695FF0E9285DB7155B8303F216E714F96E6AAE00E3FAAF5188FF6F66C71B932E10B17AA8403B17DE9A8F52F4CDA3696B4B7701D9CA898B23E0211D9A3BB64CD130C329BDFC826BCBFCA13D563259BE0136A48FE852FC559B1EA6DE9EB24A43FBE2B42E3F1AE85AB03BB99B", + "", + "02DA601E3FC8D21A247C5677D9CACFFE0BB86860BC1C233BECA0D97776AAE309DA832AE406B679DEE20F859D361B3C2A1A9EEED646F1B5B6761D1370889D2C0F4EFFFFB7792DD5B2EF16A87A3868280F745D4FAA4EB032695FF0E9285DB7155B83" + ], + "validTestCases": [ + { + "tcId": 12, + "pmsg1Indices": [ + 0, + 1, + 2 + ], + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3" + ], + "t": 1 + }, + "expectedCmsg1": "0216C95A7FA9079C73184E8646721F59BB0361A839FD6DB9B8CF53CA1EA5B9E1C602DA601E3FC8D21A247C5677D9CACFFE0BB86860BC1C233BECA0D97776AAE309DA02FB246B4866569A7A495CFFB1133DAF9096FE4FA22EDA6AF9D7A8F236F9C222B6558B3C0BFEA2180B7449D7EF101A57D8CF9FC0E32D77FAFDC712A3897007419D6CE9F8FEA08EC9935120955BDFD2519F7B29AD3E0248DA19A0E7D3900FE14CEA832AE406B679DEE20F859D361B3C2A1A9EEED646F1B5B6761D1370889D2C0F4EFFFFB7792DD5B2EF16A87A3868280F745D4FAA4EB032695FF0E9285DB7155B838A8C833B35FF0445F94744CBDE8FFC29CBBBFD3B7BA7F3B00A56E96EFA24F6C582EA017BA6A173AA31E4E9F2F5F74D1C4ABA2BBA5B9FACE5F3042B5B2323AB8102BC88DD206C8AD8C1B52F93AD147695802206D0CA6AB756BD51E5FF5BE858FD2103F216E714F96E6AAE00E3FAAF5188FF6F66C71B932E10B17AA8403B17DE9A8F52038AA8962D7EBBF847CE551B1EF8D0D5E8A051B7FD6A29127709F35A6C18CED3007B7219CB404BC96C6D7444B2B85EB2F707B2B345CEA20633657AF21250A668059F6916FE21FE3408D26DDCBFA270CDC67821D88F0B203C46481157B5559F71CD018DADE4C6444BC59855410794924D7793C2ACB37221A3017369F49A1A4FEDF7", + "comment": "valid coordinator step1" + } + ], + "errorTestCases": [ + { + "tcId": 13, + "pmsg1Indices": [ + 0, + 1, + 2 + ], + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3" + ], + "t": 0 + }, + "expectedError": { + "type": "ThresholdOrCountError" + }, + "comment": "invalid threshold value" + }, + { + "tcId": 14, + "pmsg1Indices": [ + 0, + 1, + 2 + ], + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3" + ], + "t": 4 + }, + "expectedError": { + "type": "ThresholdOrCountError" + }, + "comment": "invalid threshold value t > n" + }, + { + "tcId": 15, + "pmsg1Indices": [ + 0, + 1, + 2 + ], + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "030000000000000000000000000000000000000000000000000000000000000005" + ], + "t": 1 + }, + "expectedError": { + "type": "InvalidHostPubkeyError", + "participantId": 2 + }, + "comment": "hostpubkeys list contains an invalid value" + }, + { + "tcId": 16, + "pmsg1Indices": [ + 0, + 1, + 2 + ], + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3" + ], + "t": 1 + }, + "expectedError": { + "type": "DuplicateHostPubkeyError", + "participantId1": 0, + "participantId2": 2 + }, + "comment": "hostpubkeys list contains duplicate values" + }, + { + "tcId": 17, + "pmsg1Indices": [ + 0, + 1, + 2 + ], + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "000000000000000000000000000000000000000000000000000000000000000000" + ], + "t": 1 + }, + "expectedError": { + "type": "InvalidHostPubkeyError", + "participantId": 2 + }, + "comment": "hostpubkeys list contains an infinity point" + }, + { + "tcId": 18, + "pmsg1Indices": [ + 0, + 1 + ], + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3" + ], + "t": 1 + }, + "expectedError": { + "type": "ValueError" + }, + "comment": "invalid pmsgs1: fewer entries than participants" + }, + { + "tcId": 19, + "pmsg1Indices": [ + 0, + 1, + 2, + 0 + ], + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3" + ], + "t": 1 + }, + "expectedError": { + "type": "ValueError" + }, + "comment": "invalid pmsgs1: more entries than participants" + }, + { + "tcId": 20, + "pmsg1Indices": [ + 0, + 3, + 2 + ], + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3" + ], + "t": 1 + }, + "expectedError": { + "type": "ValueError" + }, + "comment": "participant (id 1) message has an enc_shares list of invalid length" + }, + { + "tcId": 21, + "pmsg1Indices": [ + 4, + 1, + 2 + ], + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3" + ], + "t": 1 + }, + "expectedError": { + "type": "ValueError" + }, + "comment": "missing simplpedpop participant message at index 0" + }, + { + "tcId": 22, + "pmsg1Indices": [ + 0, + 5, + 2 + ], + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3" + ], + "t": 1 + }, + "expectedError": { + "type": "ValueError" + }, + "comment": "missing public nonce in pmsg1 (index 1)" + } + ] + }, + { + "pmsg1Pool": [ + "02F659A9668C1E7FB2BF4B457CA91C9925B3AB9F3F029C489C53603EF6AF90199B03BB0D7426739F2FAB24C6A0B2693273BBC92E03D3CB1A252956D372E4484F3EFE030C43B612EC5E536EF7853FCC8744BF77AF272485349484BDAA6FB6F2222FDD5A92AE219E3FE6B306490A7510AF6D9C981983DE7E4A83127F350451986EB2AF829A8C8BF483ADD195B5C7B08073D9548FFCDFB5626F0DA6A26468AD9BE54D5C83026C6EBBC55EDC86ABA62BA55A3375A940E731228F214937BF8A2FC58B29AE8F087C47A6836F30317C4B914E33F10B72CA6EDD116A3DF9E8BFE997045F9F957D31A43BB05F6162602B96E5D8C0AE3A07C8228E41DE772B0ACD96937B6CD0115ADE0ACB3150F08EA8668C262DC091C6ACA77674E8C749023FA6FB5D6938BEC73CD8", + "034619FE6A2FF09FE393CACB98B0708545255926B9CB33103567AAF2EE4D23B05403D16A8AFDD68449C6C00FF346EC9D29EC8D3D681F3B2EC04616F0A75693940C2402BF150900B4B78A40CF51C1429F0C796B7EC6A4B9A6337B346895DAD6E7F4108D18AB016E948B6EE152EEC9B6AD07ADC102177C6060C5FA6AB774DE136D59657C62D596F9C9477FF09A14654D48AC94B9CF381221ED27DC26B0F224C70039771F0334032916F989086FB5C62722C096F0EC6A85808DB2BFA270227A72245BD7AC1BB9BC513A6059135E04745D8BA961BA2B73A8F109469632CACD2DEA19DD9F985D03F3FCD86C88731C94E5588CA37E072DBF0A968AF541994B5A5D3E6011C9D92660F6A85166FBC97DE076DD7F81CB81CF07262D7296453D47065FD31A952CA8C9", + "0250A0832ED8FECB459561A2BFE1103A29CD4B23D53B27B707376FE0E67562F200020F17B8706ED73D63DEA8F15898E59E062E761C8E04DC3BDD78092C006A73440F022DF27B11574AE811C767A2E2A8645E9D63910CFD74F52F65488A5A64133EFBDAACBC3B4D52DD7A762F752250E41218F5295C3DDFCFF43350759D74A62B695352CE3C302E8718BA21C85BED769AEF3846A88E691AE945D6EBA09F51ACB0143D1002522C52C88A6B0B1E9CF8943960E87722C1D5307069723C25B151939A58B08C10E0165D9516387D7C6A7EDE123BB40683A6037628D3853322CF37FE31A36960DABA80A39126B2817F0E441AFFB94ECB9C9133C9C377EDDC7BE2BB97A0A43675102F91CB07BEC0D3422EA4ADB05AB0264DFCBAA0D94EBB93D825110A8816C8D1F6", + "034619FE6A2FF09FE393CACB98B0708545255926B9CB33103567AAF2EE4D23B05403D16A8AFDD68449C6C00FF346EC9D29EC8D3D681F3B2EC04616F0A75693940C2402BF150900B4B78A40CF51C1429F0C796B7EC6A4B9A6337B346895DAD6E7F4108D18AB016E948B6EE152EEC9B6AD07ADC102177C6060C5FA6AB774DE136D59657C62D596F9C9477FF09A14654D48AC94B9CF381221ED27DC26B0F224C70039771F0334032916F989086FB5C62722C096F0EC6A85808DB2BFA270227A72245BD7AC1BB9BC513A6059135E04745D8BA961BA2B73A8F109469632CACD2DEA19DD9F985D03F3FCD86C88731C94E5588CA37E072DBF0A968AF541994B5A5D3E6011C9D926", + "", + "034619FE6A2FF09FE393CACB98B0708545255926B9CB33103567AAF2EE4D23B05403D16A8AFDD68449C6C00FF346EC9D29EC8D3D681F3B2EC04616F0A75693940C2402BF150900B4B78A40CF51C1429F0C796B7EC6A4B9A6337B346895DAD6E7F4108D18AB016E948B6EE152EEC9B6AD07ADC102177C6060C5FA6AB774DE136D59657C62D596F9C9477FF09A14654D48AC94B9CF381221ED27DC26B0F224C70039771F" + ], + "validTestCases": [ + { + "tcId": 23, + "pmsg1Indices": [ + 0, + 1, + 2 + ], + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3" + ], + "t": 3 + }, + "expectedCmsg1": "02F659A9668C1E7FB2BF4B457CA91C9925B3AB9F3F029C489C53603EF6AF90199B034619FE6A2FF09FE393CACB98B0708545255926B9CB33103567AAF2EE4D23B0540250A0832ED8FECB459561A2BFE1103A29CD4B23D53B27B707376FE0E67562F20003D2BAB55842CB855189CF690BA0E2C588AEFCD985F6E6B60C3CFCBDBF42FC68F702D17C1ACB4697C8032830CFA0099BE475C8DBDC63E2CE294795C8AAAF69EF908792AE219E3FE6B306490A7510AF6D9C981983DE7E4A83127F350451986EB2AF829A8C8BF483ADD195B5C7B08073D9548FFCDFB5626F0DA6A26468AD9BE54D5C8318AB016E948B6EE152EEC9B6AD07ADC102177C6060C5FA6AB774DE136D59657C62D596F9C9477FF09A14654D48AC94B9CF381221ED27DC26B0F224C70039771FACBC3B4D52DD7A762F752250E41218F5295C3DDFCFF43350759D74A62B695352CE3C302E8718BA21C85BED769AEF3846A88E691AE945D6EBA09F51ACB0143D10026C6EBBC55EDC86ABA62BA55A3375A940E731228F214937BF8A2FC58B29AE8F080334032916F989086FB5C62722C096F0EC6A85808DB2BFA270227A72245BD7AC1B02522C52C88A6B0B1E9CF8943960E87722C1D5307069723C25B151939A58B08C10161A5552E5C1C256BA8489D1D621337C132BBECEF9840E3606582F918031F3E662B050C8F49D54C73A0F4C4D0B06DA93B81DC5463511E05913D9F2E0B5DB67D39B53A4AA164B45269B41B8F06E4254C47A55B7132E0310C626CE46DB6ABCB797", + "comment": "valid coordinator step1" + } + ], + "errorTestCases": [ + { + "tcId": 24, + "pmsg1Indices": [ + 0, + 1, + 2 + ], + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3" + ], + "t": 0 + }, + "expectedError": { + "type": "ThresholdOrCountError" + }, + "comment": "invalid threshold value" + }, + { + "tcId": 25, + "pmsg1Indices": [ + 0, + 1, + 2 + ], + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3" + ], + "t": 4 + }, + "expectedError": { + "type": "ThresholdOrCountError" + }, + "comment": "invalid threshold value t > n" + }, + { + "tcId": 26, + "pmsg1Indices": [ + 0, + 1, + 2 + ], + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "030000000000000000000000000000000000000000000000000000000000000005" + ], + "t": 3 + }, + "expectedError": { + "type": "InvalidHostPubkeyError", + "participantId": 2 + }, + "comment": "hostpubkeys list contains an invalid value" + }, + { + "tcId": 27, + "pmsg1Indices": [ + 0, + 1, + 2 + ], + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3" + ], + "t": 3 + }, + "expectedError": { + "type": "DuplicateHostPubkeyError", + "participantId1": 0, + "participantId2": 2 + }, + "comment": "hostpubkeys list contains duplicate values" + }, + { + "tcId": 28, + "pmsg1Indices": [ + 0, + 1, + 2 + ], + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "000000000000000000000000000000000000000000000000000000000000000000" + ], + "t": 3 + }, + "expectedError": { + "type": "InvalidHostPubkeyError", + "participantId": 2 + }, + "comment": "hostpubkeys list contains an infinity point" + }, + { + "tcId": 29, + "pmsg1Indices": [ + 0, + 1 + ], + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3" + ], + "t": 3 + }, + "expectedError": { + "type": "ValueError" + }, + "comment": "invalid pmsgs1: fewer entries than participants" + }, + { + "tcId": 30, + "pmsg1Indices": [ + 0, + 1, + 2, + 0 + ], + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3" + ], + "t": 3 + }, + "expectedError": { + "type": "ValueError" + }, + "comment": "invalid pmsgs1: more entries than participants" + }, + { + "tcId": 31, + "pmsg1Indices": [ + 0, + 3, + 2 + ], + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3" + ], + "t": 3 + }, + "expectedError": { + "type": "ValueError" + }, + "comment": "participant (id 1) message has an enc_shares list of invalid length" + }, + { + "tcId": 32, + "pmsg1Indices": [ + 4, + 1, + 2 + ], + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3" + ], + "t": 3 + }, + "expectedError": { + "type": "ValueError" + }, + "comment": "missing simplpedpop participant message at index 0" + }, + { + "tcId": 33, + "pmsg1Indices": [ + 0, + 5, + 2 + ], + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3" + ], + "t": 3 + }, + "expectedError": { + "type": "ValueError" + }, + "comment": "missing public nonce in pmsg1 (index 1)" + } + ] + }, + { + "pmsg1Pool": [ + "037ACDA3FDC4E9098EA3364B92CD6DD02654141995CFD3500E1C33D098F618458D024F105837D6A403410B38453D712F306673B4B4DAC9F38C8FCF92A2D7BBDB2CE2082BB8A48B5B6088DC14C4E231236B929B68DBDA2F43842D8CCD7812812EF26C6490104A19A7CF603530A4A96E5F9A52E58A22C61B2B5B93C453BB11509CEF460266D2D26EAA94E40ECCF9C6BB24C1DDE27957FE8A82F2389EA6BBFD4B265ACAD5C2BC4B8326FF7015CEC59F160D74C4911E39F1C703FE64ACC5D0258A814EEAE235412ABEFA95191200CDA1F3D9F14976F2401C387A1B5FEDCEE9AE829D8DB005B6584DD5D478D2B1CEE49B2B8FB20EFCDC2BB9DD7B7F75B13ACBF819369AC43D4D8A4D5D1E3E00CB20D4CEFB1FD632C252F5D8048F295A5C5BD62B83145D9135", + "034D3BEF06BFB2780C527B2A16CC2819160C8569EB94AF89F16B4F81C604E09417033E8D05176C0F89D48D8FFA1F6E218B129B0DB5D47572443FB7AB8574F7B09C4662DADAF9B9E9DC0E28322D2FB7F459D3F76D8739CE5E48667B9831813B90B9EB4F0825157039DE1B1C0C9DB2F52C4F51E112A113E2F436FF61CF94254B3E2A26036DEF7297E97F0D229BBB7B004AABEC90F7E4A769934376ED220FD78D79C3095CBE24CC80C556589A0B8E155C7DA2429FD3BE69817C8E237B911EDB37605569C237D0D39F4904C0294A6F38AA1ED43C3769BD1DC4372201CEC2A36D328F613A85CDE8E4E2F05D5A97365681A84FC3074416AFF8C91FDEF197C73047E0D3B43C76FDCCC6A8DC55BA01C9EF8EB155ADE1051E5EA74FDC1B150FEA588BBD377C3565", + "0246EC025D2E096D23F52B755D1DED61C58608B23C67087E90555F52C428AFA61B0330B5D6D8F21A3E1B47E225C6B837C6D5E74B7DD52B67482C6D3D386A5C19369B0BCEDABBF632561F24A4BECEBE1F22F9260C830E17EFB80D88EA5919C98871B78AA91A74C8F2B20F83FEB22B9599E9B416D49706181998EC04E3FDF0AAEB789F0251B5F3A899402AAE586113B18A5B2D1F12A49330AEDD5AC2314FF07EB151A9B28BB736697045FBC5CC366FA156F25C21760942BE0ED2FE35C8AE3B24C70E8392E71F4DD29880FB7C913D3BC68F1A488EE93FBF92D99639C927F56330C180DCEB705221AADCA90841FF0FD21EAE2C72D95DBB6867CF7AD1441BEF0F7264E25CC6A8A3503F93E535C36E3DED76843ECFDFD3A1246B7CC8C806A1F6E86C5D05DD95", + "0244EEC31E9105EB71FDDD1F777889003AD61D6C3116EAACF48B23F885AA617F130259C4EEE9CA0E391339ABE9010023AFA0A50FD403CBDDDCDEB801F406DE707B819777334881108C5C19F548963F3BF8F5E693969A30B2F749498262D8AC956E0D91EB42FB355114D3F0589AFBF10289A015EF05D1DF365BD1A0468582C0706760024461A885A565BD85A57B3047CBEE3D70C7E9C400C5FB2C0146F02944AB36C42E0A7D6A1780AB7000EC43EAD753D49C5A5DD08236F37B43ED82D40C97D03E8B4823339CBF4313CD658B22977779325944D49B9E928138E470B4E90C34F4D761778EB690F7F7EC769034FE7FFFB06D4EBFBABA9E4D442925D4BE8046D0BEACD19D4DE6699AA11B5C59E23743B81327C68D6E73B5770B834FACEC47E5CF3B5FACE4", + "034D3BEF06BFB2780C527B2A16CC2819160C8569EB94AF89F16B4F81C604E09417033E8D05176C0F89D48D8FFA1F6E218B129B0DB5D47572443FB7AB8574F7B09C4662DADAF9B9E9DC0E28322D2FB7F459D3F76D8739CE5E48667B9831813B90B9EB4F0825157039DE1B1C0C9DB2F52C4F51E112A113E2F436FF61CF94254B3E2A26036DEF7297E97F0D229BBB7B004AABEC90F7E4A769934376ED220FD78D79C3095CBE24CC80C556589A0B8E155C7DA2429FD3BE69817C8E237B911EDB37605569C237D0D39F4904C0294A6F38AA1ED43C3769BD1DC4372201CEC2A36D328F613A85CDE8E4E2F05D5A97365681A84FC3074416AFF8C91FDEF197C73047E0D3B43C76", + "", + "034D3BEF06BFB2780C527B2A16CC2819160C8569EB94AF89F16B4F81C604E09417033E8D05176C0F89D48D8FFA1F6E218B129B0DB5D47572443FB7AB8574F7B09C4662DADAF9B9E9DC0E28322D2FB7F459D3F76D8739CE5E48667B9831813B90B9EB4F0825157039DE1B1C0C9DB2F52C4F51E112A113E2F436FF61CF94254B3E2A26" + ], + "validTestCases": [ + { + "tcId": 34, + "pmsg1Indices": [ + 0, + 1, + 2, + 3 + ], + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3", + "02A480743556B0DC66F3F25262A0120AC231DEB6936D4BF8FA37ECA83C395DC8A8" + ], + "t": 2 + }, + "expectedCmsg1": "037ACDA3FDC4E9098EA3364B92CD6DD02654141995CFD3500E1C33D098F618458D034D3BEF06BFB2780C527B2A16CC2819160C8569EB94AF89F16B4F81C604E094170246EC025D2E096D23F52B755D1DED61C58608B23C67087E90555F52C428AFA61B0244EEC31E9105EB71FDDD1F777889003AD61D6C3116EAACF48B23F885AA617F13037B0F4758018C2AC0DC96FF1A6F6DB042B302717D1911B65B4419CAE29D79EBD9082BB8A48B5B6088DC14C4E231236B929B68DBDA2F43842D8CCD7812812EF26C6490104A19A7CF603530A4A96E5F9A52E58A22C61B2B5B93C453BB11509CEF4662DADAF9B9E9DC0E28322D2FB7F459D3F76D8739CE5E48667B9831813B90B9EB4F0825157039DE1B1C0C9DB2F52C4F51E112A113E2F436FF61CF94254B3E2A260BCEDABBF632561F24A4BECEBE1F22F9260C830E17EFB80D88EA5919C98871B78AA91A74C8F2B20F83FEB22B9599E9B416D49706181998EC04E3FDF0AAEB789F9777334881108C5C19F548963F3BF8F5E693969A30B2F749498262D8AC956E0D91EB42FB355114D3F0589AFBF10289A015EF05D1DF365BD1A0468582C07067600266D2D26EAA94E40ECCF9C6BB24C1DDE27957FE8A82F2389EA6BBFD4B265ACAD5036DEF7297E97F0D229BBB7B004AABEC90F7E4A769934376ED220FD78D79C3095C0251B5F3A899402AAE586113B18A5B2D1F12A49330AEDD5AC2314FF07EB151A9B2024461A885A565BD85A57B3047CBEE3D70C7E9C400C5FB2C0146F02944AB36C42E1715B884DD47347692CE0EEB35DDFFAF50746670244989D422CC8B64D884E0FC7764E8F01F2EA21D679CADDC011227835F29BB3B5CC3DFBAAE992C8E1310E7AB8349E55B996BAC1B39496EF23E0ED7DC95F3FF8E50711DEA5CC6D9238D71AC9441E0CDE02F944CEA3B398EDB0CEAAA373E0B9F6994FF46A854C8C86243D2CE91", + "comment": "valid coordinator step1" + } + ], + "errorTestCases": [ + { + "tcId": 35, + "pmsg1Indices": [ + 0, + 1, + 2, + 3 + ], + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3", + "02A480743556B0DC66F3F25262A0120AC231DEB6936D4BF8FA37ECA83C395DC8A8" + ], + "t": 0 + }, + "expectedError": { + "type": "ThresholdOrCountError" + }, + "comment": "invalid threshold value" + }, + { + "tcId": 36, + "pmsg1Indices": [ + 0, + 1, + 2, + 3 + ], + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3", + "02A480743556B0DC66F3F25262A0120AC231DEB6936D4BF8FA37ECA83C395DC8A8" + ], + "t": 5 + }, + "expectedError": { + "type": "ThresholdOrCountError" + }, + "comment": "invalid threshold value t > n" + }, + { + "tcId": 37, + "pmsg1Indices": [ + 0, + 1, + 2, + 3 + ], + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3", + "030000000000000000000000000000000000000000000000000000000000000005" + ], + "t": 2 + }, + "expectedError": { + "type": "InvalidHostPubkeyError", + "participantId": 3 + }, + "comment": "hostpubkeys list contains an invalid value" + }, + { + "tcId": 38, + "pmsg1Indices": [ + 0, + 1, + 2, + 3 + ], + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3", + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3" + ], + "t": 2 + }, + "expectedError": { + "type": "DuplicateHostPubkeyError", + "participantId1": 0, + "participantId2": 3 + }, + "comment": "hostpubkeys list contains duplicate values" + }, + { + "tcId": 39, + "pmsg1Indices": [ + 0, + 1, + 2, + 3 + ], + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3", + "000000000000000000000000000000000000000000000000000000000000000000" + ], + "t": 2 + }, + "expectedError": { + "type": "InvalidHostPubkeyError", + "participantId": 3 + }, + "comment": "hostpubkeys list contains an infinity point" + }, + { + "tcId": 40, + "pmsg1Indices": [ + 0, + 1, + 2 + ], + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3", + "02A480743556B0DC66F3F25262A0120AC231DEB6936D4BF8FA37ECA83C395DC8A8" + ], + "t": 2 + }, + "expectedError": { + "type": "ValueError" + }, + "comment": "invalid pmsgs1: fewer entries than participants" + }, + { + "tcId": 41, + "pmsg1Indices": [ + 0, + 1, + 2, + 3, + 0 + ], + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3", + "02A480743556B0DC66F3F25262A0120AC231DEB6936D4BF8FA37ECA83C395DC8A8" + ], + "t": 2 + }, + "expectedError": { + "type": "ValueError" + }, + "comment": "invalid pmsgs1: more entries than participants" + }, + { + "tcId": 42, + "pmsg1Indices": [ + 0, + 4, + 2, + 3 + ], + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3", + "02A480743556B0DC66F3F25262A0120AC231DEB6936D4BF8FA37ECA83C395DC8A8" + ], + "t": 2 + }, + "expectedError": { + "type": "ValueError" + }, + "comment": "participant (id 1) message has an enc_shares list of invalid length" + }, + { + "tcId": 43, + "pmsg1Indices": [ + 5, + 1, + 2, + 3 + ], + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3", + "02A480743556B0DC66F3F25262A0120AC231DEB6936D4BF8FA37ECA83C395DC8A8" + ], + "t": 2 + }, + "expectedError": { + "type": "ValueError" + }, + "comment": "missing simplpedpop participant message at index 0" + }, + { + "tcId": 44, + "pmsg1Indices": [ + 0, + 6, + 2, + 3 + ], + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3", + "02A480743556B0DC66F3F25262A0120AC231DEB6936D4BF8FA37ECA83C395DC8A8" + ], + "t": 2 + }, + "expectedError": { + "type": "ValueError" + }, + "comment": "missing public nonce in pmsg1 (index 1)" + } + ] + } + ] +} \ No newline at end of file diff --git a/bip-chilldkg/vectors/hostpubkey_gen_vectors.json b/bip-chilldkg/vectors/hostpubkey_gen_vectors.json new file mode 100644 index 0000000000..21e50417d4 --- /dev/null +++ b/bip-chilldkg/vectors/hostpubkey_gen_vectors.json @@ -0,0 +1,48 @@ +{ + "description": [ + "Test vectors for hostpubkey_gen(hostseckey).", + "Generates a compressed public key (33 bytes) from a 32-byte host secret key.", + "", + "For each valid test case:", + " Call hostpubkey_gen(hostseckey) and verify the result equals expectedHostpubkey.", + "", + "For each error test case:", + " Call hostpubkey_gen(hostseckey) and verify it raises an exception matching expectedError.", + " The expectedError object contains 'type' (the exception class name)." + ], + "totalTests": 4, + "validTestCases": [ + { + "tcId": 1, + "hostseckey": "631C047D50A67E45E27ED1FF25FCE179CAF059A2120D346ACD9774C1F2BAB66F", + "expectedHostpubkey": "0290D2B2CE35F62C2D88003D1E3E2E43B4BBDE194E849C84E059B2455E9772BAC4", + "comment": "valid host secret key" + } + ], + "errorTestCases": [ + { + "tcId": 2, + "hostseckey": "631C047D50A67E45E27ED1FF25FCE179", + "expectedError": { + "type": "ValueError" + }, + "comment": "length of host secret key is not 32 bytes" + }, + { + "tcId": 3, + "hostseckey": "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", + "expectedError": { + "type": "HostSeckeyError" + }, + "comment": "host secret key is out of range" + }, + { + "tcId": 4, + "hostseckey": "0000000000000000000000000000000000000000000000000000000000000000", + "expectedError": { + "type": "HostSeckeyError" + }, + "comment": "zeroed host secret key" + } + ] +} \ No newline at end of file diff --git a/bip-chilldkg/vectors/params_hash_vectors.json b/bip-chilldkg/vectors/params_hash_vectors.json new file mode 100644 index 0000000000..7298626acf --- /dev/null +++ b/bip-chilldkg/vectors/params_hash_vectors.json @@ -0,0 +1,108 @@ +{ + "description": [ + "Test vectors for params_hash(params).", + "Computes a 32-byte hash of the session parameters (hostpubkeys, threshold).", + "", + "For each valid test case:", + " Call params_hash(params) and verify the result equals expectedParamsHash.", + "", + "For each error test case:", + " Call params_hash(params) and verify it raises an exception matching expectedError.", + " The expectedError object contains 'type' (the exception class name).", + " Some errors include 'participantId' (identifier of the blamed participant)", + " or 'participantId1'/'participantId2' (identifiers of participants with duplicate keys)." + ], + "totalTests": 6, + "validTestCases": [ + { + "tcId": 1, + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3" + ], + "t": 2 + }, + "expectedParamsHash": "6A03D4E831DBF10F71C2C47F8F31FA5BCEDBC266B336DEBA7E11607697CEEB7C", + "comment": "standard 2-of-3 threshold" + }, + { + "tcId": 2, + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3" + ], + "t": 1 + }, + "expectedParamsHash": "3A0AC9644496D6A5723772EC7CF81462095519D107C3D4C6E6B0DD66951FADEB", + "comment": "min threshold value" + }, + { + "tcId": 3, + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3" + ], + "t": 3 + }, + "expectedParamsHash": "CAE987A3D2B2ACD32FEACE9CEAE231026C03AE26228EA026BCE82A08A5BF77E2", + "comment": "max threshold value" + } + ], + "errorTestCases": [ + { + "tcId": 4, + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3" + ], + "t": 0 + }, + "expectedError": { + "type": "ThresholdOrCountError" + }, + "comment": "invalid threshold value" + }, + { + "tcId": 5, + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "030000000000000000000000000000000000000000000000000000000000000005", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3" + ], + "t": 2 + }, + "expectedError": { + "type": "InvalidHostPubkeyError", + "participantId": 1 + }, + "comment": "hostpubkeys list contains an invalid value" + }, + { + "tcId": 6, + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7" + ], + "t": 2 + }, + "expectedError": { + "type": "DuplicateHostPubkeyError", + "participantId1": 1, + "participantId2": 3 + }, + "comment": "hostpubkeys list contains duplicate values" + } + ] +} \ No newline at end of file diff --git a/bip-chilldkg/vectors/participant_finalize_vectors.json b/bip-chilldkg/vectors/participant_finalize_vectors.json new file mode 100644 index 0000000000..ce52921061 --- /dev/null +++ b/bip-chilldkg/vectors/participant_finalize_vectors.json @@ -0,0 +1,274 @@ +{ + "description": [ + "Test vectors for participant_finalize(pstate2, cmsg2).", + "Finalizes the DKG protocol from a participant's perspective.", + "Verifies the coordinator's certificate (cmsg2) and outputs the DKG result and recovery data.", + "", + "Harness setup (re-derive state through two prior rounds):", + " 1. Call participant_step1(hostseckey, params, random) to obtain (pstate1, pmsg1_out).", + " Assert pmsg1_out == pmsg1.", + " 2. Call participant_step2(hostseckey, pstate1, cmsg1, auxRand) to obtain (pstate2, pmsg2_out).", + " Assert pmsg2_out == pmsg2.", + "", + "For each valid test case:", + " Call participant_finalize(pstate2, cmsg2).", + " Verify the result matches expectedOutput (dkgOutput and recoveryData).", + "", + "For each error test case:", + " Call participant_finalize(pstate2, cmsg2).", + " Verify it raises an exception matching expectedError." + ], + "totalTests": 16, + "testGroups": [ + { + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3" + ], + "t": 2 + }, + "hostseckey": "ADE179B2C56CB75868D44B333C16C89CB00DFDE378AD79C84D0CCE856E4F9207", + "random": "42B53D62E27380D6F7096EDA1C28C57DDB89FCD4CE5B843EDAC220E165B5A7EC", + "auxRand": "005F5C3A69BB274F4559490AD754F1F5AFFABAED4C71AD5D8ACBAEFC2B491573", + "pmsg1": "036BEE122EF60CBBB5DFC3572FE6D3A9AC0B35BB427E250F42C38F7C8229889CAB03E99E34AA2A0646563DF90F2407D0D0E345EAE2CF7F07F9F6561302F724EFA74708AE5A94BBDD3FC53924623E0D6764E089D679048F699118D7807884AD13CF484866D8210BCB76DAEB103AD6BA1CED0DBFE78F1E8A44F26FC69D5464BF23324D0260537C74BC2B79676CDE217FFA7D48236D4A309954153DEB39A31AADF6B369CCC6C144287B356EF70378A7F1054FF00354A01352587DC076554A82CD324B66A17006774A86C116C19F2C021793EAB87DF19EE3A978C60F757BA192DA23BAE5F56A3612D0720F35828FCFBA9DFEA790883B185A8B75A27449AF23169C919B47C7", + "cmsg1": "036BEE122EF60CBBB5DFC3572FE6D3A9AC0B35BB427E250F42C38F7C8229889CAB037C42E200B601CD439CDEAA12FB2BD8949C566E59096AA9207165C6FF0602B1D6026F714517277FB154C47E487FBE415C25C0D0F79466F1479B31A8D78319978D8A03B9C3F15E5C0E086EDE931D69BABD36167BE6CCA8705378F8849A4F00EDA2941308AE5A94BBDD3FC53924623E0D6764E089D679048F699118D7807884AD13CF484866D8210BCB76DAEB103AD6BA1CED0DBFE78F1E8A44F26FC69D5464BF23324D53EF7BE0C965DBBA2418E1FAA16FB9CA5FC990337E0E1858F263A8B88AE081D053CB4BCA7D90B63225ABA7C40F4B86B1D2E91D0EEFC52657B70695BE96FEEF3768EFCB547A2ED10F9D35C10E8FFE3A5A7F049FF7FFBF5F7400586830FBAC2B6908C2742DE20520065E89CF8CF7DBD279063967C0E1E77A97297BFDDF28B58C1A0260537C74BC2B79676CDE217FFA7D48236D4A309954153DEB39A31AADF6B369CC022E7B7DD7A0B4A72097703E60648B615A4DDDF2D715A5E0992379537A44C13AD103D1FD5A186167E5DE84B11C0F56E1FC6808D2DB35E6E3587243CF59DE605CB806E242A37A2E03838EDCB4B064425FBA5F5F8331EDDF79B46AACC70EEB714995A27B5A879577BDAF956EC9F1C234BD3942E2B2F0E372902637993C54815B69F7EAFF288C16C20D3F8D26B64E23599E6996A833E286FEAD5A7490151202E859E484", + "pmsg2": "A6C2DE535556E47C8F3427B9716CFC4A66ADD0F2788AB793E2FBFF7D81DA06315C3F62CAA2B85E369922E68A94113EB839B5E3E424A4F4BF2E8D2908F517E8BE", + "validTestCases": [ + { + "tcId": 1, + "cmsg2": "A6C2DE535556E47C8F3427B9716CFC4A66ADD0F2788AB793E2FBFF7D81DA06315C3F62CAA2B85E369922E68A94113EB839B5E3E424A4F4BF2E8D2908F517E8BE3B1B96C40D7F9A378F2A61FACF8469447344B5280F5E7B3C66B560709A6FA4D7E083E650F3C109995ACB96EEA2EE4EF252257E1BF92FFF121CE916DE2D7C4B5479195E7CBE22A8B53C41012ABEA37F0B59579E43EDC6477E8CC5C7EB9F3C8BF7AEC850C63A659FADD91C9063908054FC4D193594E132ADFBE1F24BE74C8BEA7A", + "expectedOutput": { + "dkgOutput": { + "secshare": "78F979492EF00DFD84069C2E8367753A712447527C02A2887D5AF86A6F4D02BA", + "threshPk": "03DF2E2C605ACE90BFAAE275614FDA6D6233B1438EE6D8CE1EA74111887E3110F7", + "pubshares": [ + "025C8EC2A3D153823BD2820F6943FD4010B9A9C48D779A6845D2CDDB0EFE36E1EC", + "03397F3E8A16CCF5BD71E084163A9370C290F2915BC4BF80FDEA1C0E713B91F9C3", + "037709A9E38D16A7C57821BF5D97DEBA9588BB0C3DAC8B29BB732D088AB70FCFB6" + ] + }, + "recoveryData": "0000000203B74C6FD38288DDF94300E887B12DD40F655791144A07C32487FBD81CC228C07903B9C3F15E5C0E086EDE931D69BABD36167BE6CCA8705378F8849A4F00EDA2941303AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F303AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A30260537C74BC2B79676CDE217FFA7D48236D4A309954153DEB39A31AADF6B369CC022E7B7DD7A0B4A72097703E60648B615A4DDDF2D715A5E0992379537A44C13AD103D1FD5A186167E5DE84B11C0F56E1FC6808D2DB35E6E3587243CF59DE605CB806E242A37A2E03838EDCB4B064425FBA5F5F8331EDDF79B46AACC70EEB714995A27B5A879577BDAF956EC9F1C234BD3942E2B2F0E372902637993C54815B69F7EAFF288C16C20D3F8D26B64E23599E6996A833E286FEAD5A7490151202E859E484A6C2DE535556E47C8F3427B9716CFC4A66ADD0F2788AB793E2FBFF7D81DA06315C3F62CAA2B85E369922E68A94113EB839B5E3E424A4F4BF2E8D2908F517E8BE3B1B96C40D7F9A378F2A61FACF8469447344B5280F5E7B3C66B560709A6FA4D7E083E650F3C109995ACB96EEA2EE4EF252257E1BF92FFF121CE916DE2D7C4B5479195E7CBE22A8B53C41012ABEA37F0B59579E43EDC6477E8CC5C7EB9F3C8BF7AEC850C63A659FADD91C9063908054FC4D193594E132ADFBE1F24BE74C8BEA7A" + }, + "comment": "valid participant finalize" + } + ], + "errorTestCases": [ + { + "tcId": 2, + "cmsg2": "A6C2DE535556E47C8F3427B9716CFC4A66ADD0F2788AB793E2FBFF7D81DA06315C3F62CAA2B85E369922E68A94113EB839B5E3E424A4F4BF2E8D2908F517E8BE3B1B96C40D7F9A378F2A61FACF8469447344B5280F5E7B3C66B560709A6FA4D7E083E650F3C109995ACB96EEA2EE4EF252257E1BF92FFF121CE916DE2D7C4B54", + "expectedError": { + "type": "ValueError" + }, + "comment": "invalid cmsg2: length is invalid (missing last signature)" + }, + { + "tcId": 3, + "cmsg2": "A6C2DE535556E47C8F3427B9716CFC4A66ADD0F2788AB793E2FBFF7D81DA06315C3F62CAA2B85E369922E68A94113EB839B5E3E424A4F4BF2E8D2908F517E8BE3B1B96C40D7F9A378F2A61FACF8469447344B5280F5E7B3C66B560709A6FA4D7E083E650F3C109995ACB96EEA2EE4EF252257E1BF92FFF121CE916DE2D7C4B5479195E7CBE22A8B53C41012ABEA37F0B59579E43EDC6477E8CC5C7EB9F3C8BF7AEC850C63A659FADD91C9063908054FC4D193594E132ADFBE1F24BE74C8BEA7A00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "expectedError": { + "type": "ValueError" + }, + "comment": "invalid cmsg2: length is invalid (extra data appended)" + }, + { + "tcId": 4, + "cmsg2": "A6C2DE535556E47C8F3427B9716CFC4A66ADD0F2788AB793E2FBFF7D81DA06315C3F62CAA2B85E369922E68A94113EB839B5E3E424A4F4BF2E8D2908F517E8BE3B1B96C40D7F9A378F2A61FACF8469447344B5280F5E7B3C66B560709A6FA4D7E083E650F3C109995ACB96EEA2EE4EF252257E1BF92FFF121CE916DE2D7C4B5409C289578B96E6283AB13E4741FB489FC147FB1A5F446A314BA73C052131EFB04B83247A0BCEDF5205202AD64188B24B0BC5B51A17AEB218BD98DBE000C843B9", + "expectedError": { + "type": "FaultyCoordinatorError", + "message": "Coordinator has provided a certificate with an invalid signature" + }, + "comment": "invalid cmsg2: last signature is invalid" + } + ] + }, + { + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3" + ], + "t": 1 + }, + "hostseckey": "ADE179B2C56CB75868D44B333C16C89CB00DFDE378AD79C84D0CCE856E4F9207", + "random": "42B53D62E27380D6F7096EDA1C28C57DDB89FCD4CE5B843EDAC220E165B5A7EC", + "auxRand": "005F5C3A69BB274F4559490AD754F1F5AFFABAED4C71AD5D8ACBAEFC2B491573", + "pmsg1": "0216C95A7FA9079C73184E8646721F59BB0361A839FD6DB9B8CF53CA1EA5B9E1C6558B3C0BFEA2180B7449D7EF101A57D8CF9FC0E32D77FAFDC712A3897007419D6CE9F8FEA08EC9935120955BDFD2519F7B29AD3E0248DA19A0E7D3900FE14CEA02BC88DD206C8AD8C1B52F93AD147695802206D0CA6AB756BD51E5FF5BE858FD219827C7BD6ECCE374663262B90B8D2944C94F9AB1E83F38DBD12823DD7D58B8B3CF1C0AB792C1AF61FA3866CCC4221CAC3CB9D35F7F5358990AB02D7E246BCEDD2AA0622F84004D5AED9B4FC855F20AE22CE260B6E79F56DE5BC2F5B73D137DE3", + "cmsg1": "0216C95A7FA9079C73184E8646721F59BB0361A839FD6DB9B8CF53CA1EA5B9E1C602DA601E3FC8D21A247C5677D9CACFFE0BB86860BC1C233BECA0D97776AAE309DA02FB246B4866569A7A495CFFB1133DAF9096FE4FA22EDA6AF9D7A8F236F9C222B6558B3C0BFEA2180B7449D7EF101A57D8CF9FC0E32D77FAFDC712A3897007419D6CE9F8FEA08EC9935120955BDFD2519F7B29AD3E0248DA19A0E7D3900FE14CEA832AE406B679DEE20F859D361B3C2A1A9EEED646F1B5B6761D1370889D2C0F4EFFFFB7792DD5B2EF16A87A3868280F745D4FAA4EB032695FF0E9285DB7155B838A8C833B35FF0445F94744CBDE8FFC29CBBBFD3B7BA7F3B00A56E96EFA24F6C582EA017BA6A173AA31E4E9F2F5F74D1C4ABA2BBA5B9FACE5F3042B5B2323AB8102BC88DD206C8AD8C1B52F93AD147695802206D0CA6AB756BD51E5FF5BE858FD2103F216E714F96E6AAE00E3FAAF5188FF6F66C71B932E10B17AA8403B17DE9A8F52038AA8962D7EBBF847CE551B1EF8D0D5E8A051B7FD6A29127709F35A6C18CED3007B7219CB404BC96C6D7444B2B85EB2F707B2B345CEA20633657AF21250A668059F6916FE21FE3408D26DDCBFA270CDC67821D88F0B203C46481157B5559F71CD018DADE4C6444BC59855410794924D7793C2ACB37221A3017369F49A1A4FEDF7", + "pmsg2": "82E33ED511440D5609481CB77FDE1E385865F4F00C2E84ACF8DED01EC3532C714C28D13DD572C5808AF82272C1A782B3C0C720EC9527A7AFFE2535688D29AC06", + "validTestCases": [ + { + "tcId": 5, + "cmsg2": "82E33ED511440D5609481CB77FDE1E385865F4F00C2E84ACF8DED01EC3532C714C28D13DD572C5808AF82272C1A782B3C0C720EC9527A7AFFE2535688D29AC06B2FDC730FF8396ED094040BA12EA54F3D0B53DBD4613DCDF4AE0C89A6C7A9AC6A973EB2157189E3CBFB144FAE1F421F42450F49A48FB9027C8FDA37BAFE334EEA9B4EADAC901B2D6A24ADFF77FBFA1A948F8846CA445E7A7E970D26EA440370F1AD8302DDB489191F5536CA4B102EF5535406BBB1D4003AAA6AEF8E83F7D2AD6", + "expectedOutput": { + "dkgOutput": { + "secshare": "2736A277FB0F9BC76409D2F8C170DF72CB0BDF88AB3519F767101C69EB546A35", + "threshPk": "0285488475157EAD0313E90209EB43F010ED843D9397DC427E029352DEF8402F38", + "pubshares": [ + "0285488475157EAD0313E90209EB43F010ED843D9397DC427E029352DEF8402F38", + "0285488475157EAD0313E90209EB43F010ED843D9397DC427E029352DEF8402F38", + "0285488475157EAD0313E90209EB43F010ED843D9397DC427E029352DEF8402F38" + ] + }, + "recoveryData": "0000000103D944E06ECEC686276C06903F999B55124292AC186A8CFF91C4B5812869F43C0103AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F303AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A302BC88DD206C8AD8C1B52F93AD147695802206D0CA6AB756BD51E5FF5BE858FD2103F216E714F96E6AAE00E3FAAF5188FF6F66C71B932E10B17AA8403B17DE9A8F52038AA8962D7EBBF847CE551B1EF8D0D5E8A051B7FD6A29127709F35A6C18CED3007B7219CB404BC96C6D7444B2B85EB2F707B2B345CEA20633657AF21250A668059F6916FE21FE3408D26DDCBFA270CDC67821D88F0B203C46481157B5559F71CD018DADE4C6444BC59855410794924D7793C2ACB37221A3017369F49A1A4FEDF782E33ED511440D5609481CB77FDE1E385865F4F00C2E84ACF8DED01EC3532C714C28D13DD572C5808AF82272C1A782B3C0C720EC9527A7AFFE2535688D29AC06B2FDC730FF8396ED094040BA12EA54F3D0B53DBD4613DCDF4AE0C89A6C7A9AC6A973EB2157189E3CBFB144FAE1F421F42450F49A48FB9027C8FDA37BAFE334EEA9B4EADAC901B2D6A24ADFF77FBFA1A948F8846CA445E7A7E970D26EA440370F1AD8302DDB489191F5536CA4B102EF5535406BBB1D4003AAA6AEF8E83F7D2AD6" + }, + "comment": "valid participant finalize" + } + ], + "errorTestCases": [ + { + "tcId": 6, + "cmsg2": "82E33ED511440D5609481CB77FDE1E385865F4F00C2E84ACF8DED01EC3532C714C28D13DD572C5808AF82272C1A782B3C0C720EC9527A7AFFE2535688D29AC06B2FDC730FF8396ED094040BA12EA54F3D0B53DBD4613DCDF4AE0C89A6C7A9AC6A973EB2157189E3CBFB144FAE1F421F42450F49A48FB9027C8FDA37BAFE334EE", + "expectedError": { + "type": "ValueError" + }, + "comment": "invalid cmsg2: length is invalid (missing last signature)" + }, + { + "tcId": 7, + "cmsg2": "82E33ED511440D5609481CB77FDE1E385865F4F00C2E84ACF8DED01EC3532C714C28D13DD572C5808AF82272C1A782B3C0C720EC9527A7AFFE2535688D29AC06B2FDC730FF8396ED094040BA12EA54F3D0B53DBD4613DCDF4AE0C89A6C7A9AC6A973EB2157189E3CBFB144FAE1F421F42450F49A48FB9027C8FDA37BAFE334EEA9B4EADAC901B2D6A24ADFF77FBFA1A948F8846CA445E7A7E970D26EA440370F1AD8302DDB489191F5536CA4B102EF5535406BBB1D4003AAA6AEF8E83F7D2AD600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "expectedError": { + "type": "ValueError" + }, + "comment": "invalid cmsg2: length is invalid (extra data appended)" + }, + { + "tcId": 8, + "cmsg2": "82E33ED511440D5609481CB77FDE1E385865F4F00C2E84ACF8DED01EC3532C714C28D13DD572C5808AF82272C1A782B3C0C720EC9527A7AFFE2535688D29AC06B2FDC730FF8396ED094040BA12EA54F3D0B53DBD4613DCDF4AE0C89A6C7A9AC6A973EB2157189E3CBFB144FAE1F421F42450F49A48FB9027C8FDA37BAFE334EE09C289578B96E6283AB13E4741FB489FC147FB1A5F446A314BA73C052131EFB04B83247A0BCEDF5205202AD64188B24B0BC5B51A17AEB218BD98DBE000C843B9", + "expectedError": { + "type": "FaultyCoordinatorError", + "message": "Coordinator has provided a certificate with an invalid signature" + }, + "comment": "invalid cmsg2: last signature is invalid" + } + ] + }, + { + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3" + ], + "t": 3 + }, + "hostseckey": "ADE179B2C56CB75868D44B333C16C89CB00DFDE378AD79C84D0CCE856E4F9207", + "random": "42B53D62E27380D6F7096EDA1C28C57DDB89FCD4CE5B843EDAC220E165B5A7EC", + "auxRand": "005F5C3A69BB274F4559490AD754F1F5AFFABAED4C71AD5D8ACBAEFC2B491573", + "pmsg1": "02F659A9668C1E7FB2BF4B457CA91C9925B3AB9F3F029C489C53603EF6AF90199B03BB0D7426739F2FAB24C6A0B2693273BBC92E03D3CB1A252956D372E4484F3EFE030C43B612EC5E536EF7853FCC8744BF77AF272485349484BDAA6FB6F2222FDD5A92AE219E3FE6B306490A7510AF6D9C981983DE7E4A83127F350451986EB2AF829A8C8BF483ADD195B5C7B08073D9548FFCDFB5626F0DA6A26468AD9BE54D5C83026C6EBBC55EDC86ABA62BA55A3375A940E731228F214937BF8A2FC58B29AE8F087C47A6836F30317C4B914E33F10B72CA6EDD116A3DF9E8BFE997045F9F957D31A43BB05F6162602B96E5D8C0AE3A07C8228E41DE772B0ACD96937B6CD0115ADE0ACB3150F08EA8668C262DC091C6ACA77674E8C749023FA6FB5D6938BEC73CD8", + "cmsg1": "02F659A9668C1E7FB2BF4B457CA91C9925B3AB9F3F029C489C53603EF6AF90199B034619FE6A2FF09FE393CACB98B0708545255926B9CB33103567AAF2EE4D23B0540250A0832ED8FECB459561A2BFE1103A29CD4B23D53B27B707376FE0E67562F20003D2BAB55842CB855189CF690BA0E2C588AEFCD985F6E6B60C3CFCBDBF42FC68F702D17C1ACB4697C8032830CFA0099BE475C8DBDC63E2CE294795C8AAAF69EF908792AE219E3FE6B306490A7510AF6D9C981983DE7E4A83127F350451986EB2AF829A8C8BF483ADD195B5C7B08073D9548FFCDFB5626F0DA6A26468AD9BE54D5C8318AB016E948B6EE152EEC9B6AD07ADC102177C6060C5FA6AB774DE136D59657C62D596F9C9477FF09A14654D48AC94B9CF381221ED27DC26B0F224C70039771FACBC3B4D52DD7A762F752250E41218F5295C3DDFCFF43350759D74A62B695352CE3C302E8718BA21C85BED769AEF3846A88E691AE945D6EBA09F51ACB0143D10026C6EBBC55EDC86ABA62BA55A3375A940E731228F214937BF8A2FC58B29AE8F080334032916F989086FB5C62722C096F0EC6A85808DB2BFA270227A72245BD7AC1B02522C52C88A6B0B1E9CF8943960E87722C1D5307069723C25B151939A58B08C10161A5552E5C1C256BA8489D1D621337C132BBECEF9840E3606582F918031F3E662B050C8F49D54C73A0F4C4D0B06DA93B81DC5463511E05913D9F2E0B5DB67D39B53A4AA164B45269B41B8F06E4254C47A55B7132E0310C626CE46DB6ABCB797", + "pmsg2": "BFD15AA9C0A086B4BC83CC1FBE2864519E95360101E038B56CC6F58A13BDCBCDA5310629AD258C8FEC60DEB8ED49A2749E2EDD2204257F41713CF3181FB4F89B", + "validTestCases": [ + { + "tcId": 9, + "cmsg2": "BFD15AA9C0A086B4BC83CC1FBE2864519E95360101E038B56CC6F58A13BDCBCDA5310629AD258C8FEC60DEB8ED49A2749E2EDD2204257F41713CF3181FB4F89B0017D390C1E89DA9D738F5933E1A35B70C10DED152768C9FAB8593190E7417754AB67C0D15E873A2594A7EA490CC0EF1643A153A5B7467232FAE413CC8A8D8E7E27651C1E2907EDFA9CA3310332C9BE64A22B78FF873C307632E855E9E24E86C6F9E10F7D135A06F5B6839DFAA2F78973DAD89179131016D55D4E7B7FF2837F5", + "expectedOutput": { + "dkgOutput": { + "secshare": "A37C8E092C77AA1EE483503747827B7E5EBAE579347755CFA92A352BD32201E7", + "threshPk": "0387DF054E5992F51C6C9DF60EB956DFAC73F12B218FF559159FA1A14945BAEA97", + "pubshares": [ + "0338FF57248C1B692D92C39D9FF0834CE09DC84136D45A74F59A34A0DE8F82AF7C", + "03FEA503453A344A1FB374B6478A51D3220B6A7C881911FAA7656EB268E389B2E7", + "0324C87B62E0EA24C7F619DDED9EDCB8A42568EBC07FF4AD21A578C8DF3615365C" + ] + }, + "recoveryData": "000000030251F6B0ED5F9AF13FBB1A2EB117C1DC50EA0CEEA17A3C97C4B9BC88D14C8D59F903D2BAB55842CB855189CF690BA0E2C588AEFCD985F6E6B60C3CFCBDBF42FC68F702D17C1ACB4697C8032830CFA0099BE475C8DBDC63E2CE294795C8AAAF69EF908703AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F303AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3026C6EBBC55EDC86ABA62BA55A3375A940E731228F214937BF8A2FC58B29AE8F080334032916F989086FB5C62722C096F0EC6A85808DB2BFA270227A72245BD7AC1B02522C52C88A6B0B1E9CF8943960E87722C1D5307069723C25B151939A58B08C10161A5552E5C1C256BA8489D1D621337C132BBECEF9840E3606582F918031F3E662B050C8F49D54C73A0F4C4D0B06DA93B81DC5463511E05913D9F2E0B5DB67D39B53A4AA164B45269B41B8F06E4254C47A55B7132E0310C626CE46DB6ABCB797BFD15AA9C0A086B4BC83CC1FBE2864519E95360101E038B56CC6F58A13BDCBCDA5310629AD258C8FEC60DEB8ED49A2749E2EDD2204257F41713CF3181FB4F89B0017D390C1E89DA9D738F5933E1A35B70C10DED152768C9FAB8593190E7417754AB67C0D15E873A2594A7EA490CC0EF1643A153A5B7467232FAE413CC8A8D8E7E27651C1E2907EDFA9CA3310332C9BE64A22B78FF873C307632E855E9E24E86C6F9E10F7D135A06F5B6839DFAA2F78973DAD89179131016D55D4E7B7FF2837F5" + }, + "comment": "valid participant finalize" + } + ], + "errorTestCases": [ + { + "tcId": 10, + "cmsg2": "BFD15AA9C0A086B4BC83CC1FBE2864519E95360101E038B56CC6F58A13BDCBCDA5310629AD258C8FEC60DEB8ED49A2749E2EDD2204257F41713CF3181FB4F89B0017D390C1E89DA9D738F5933E1A35B70C10DED152768C9FAB8593190E7417754AB67C0D15E873A2594A7EA490CC0EF1643A153A5B7467232FAE413CC8A8D8E7", + "expectedError": { + "type": "ValueError" + }, + "comment": "invalid cmsg2: length is invalid (missing last signature)" + }, + { + "tcId": 11, + "cmsg2": "BFD15AA9C0A086B4BC83CC1FBE2864519E95360101E038B56CC6F58A13BDCBCDA5310629AD258C8FEC60DEB8ED49A2749E2EDD2204257F41713CF3181FB4F89B0017D390C1E89DA9D738F5933E1A35B70C10DED152768C9FAB8593190E7417754AB67C0D15E873A2594A7EA490CC0EF1643A153A5B7467232FAE413CC8A8D8E7E27651C1E2907EDFA9CA3310332C9BE64A22B78FF873C307632E855E9E24E86C6F9E10F7D135A06F5B6839DFAA2F78973DAD89179131016D55D4E7B7FF2837F500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "expectedError": { + "type": "ValueError" + }, + "comment": "invalid cmsg2: length is invalid (extra data appended)" + }, + { + "tcId": 12, + "cmsg2": "BFD15AA9C0A086B4BC83CC1FBE2864519E95360101E038B56CC6F58A13BDCBCDA5310629AD258C8FEC60DEB8ED49A2749E2EDD2204257F41713CF3181FB4F89B0017D390C1E89DA9D738F5933E1A35B70C10DED152768C9FAB8593190E7417754AB67C0D15E873A2594A7EA490CC0EF1643A153A5B7467232FAE413CC8A8D8E709C289578B96E6283AB13E4741FB489FC147FB1A5F446A314BA73C052131EFB04B83247A0BCEDF5205202AD64188B24B0BC5B51A17AEB218BD98DBE000C843B9", + "expectedError": { + "type": "FaultyCoordinatorError", + "message": "Coordinator has provided a certificate with an invalid signature" + }, + "comment": "invalid cmsg2: last signature is invalid" + } + ] + }, + { + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3", + "02A480743556B0DC66F3F25262A0120AC231DEB6936D4BF8FA37ECA83C395DC8A8" + ], + "t": 2 + }, + "hostseckey": "ADE179B2C56CB75868D44B333C16C89CB00DFDE378AD79C84D0CCE856E4F9207", + "random": "42B53D62E27380D6F7096EDA1C28C57DDB89FCD4CE5B843EDAC220E165B5A7EC", + "auxRand": "005F5C3A69BB274F4559490AD754F1F5AFFABAED4C71AD5D8ACBAEFC2B491573", + "pmsg1": "037ACDA3FDC4E9098EA3364B92CD6DD02654141995CFD3500E1C33D098F618458D024F105837D6A403410B38453D712F306673B4B4DAC9F38C8FCF92A2D7BBDB2CE2082BB8A48B5B6088DC14C4E231236B929B68DBDA2F43842D8CCD7812812EF26C6490104A19A7CF603530A4A96E5F9A52E58A22C61B2B5B93C453BB11509CEF460266D2D26EAA94E40ECCF9C6BB24C1DDE27957FE8A82F2389EA6BBFD4B265ACAD5C2BC4B8326FF7015CEC59F160D74C4911E39F1C703FE64ACC5D0258A814EEAE235412ABEFA95191200CDA1F3D9F14976F2401C387A1B5FEDCEE9AE829D8DB005B6584DD5D478D2B1CEE49B2B8FB20EFCDC2BB9DD7B7F75B13ACBF819369AC43D4D8A4D5D1E3E00CB20D4CEFB1FD632C252F5D8048F295A5C5BD62B83145D9135", + "cmsg1": "037ACDA3FDC4E9098EA3364B92CD6DD02654141995CFD3500E1C33D098F618458D034D3BEF06BFB2780C527B2A16CC2819160C8569EB94AF89F16B4F81C604E094170246EC025D2E096D23F52B755D1DED61C58608B23C67087E90555F52C428AFA61B0244EEC31E9105EB71FDDD1F777889003AD61D6C3116EAACF48B23F885AA617F13037B0F4758018C2AC0DC96FF1A6F6DB042B302717D1911B65B4419CAE29D79EBD9082BB8A48B5B6088DC14C4E231236B929B68DBDA2F43842D8CCD7812812EF26C6490104A19A7CF603530A4A96E5F9A52E58A22C61B2B5B93C453BB11509CEF4662DADAF9B9E9DC0E28322D2FB7F459D3F76D8739CE5E48667B9831813B90B9EB4F0825157039DE1B1C0C9DB2F52C4F51E112A113E2F436FF61CF94254B3E2A260BCEDABBF632561F24A4BECEBE1F22F9260C830E17EFB80D88EA5919C98871B78AA91A74C8F2B20F83FEB22B9599E9B416D49706181998EC04E3FDF0AAEB789F9777334881108C5C19F548963F3BF8F5E693969A30B2F749498262D8AC956E0D91EB42FB355114D3F0589AFBF10289A015EF05D1DF365BD1A0468582C07067600266D2D26EAA94E40ECCF9C6BB24C1DDE27957FE8A82F2389EA6BBFD4B265ACAD5036DEF7297E97F0D229BBB7B004AABEC90F7E4A769934376ED220FD78D79C3095C0251B5F3A899402AAE586113B18A5B2D1F12A49330AEDD5AC2314FF07EB151A9B2024461A885A565BD85A57B3047CBEE3D70C7E9C400C5FB2C0146F02944AB36C42E1715B884DD47347692CE0EEB35DDFFAF50746670244989D422CC8B64D884E0FC7764E8F01F2EA21D679CADDC011227835F29BB3B5CC3DFBAAE992C8E1310E7AB8349E55B996BAC1B39496EF23E0ED7DC95F3FF8E50711DEA5CC6D9238D71AC9441E0CDE02F944CEA3B398EDB0CEAAA373E0B9F6994FF46A854C8C86243D2CE91", + "pmsg2": "C40687CB309D263B5D52C4D782F9A66411B57627FD56FD0ABE3C87CB051CE248E19A95E68212599B8DAD04ADF7CE37DC809BBC04F27B019D5543F35223FC53E6", + "validTestCases": [ + { + "tcId": 13, + "cmsg2": "C40687CB309D263B5D52C4D782F9A66411B57627FD56FD0ABE3C87CB051CE248E19A95E68212599B8DAD04ADF7CE37DC809BBC04F27B019D5543F35223FC53E6978415455495D12CF1AC203BC4E87CC6628B0C582EDAB40DD8C9137AC8DF637BD8D2F1FA2930AEFC25B33179986E54644F3E7C2401327E5EA10394B93B2933DF8F0607BCEB003FF44C179B0C731904607CDBB40E2CD31A3BAB2D7ABCE592DB9D61CABF2104E3E3B3E0EB66104DC56ADE39741C844E03966E0C9CCB629DA1EE485AD6B8D354A6D89F067A859AB1941E403E8C72413D61E11EC49588C977831CCB37224C44A482EC53A2373F5BD2B38041797C77C85CC14AB25E4F6226FFA6CAA5", + "expectedOutput": { + "dkgOutput": { + "secshare": "9A1AA8C42D08396A54029339A5D5F689AF69D711C06E9F629ED51E415DDBA593", + "threshPk": "029D2139223BE442F3ED257971596919143AC6A21737C3742CB17C962C4DEBE73D", + "pubshares": [ + "02C5623B00B3CFCED42F7AFD45E43A34E06075B97534563934D1755FDBA8D073E1", + "0272117A2B890D887E3419E0465424EE71B647BF35A84F26D8B40427A5D4A97B43", + "03F4468332D5B10B2CEB460DE83199A8A3E5E0340CEAFAA713896DFA11521DCB6E", + "02C35519941E85AE04FD42AA98D03D4F38AFD7C5E7350B50553DD14E22CE259B68" + ] + }, + "recoveryData": "0000000202C0D0DC96E9C331610E67BD7C8C71A978477E34DAB3BE929694022C997D19338B037B0F4758018C2AC0DC96FF1A6F6DB042B302717D1911B65B4419CAE29D79EBD903AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F303AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A302A480743556B0DC66F3F25262A0120AC231DEB6936D4BF8FA37ECA83C395DC8A80266D2D26EAA94E40ECCF9C6BB24C1DDE27957FE8A82F2389EA6BBFD4B265ACAD5036DEF7297E97F0D229BBB7B004AABEC90F7E4A769934376ED220FD78D79C3095C0251B5F3A899402AAE586113B18A5B2D1F12A49330AEDD5AC2314FF07EB151A9B2024461A885A565BD85A57B3047CBEE3D70C7E9C400C5FB2C0146F02944AB36C42E1715B884DD47347692CE0EEB35DDFFAF50746670244989D422CC8B64D884E0FC7764E8F01F2EA21D679CADDC011227835F29BB3B5CC3DFBAAE992C8E1310E7AB8349E55B996BAC1B39496EF23E0ED7DC95F3FF8E50711DEA5CC6D9238D71AC9441E0CDE02F944CEA3B398EDB0CEAAA373E0B9F6994FF46A854C8C86243D2CE91C40687CB309D263B5D52C4D782F9A66411B57627FD56FD0ABE3C87CB051CE248E19A95E68212599B8DAD04ADF7CE37DC809BBC04F27B019D5543F35223FC53E6978415455495D12CF1AC203BC4E87CC6628B0C582EDAB40DD8C9137AC8DF637BD8D2F1FA2930AEFC25B33179986E54644F3E7C2401327E5EA10394B93B2933DF8F0607BCEB003FF44C179B0C731904607CDBB40E2CD31A3BAB2D7ABCE592DB9D61CABF2104E3E3B3E0EB66104DC56ADE39741C844E03966E0C9CCB629DA1EE485AD6B8D354A6D89F067A859AB1941E403E8C72413D61E11EC49588C977831CCB37224C44A482EC53A2373F5BD2B38041797C77C85CC14AB25E4F6226FFA6CAA5" + }, + "comment": "valid participant finalize" + } + ], + "errorTestCases": [ + { + "tcId": 14, + "cmsg2": "C40687CB309D263B5D52C4D782F9A66411B57627FD56FD0ABE3C87CB051CE248E19A95E68212599B8DAD04ADF7CE37DC809BBC04F27B019D5543F35223FC53E6978415455495D12CF1AC203BC4E87CC6628B0C582EDAB40DD8C9137AC8DF637BD8D2F1FA2930AEFC25B33179986E54644F3E7C2401327E5EA10394B93B2933DF8F0607BCEB003FF44C179B0C731904607CDBB40E2CD31A3BAB2D7ABCE592DB9D61CABF2104E3E3B3E0EB66104DC56ADE39741C844E03966E0C9CCB629DA1EE48", + "expectedError": { + "type": "ValueError" + }, + "comment": "invalid cmsg2: length is invalid (missing last signature)" + }, + { + "tcId": 15, + "cmsg2": "C40687CB309D263B5D52C4D782F9A66411B57627FD56FD0ABE3C87CB051CE248E19A95E68212599B8DAD04ADF7CE37DC809BBC04F27B019D5543F35223FC53E6978415455495D12CF1AC203BC4E87CC6628B0C582EDAB40DD8C9137AC8DF637BD8D2F1FA2930AEFC25B33179986E54644F3E7C2401327E5EA10394B93B2933DF8F0607BCEB003FF44C179B0C731904607CDBB40E2CD31A3BAB2D7ABCE592DB9D61CABF2104E3E3B3E0EB66104DC56ADE39741C844E03966E0C9CCB629DA1EE485AD6B8D354A6D89F067A859AB1941E403E8C72413D61E11EC49588C977831CCB37224C44A482EC53A2373F5BD2B38041797C77C85CC14AB25E4F6226FFA6CAA500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "expectedError": { + "type": "ValueError" + }, + "comment": "invalid cmsg2: length is invalid (extra data appended)" + }, + { + "tcId": 16, + "cmsg2": "C40687CB309D263B5D52C4D782F9A66411B57627FD56FD0ABE3C87CB051CE248E19A95E68212599B8DAD04ADF7CE37DC809BBC04F27B019D5543F35223FC53E6978415455495D12CF1AC203BC4E87CC6628B0C582EDAB40DD8C9137AC8DF637BD8D2F1FA2930AEFC25B33179986E54644F3E7C2401327E5EA10394B93B2933DF8F0607BCEB003FF44C179B0C731904607CDBB40E2CD31A3BAB2D7ABCE592DB9D61CABF2104E3E3B3E0EB66104DC56ADE39741C844E03966E0C9CCB629DA1EE4809C289578B96E6283AB13E4741FB489FC147FB1A5F446A314BA73C052131EFB04B83247A0BCEDF5205202AD64188B24B0BC5B51A17AEB218BD98DBE000C843B9", + "expectedError": { + "type": "FaultyCoordinatorError", + "message": "Coordinator has provided a certificate with an invalid signature" + }, + "comment": "invalid cmsg2: last signature is invalid" + } + ] + } + ] +} \ No newline at end of file diff --git a/bip-chilldkg/vectors/participant_investigate_vectors.json b/bip-chilldkg/vectors/participant_investigate_vectors.json new file mode 100644 index 0000000000..a3220b6705 --- /dev/null +++ b/bip-chilldkg/vectors/participant_investigate_vectors.json @@ -0,0 +1,266 @@ +{ + "description": [ + "Test vectors for participant_investigate(error, cinv_msg).", + "Narrows down a faulty party after participant_step2 raised UnknownFaultyParticipantOrCoordinatorError.", + "This function always raises an exception (FaultyParticipantOrCoordinatorError or FaultyCoordinatorError).", + "", + "Harness setup:", + " 1. Call participant_step1(hostseckey, params, random) to obtain (pstate1, pmsg1_out).", + " Assert pmsg1_out == pmsg1.", + " 2. Per test case: look up cmsg1 from cmsg1Pool using cmsg1Index.", + " 3. Call participant_step2(hostseckey, pstate1, cmsg1, auxRand).", + " It must raise UnknownFaultyParticipantOrCoordinatorError. Capture that error object.", + " 4. Call participant_investigate(error, cinvMsg) and verify it raises expectedError.", + "", + "All test cases are error cases (this function never returns successfully).", + "Error objects contain 'type' and optionally 'participantId' (identifier of the blamed party)." + ], + "totalTests": 16, + "testGroups": [ + { + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3" + ], + "t": 2 + }, + "hostseckey": "ADE179B2C56CB75868D44B333C16C89CB00DFDE378AD79C84D0CCE856E4F9207", + "random": "42B53D62E27380D6F7096EDA1C28C57DDB89FCD4CE5B843EDAC220E165B5A7EC", + "auxRand": "005F5C3A69BB274F4559490AD754F1F5AFFABAED4C71AD5D8ACBAEFC2B491573", + "pmsg1": "036BEE122EF60CBBB5DFC3572FE6D3A9AC0B35BB427E250F42C38F7C8229889CAB03E99E34AA2A0646563DF90F2407D0D0E345EAE2CF7F07F9F6561302F724EFA74708AE5A94BBDD3FC53924623E0D6764E089D679048F699118D7807884AD13CF484866D8210BCB76DAEB103AD6BA1CED0DBFE78F1E8A44F26FC69D5464BF23324D0260537C74BC2B79676CDE217FFA7D48236D4A309954153DEB39A31AADF6B369CCC6C144287B356EF70378A7F1054FF00354A01352587DC076554A82CD324B66A17006774A86C116C19F2C021793EAB87DF19EE3A978C60F757BA192DA23BAE5F56A3612D0720F35828FCFBA9DFEA790883B185A8B75A27449AF23169C919B47C7", + "cmsg1Pool": [ + "036BEE122EF60CBBB5DFC3572FE6D3A9AC0B35BB427E250F42C38F7C8229889CAB037C42E200B601CD439CDEAA12FB2BD8949C566E59096AA9207165C6FF0602B1D6026F714517277FB154C47E487FBE415C25C0D0F79466F1479B31A8D78319978D8A03B9C3F15E5C0E086EDE931D69BABD36167BE6CCA8705378F8849A4F00EDA2941308AE5A94BBDD3FC53924623E0D6764E089D679048F699118D7807884AD13CF484866D8210BCB76DAEB103AD6BA1CED0DBFE78F1E8A44F26FC69D5464BF23324D53EF7BE0C965DBBA2418E1FAA16FB9CA5FC990337E0E1858F263A8B88AE081D053CB4BCA7D90B63225ABA7C40F4B86B1D2E91D0EEFC52657B70695BE96FEEF3768EFCB547A2ED10F9D35C10E8FFE3A5A7F049FF7FFBF5F7400586830FBAC2B6908C2742DE20520065E89CF8CF7DBD279063967C0E1E77A97297BFDDF28B58C1A0260537C74BC2B79676CDE217FFA7D48236D4A309954153DEB39A31AADF6B369CC022E7B7DD7A0B4A72097703E60648B615A4DDDF2D715A5E0992379537A44C13AD103D1FD5A186167E5DE84B11C0F56E1FC6808D2DB35E6E3587243CF59DE605CB806E242A37A2E03838EDCB4B064425FBA5F5F8331EDDF79B46AACC70EEB714995B37B5A879577BDAF956EC9F1C234BD3942E2B2F0E372902637993C54815B69F7EAFF288C16C20D3F8D26B64E23599E6996A833E286FEAD5A7490151202E859E484", + "036BEE122EF60CBBB5DFC3572FE6D3A9AC0B35BB427E250F42C38F7C8229889CAB037C42E200B601CD439CDEAA12FB2BD8949C566E59096AA9207165C6FF0602B1D6026F714517277FB154C47E487FBE415C25C0D0F79466F1479B31A8D78319978D8A03B9C3F15E5C0E086EDE931D69BABD36167BE6CCA8705378F8849A4F00EDA2941308AE5A94BBDD3FC53924623E0D6764E089D679048F699118D7807884AD13CF484866D8210BCB76DAEB103AD6BA1CED0DBFE78F1E8A44F26FC69D5464BF23324D53EF7BE0C965DBBA2418E1FAA16FB9CA5FC990337E0E1858F263A8B88AE081D053CB4BCA7D90B63225ABA7C40F4B86B1D2E91D0EEFC52657B70695BE96FEEF3768EFCB547A2ED10F9D35C10E8FFE3A5A7F049FF7FFBF5F7400586830FBAC2B6908C2742DE20520065E89CF8CF7DBD279063967C0E1E77A97297BFDDF28B58C1A0260537C74BC2B79676CDE217FFA7D48236D4A309954153DEB39A31AADF6B369CC022E7B7DD7A0B4A72097703E60648B615A4DDDF2D715A5E0992379537A44C13AD103D1FD5A186167E5DE84B11C0F56E1FC6808D2DB35E6E3587243CF59DE605CB806E242A37A2E03838EDCB4B064425FBA5F5F8331EDDF79B46AACC70EEB714995B37B5A879577BDAF956EC9F1C234BD3942E2B2F0E372902637993C54815B69F7EAFF288C16C20D3F8D26B64E23599E6996A833E286FEAD5A7490151202E859E484" + ], + "errorTestCases": [ + { + "tcId": 1, + "cmsg1Index": 0, + "cinvMsg": "C6C144287B356EF70378A7F1054FF00354A01352587DC076554A82CD324B66A17A5C832E385A79C54BB5E400CF3FA2DF8A747E7F362AA3345CB0097DAF2C7170A124DC237A739AD28D8624726DD0277B3B1D7D030019F0FBBA9EE12D6007FEE3036D44D81C8FC25D67A6E1E125246D1FF9BE76A1658F97C07CE39940D0B43E52E303BF0AF5C64B79B08F41BCFABC9FCECC5ADDA3F12CFA6AA1154E02DA1647FE8B0702092454A24C75464D046869DCD67E790172FB1F12A149E50DAC5E6C4D8BFAAE2F", + "expectedError": { + "type": "FaultyParticipantOrCoordinatorError", + "message": "Participant sent invalid partial secshare", + "participantId": 1 + }, + "comment": "participant 1 sent an invalid secshare for participant 0" + }, + { + "tcId": 2, + "cmsg1Index": 1, + "cinvMsg": "C6C144287B356EF70378A7F1054FF00354A01352587DC076554A82CD324B66A17A5C832E385A79C54BB5E400CF3FA2DF8A747E7F362AA3345CB0097DAF2C715FA124DC237A739AD28D8624726DD0277B3B1D7D030019F0FBBA9EE12D6007FEE3036D44D81C8FC25D67A6E1E125246D1FF9BE76A1658F97C07CE39940D0B43E52E303BF0AF5C64B79B08F41BCFABC9FCECC5ADDA3F12CFA6AA1154E02DA1647FE8B0702092454A24C75464D046869DCD67E790172FB1F12A149E50DAC5E6C4D8BFAAE2F", + "expectedError": { + "type": "FaultyCoordinatorError", + "message": "Sum of encrypted partial secshares not equal to encrypted secshare" + }, + "comment": "coordinator tampered with participant 0's encrypted secshare" + }, + { + "tcId": 3, + "cmsg1Index": 1, + "cinvMsg": "C6C144287B356EF70378A7F1054FF00354A01352587DC076554A82CD324B66B27A5C832E385A79C54BB5E400CF3FA2DF8A747E7F362AA3345CB0097DAF2C715FA124DC237A739AD28D8624726DD0277B3B1D7D030019F0FBBA9EE12D6007FEE3036D44D81C8FC25D67A6E1E125246D1FF9BE76A1658F97C07CE39940D0B43E52E303BF0AF5C64B79B08F41BCFABC9FCECC5ADDA3F12CFA6AA1154E02DA1647FE8B0702092454A24C75464D046869DCD67E790172FB1F12A149E50DAC5E6C4D8BFAAE2F", + "expectedError": { + "type": "FaultyCoordinatorError", + "message": "Coordinator fiddled with the share from me to myself" + }, + "comment": "coordinator tampered with self-encrypted partial secshare (participant 0)" + }, + { + "tcId": 4, + "cmsg1Index": 1, + "cinvMsg": "C6C144287B356EF70378A7F1054FF00354A01352587DC076554A82CD324B66A17A5C832E385A79C54BB5E400CF3FA2DF8A747E7F362AA3345CB0097DAF2C715FA124DC237A739AD28D8624726DD0277B3B1D7D030019F0FBBA9EE12D6007FEE3036D44D81C8FC25D67A6E1E125246D1FF9BE76A1658F97C07CE39940D0B43E52E30260C301C1EEC41AD16BF53F55F97B7B6EB842D9E2B8139712BA54695FF711607302092454A24C75464D046869DCD67E790172FB1F12A149E50DAC5E6C4D8BFAAE2F", + "expectedError": { + "type": "FaultyCoordinatorError", + "message": "Sum of partial pubshares not equal to pubshare" + }, + "comment": "partial pubshares list in cinv_msg has an arbitrary value at index 1" + } + ] + }, + { + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3" + ], + "t": 1 + }, + "hostseckey": "ADE179B2C56CB75868D44B333C16C89CB00DFDE378AD79C84D0CCE856E4F9207", + "random": "42B53D62E27380D6F7096EDA1C28C57DDB89FCD4CE5B843EDAC220E165B5A7EC", + "auxRand": "005F5C3A69BB274F4559490AD754F1F5AFFABAED4C71AD5D8ACBAEFC2B491573", + "pmsg1": "0216C95A7FA9079C73184E8646721F59BB0361A839FD6DB9B8CF53CA1EA5B9E1C6558B3C0BFEA2180B7449D7EF101A57D8CF9FC0E32D77FAFDC712A3897007419D6CE9F8FEA08EC9935120955BDFD2519F7B29AD3E0248DA19A0E7D3900FE14CEA02BC88DD206C8AD8C1B52F93AD147695802206D0CA6AB756BD51E5FF5BE858FD219827C7BD6ECCE374663262B90B8D2944C94F9AB1E83F38DBD12823DD7D58B8B3CF1C0AB792C1AF61FA3866CCC4221CAC3CB9D35F7F5358990AB02D7E246BCEDD2AA0622F84004D5AED9B4FC855F20AE22CE260B6E79F56DE5BC2F5B73D137DE3", + "cmsg1Pool": [ + "0216C95A7FA9079C73184E8646721F59BB0361A839FD6DB9B8CF53CA1EA5B9E1C602DA601E3FC8D21A247C5677D9CACFFE0BB86860BC1C233BECA0D97776AAE309DA02FB246B4866569A7A495CFFB1133DAF9096FE4FA22EDA6AF9D7A8F236F9C222B6558B3C0BFEA2180B7449D7EF101A57D8CF9FC0E32D77FAFDC712A3897007419D6CE9F8FEA08EC9935120955BDFD2519F7B29AD3E0248DA19A0E7D3900FE14CEA832AE406B679DEE20F859D361B3C2A1A9EEED646F1B5B6761D1370889D2C0F4EFFFFB7792DD5B2EF16A87A3868280F745D4FAA4EB032695FF0E9285DB7155B838A8C833B35FF0445F94744CBDE8FFC29CBBBFD3B7BA7F3B00A56E96EFA24F6C582EA017BA6A173AA31E4E9F2F5F74D1C4ABA2BBA5B9FACE5F3042B5B2323AB8102BC88DD206C8AD8C1B52F93AD147695802206D0CA6AB756BD51E5FF5BE858FD2103F216E714F96E6AAE00E3FAAF5188FF6F66C71B932E10B17AA8403B17DE9A8F52038AA8962D7EBBF847CE551B1EF8D0D5E8A051B7FD6A29127709F35A6C18CED3007B7219CB404BC96C6D7444B2B85EB2F707B2B345CEA20633657AF21250A668169F6916FE21FE3408D26DDCBFA270CDC67821D88F0B203C46481157B5559F71CD018DADE4C6444BC59855410794924D7793C2ACB37221A3017369F49A1A4FEDF7", + "0216C95A7FA9079C73184E8646721F59BB0361A839FD6DB9B8CF53CA1EA5B9E1C602DA601E3FC8D21A247C5677D9CACFFE0BB86860BC1C233BECA0D97776AAE309DA02FB246B4866569A7A495CFFB1133DAF9096FE4FA22EDA6AF9D7A8F236F9C222B6558B3C0BFEA2180B7449D7EF101A57D8CF9FC0E32D77FAFDC712A3897007419D6CE9F8FEA08EC9935120955BDFD2519F7B29AD3E0248DA19A0E7D3900FE14CEA832AE406B679DEE20F859D361B3C2A1A9EEED646F1B5B6761D1370889D2C0F4EFFFFB7792DD5B2EF16A87A3868280F745D4FAA4EB032695FF0E9285DB7155B838A8C833B35FF0445F94744CBDE8FFC29CBBBFD3B7BA7F3B00A56E96EFA24F6C582EA017BA6A173AA31E4E9F2F5F74D1C4ABA2BBA5B9FACE5F3042B5B2323AB8102BC88DD206C8AD8C1B52F93AD147695802206D0CA6AB756BD51E5FF5BE858FD2103F216E714F96E6AAE00E3FAAF5188FF6F66C71B932E10B17AA8403B17DE9A8F52038AA8962D7EBBF847CE551B1EF8D0D5E8A051B7FD6A29127709F35A6C18CED3007B7219CB404BC96C6D7444B2B85EB2F707B2B345CEA20633657AF21250A668169F6916FE21FE3408D26DDCBFA270CDC67821D88F0B203C46481157B5559F71CD018DADE4C6444BC59855410794924D7793C2ACB37221A3017369F49A1A4FEDF7" + ], + "errorTestCases": [ + { + "tcId": 5, + "cmsg1Index": 0, + "cinvMsg": "9827C7BD6ECCE374663262B90B8D2944C94F9AB1E83F38DBD12823DD7D58B8B3F4CDA3696B4B7701D9CA898B23E0211D9A3BB64CD130C329BDFC826BCBFCA14EEE7CAEA466336EF62D77586E88F1689219851C1473C34AA555FB08E2A7BD90970216C95A7FA9079C73184E8646721F59BB0361A839FD6DB9B8CF53CA1EA5B9E1C602DA601E3FC8D21A247C5677D9CACFFE0BB86860BC1C233BECA0D97776AAE309DA02FB246B4866569A7A495CFFB1133DAF9096FE4FA22EDA6AF9D7A8F236F9C222B6", + "expectedError": { + "type": "FaultyParticipantOrCoordinatorError", + "message": "Participant sent invalid partial secshare", + "participantId": 1 + }, + "comment": "participant 1 sent an invalid secshare for participant 0" + }, + { + "tcId": 6, + "cmsg1Index": 1, + "cinvMsg": "9827C7BD6ECCE374663262B90B8D2944C94F9AB1E83F38DBD12823DD7D58B8B3F4CDA3696B4B7701D9CA898B23E0211D9A3BB64CD130C329BDFC826BCBFCA13DEE7CAEA466336EF62D77586E88F1689219851C1473C34AA555FB08E2A7BD90970216C95A7FA9079C73184E8646721F59BB0361A839FD6DB9B8CF53CA1EA5B9E1C602DA601E3FC8D21A247C5677D9CACFFE0BB86860BC1C233BECA0D97776AAE309DA02FB246B4866569A7A495CFFB1133DAF9096FE4FA22EDA6AF9D7A8F236F9C222B6", + "expectedError": { + "type": "FaultyCoordinatorError", + "message": "Sum of encrypted partial secshares not equal to encrypted secshare" + }, + "comment": "coordinator tampered with participant 0's encrypted secshare" + }, + { + "tcId": 7, + "cmsg1Index": 1, + "cinvMsg": "9827C7BD6ECCE374663262B90B8D2944C94F9AB1E83F38DBD12823DD7D58B8C4F4CDA3696B4B7701D9CA898B23E0211D9A3BB64CD130C329BDFC826BCBFCA13DEE7CAEA466336EF62D77586E88F1689219851C1473C34AA555FB08E2A7BD90970216C95A7FA9079C73184E8646721F59BB0361A839FD6DB9B8CF53CA1EA5B9E1C602DA601E3FC8D21A247C5677D9CACFFE0BB86860BC1C233BECA0D97776AAE309DA02FB246B4866569A7A495CFFB1133DAF9096FE4FA22EDA6AF9D7A8F236F9C222B6", + "expectedError": { + "type": "FaultyCoordinatorError", + "message": "Coordinator fiddled with the share from me to myself" + }, + "comment": "coordinator tampered with self-encrypted partial secshare (participant 0)" + }, + { + "tcId": 8, + "cmsg1Index": 1, + "cinvMsg": "9827C7BD6ECCE374663262B90B8D2944C94F9AB1E83F38DBD12823DD7D58B8B3F4CDA3696B4B7701D9CA898B23E0211D9A3BB64CD130C329BDFC826BCBFCA13DEE7CAEA466336EF62D77586E88F1689219851C1473C34AA555FB08E2A7BD90970216C95A7FA9079C73184E8646721F59BB0361A839FD6DB9B8CF53CA1EA5B9E1C60260C301C1EEC41AD16BF53F55F97B7B6EB842D9E2B8139712BA54695FF711607302FB246B4866569A7A495CFFB1133DAF9096FE4FA22EDA6AF9D7A8F236F9C222B6", + "expectedError": { + "type": "FaultyCoordinatorError", + "message": "Sum of partial pubshares not equal to pubshare" + }, + "comment": "partial pubshares list in cinv_msg has an arbitrary value at index 1" + } + ] + }, + { + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3" + ], + "t": 3 + }, + "hostseckey": "ADE179B2C56CB75868D44B333C16C89CB00DFDE378AD79C84D0CCE856E4F9207", + "random": "42B53D62E27380D6F7096EDA1C28C57DDB89FCD4CE5B843EDAC220E165B5A7EC", + "auxRand": "005F5C3A69BB274F4559490AD754F1F5AFFABAED4C71AD5D8ACBAEFC2B491573", + "pmsg1": "02F659A9668C1E7FB2BF4B457CA91C9925B3AB9F3F029C489C53603EF6AF90199B03BB0D7426739F2FAB24C6A0B2693273BBC92E03D3CB1A252956D372E4484F3EFE030C43B612EC5E536EF7853FCC8744BF77AF272485349484BDAA6FB6F2222FDD5A92AE219E3FE6B306490A7510AF6D9C981983DE7E4A83127F350451986EB2AF829A8C8BF483ADD195B5C7B08073D9548FFCDFB5626F0DA6A26468AD9BE54D5C83026C6EBBC55EDC86ABA62BA55A3375A940E731228F214937BF8A2FC58B29AE8F087C47A6836F30317C4B914E33F10B72CA6EDD116A3DF9E8BFE997045F9F957D31A43BB05F6162602B96E5D8C0AE3A07C8228E41DE772B0ACD96937B6CD0115ADE0ACB3150F08EA8668C262DC091C6ACA77674E8C749023FA6FB5D6938BEC73CD8", + "cmsg1Pool": [ + "02F659A9668C1E7FB2BF4B457CA91C9925B3AB9F3F029C489C53603EF6AF90199B034619FE6A2FF09FE393CACB98B0708545255926B9CB33103567AAF2EE4D23B0540250A0832ED8FECB459561A2BFE1103A29CD4B23D53B27B707376FE0E67562F20003D2BAB55842CB855189CF690BA0E2C588AEFCD985F6E6B60C3CFCBDBF42FC68F702D17C1ACB4697C8032830CFA0099BE475C8DBDC63E2CE294795C8AAAF69EF908792AE219E3FE6B306490A7510AF6D9C981983DE7E4A83127F350451986EB2AF829A8C8BF483ADD195B5C7B08073D9548FFCDFB5626F0DA6A26468AD9BE54D5C8318AB016E948B6EE152EEC9B6AD07ADC102177C6060C5FA6AB774DE136D59657C62D596F9C9477FF09A14654D48AC94B9CF381221ED27DC26B0F224C70039771FACBC3B4D52DD7A762F752250E41218F5295C3DDFCFF43350759D74A62B695352CE3C302E8718BA21C85BED769AEF3846A88E691AE945D6EBA09F51ACB0143D10026C6EBBC55EDC86ABA62BA55A3375A940E731228F214937BF8A2FC58B29AE8F080334032916F989086FB5C62722C096F0EC6A85808DB2BFA270227A72245BD7AC1B02522C52C88A6B0B1E9CF8943960E87722C1D5307069723C25B151939A58B08C10161A5552E5C1C256BA8489D1D621337C132BBECEF9840E3606582F918031F3F762B050C8F49D54C73A0F4C4D0B06DA93B81DC5463511E05913D9F2E0B5DB67D39B53A4AA164B45269B41B8F06E4254C47A55B7132E0310C626CE46DB6ABCB797", + "02F659A9668C1E7FB2BF4B457CA91C9925B3AB9F3F029C489C53603EF6AF90199B034619FE6A2FF09FE393CACB98B0708545255926B9CB33103567AAF2EE4D23B0540250A0832ED8FECB459561A2BFE1103A29CD4B23D53B27B707376FE0E67562F20003D2BAB55842CB855189CF690BA0E2C588AEFCD985F6E6B60C3CFCBDBF42FC68F702D17C1ACB4697C8032830CFA0099BE475C8DBDC63E2CE294795C8AAAF69EF908792AE219E3FE6B306490A7510AF6D9C981983DE7E4A83127F350451986EB2AF829A8C8BF483ADD195B5C7B08073D9548FFCDFB5626F0DA6A26468AD9BE54D5C8318AB016E948B6EE152EEC9B6AD07ADC102177C6060C5FA6AB774DE136D59657C62D596F9C9477FF09A14654D48AC94B9CF381221ED27DC26B0F224C70039771FACBC3B4D52DD7A762F752250E41218F5295C3DDFCFF43350759D74A62B695352CE3C302E8718BA21C85BED769AEF3846A88E691AE945D6EBA09F51ACB0143D10026C6EBBC55EDC86ABA62BA55A3375A940E731228F214937BF8A2FC58B29AE8F080334032916F989086FB5C62722C096F0EC6A85808DB2BFA270227A72245BD7AC1B02522C52C88A6B0B1E9CF8943960E87722C1D5307069723C25B151939A58B08C10161A5552E5C1C256BA8489D1D621337C132BBECEF9840E3606582F918031F3F762B050C8F49D54C73A0F4C4D0B06DA93B81DC5463511E05913D9F2E0B5DB67D39B53A4AA164B45269B41B8F06E4254C47A55B7132E0310C626CE46DB6ABCB797" + ], + "errorTestCases": [ + { + "tcId": 9, + "cmsg1Index": 0, + "cinvMsg": "7C47A6836F30317C4B914E33F10B72CA6EDD116A3DF9E8BFE997045F9F957D31B9BC513A6059135E04745D8BA961BA2B73A8F109469632CACD2DEA19DD9F986EE0165D9516387D7C6A7EDE123BB40683A6037628D3853322CF37FE31A36960DA02EB2C6840DF67A868BD870B7FB28C6166EE7175F26E1709EE6055C30BEDBED4440211DAB363110221D0DDC7A94DC8C125FADE58F7183C5C24C606572914B327D6E503FE94E345B4190A99EB1B6EE05449AE2F638456DFB603C74083387CB8F3ADCB12", + "expectedError": { + "type": "FaultyParticipantOrCoordinatorError", + "message": "Participant sent invalid partial secshare", + "participantId": 1 + }, + "comment": "participant 1 sent an invalid secshare for participant 0" + }, + { + "tcId": 10, + "cmsg1Index": 1, + "cinvMsg": "7C47A6836F30317C4B914E33F10B72CA6EDD116A3DF9E8BFE997045F9F957D31B9BC513A6059135E04745D8BA961BA2B73A8F109469632CACD2DEA19DD9F985DE0165D9516387D7C6A7EDE123BB40683A6037628D3853322CF37FE31A36960DA02EB2C6840DF67A868BD870B7FB28C6166EE7175F26E1709EE6055C30BEDBED4440211DAB363110221D0DDC7A94DC8C125FADE58F7183C5C24C606572914B327D6E503FE94E345B4190A99EB1B6EE05449AE2F638456DFB603C74083387CB8F3ADCB12", + "expectedError": { + "type": "FaultyCoordinatorError", + "message": "Sum of encrypted partial secshares not equal to encrypted secshare" + }, + "comment": "coordinator tampered with participant 0's encrypted secshare" + }, + { + "tcId": 11, + "cmsg1Index": 1, + "cinvMsg": "7C47A6836F30317C4B914E33F10B72CA6EDD116A3DF9E8BFE997045F9F957D42B9BC513A6059135E04745D8BA961BA2B73A8F109469632CACD2DEA19DD9F985DE0165D9516387D7C6A7EDE123BB40683A6037628D3853322CF37FE31A36960DA02EB2C6840DF67A868BD870B7FB28C6166EE7175F26E1709EE6055C30BEDBED4440211DAB363110221D0DDC7A94DC8C125FADE58F7183C5C24C606572914B327D6E503FE94E345B4190A99EB1B6EE05449AE2F638456DFB603C74083387CB8F3ADCB12", + "expectedError": { + "type": "FaultyCoordinatorError", + "message": "Coordinator fiddled with the share from me to myself" + }, + "comment": "coordinator tampered with self-encrypted partial secshare (participant 0)" + }, + { + "tcId": 12, + "cmsg1Index": 1, + "cinvMsg": "7C47A6836F30317C4B914E33F10B72CA6EDD116A3DF9E8BFE997045F9F957D31B9BC513A6059135E04745D8BA961BA2B73A8F109469632CACD2DEA19DD9F985DE0165D9516387D7C6A7EDE123BB40683A6037628D3853322CF37FE31A36960DA02EB2C6840DF67A868BD870B7FB28C6166EE7175F26E1709EE6055C30BEDBED4440260C301C1EEC41AD16BF53F55F97B7B6EB842D9E2B8139712BA54695FF711607303FE94E345B4190A99EB1B6EE05449AE2F638456DFB603C74083387CB8F3ADCB12", + "expectedError": { + "type": "FaultyCoordinatorError", + "message": "Sum of partial pubshares not equal to pubshare" + }, + "comment": "partial pubshares list in cinv_msg has an arbitrary value at index 1" + } + ] + }, + { + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3", + "02A480743556B0DC66F3F25262A0120AC231DEB6936D4BF8FA37ECA83C395DC8A8" + ], + "t": 2 + }, + "hostseckey": "ADE179B2C56CB75868D44B333C16C89CB00DFDE378AD79C84D0CCE856E4F9207", + "random": "42B53D62E27380D6F7096EDA1C28C57DDB89FCD4CE5B843EDAC220E165B5A7EC", + "auxRand": "005F5C3A69BB274F4559490AD754F1F5AFFABAED4C71AD5D8ACBAEFC2B491573", + "pmsg1": "037ACDA3FDC4E9098EA3364B92CD6DD02654141995CFD3500E1C33D098F618458D024F105837D6A403410B38453D712F306673B4B4DAC9F38C8FCF92A2D7BBDB2CE2082BB8A48B5B6088DC14C4E231236B929B68DBDA2F43842D8CCD7812812EF26C6490104A19A7CF603530A4A96E5F9A52E58A22C61B2B5B93C453BB11509CEF460266D2D26EAA94E40ECCF9C6BB24C1DDE27957FE8A82F2389EA6BBFD4B265ACAD5C2BC4B8326FF7015CEC59F160D74C4911E39F1C703FE64ACC5D0258A814EEAE235412ABEFA95191200CDA1F3D9F14976F2401C387A1B5FEDCEE9AE829D8DB005B6584DD5D478D2B1CEE49B2B8FB20EFCDC2BB9DD7B7F75B13ACBF819369AC43D4D8A4D5D1E3E00CB20D4CEFB1FD632C252F5D8048F295A5C5BD62B83145D9135", + "cmsg1Pool": [ + "037ACDA3FDC4E9098EA3364B92CD6DD02654141995CFD3500E1C33D098F618458D034D3BEF06BFB2780C527B2A16CC2819160C8569EB94AF89F16B4F81C604E094170246EC025D2E096D23F52B755D1DED61C58608B23C67087E90555F52C428AFA61B0244EEC31E9105EB71FDDD1F777889003AD61D6C3116EAACF48B23F885AA617F13037B0F4758018C2AC0DC96FF1A6F6DB042B302717D1911B65B4419CAE29D79EBD9082BB8A48B5B6088DC14C4E231236B929B68DBDA2F43842D8CCD7812812EF26C6490104A19A7CF603530A4A96E5F9A52E58A22C61B2B5B93C453BB11509CEF4662DADAF9B9E9DC0E28322D2FB7F459D3F76D8739CE5E48667B9831813B90B9EB4F0825157039DE1B1C0C9DB2F52C4F51E112A113E2F436FF61CF94254B3E2A260BCEDABBF632561F24A4BECEBE1F22F9260C830E17EFB80D88EA5919C98871B78AA91A74C8F2B20F83FEB22B9599E9B416D49706181998EC04E3FDF0AAEB789F9777334881108C5C19F548963F3BF8F5E693969A30B2F749498262D8AC956E0D91EB42FB355114D3F0589AFBF10289A015EF05D1DF365BD1A0468582C07067600266D2D26EAA94E40ECCF9C6BB24C1DDE27957FE8A82F2389EA6BBFD4B265ACAD5036DEF7297E97F0D229BBB7B004AABEC90F7E4A769934376ED220FD78D79C3095C0251B5F3A899402AAE586113B18A5B2D1F12A49330AEDD5AC2314FF07EB151A9B2024461A885A565BD85A57B3047CBEE3D70C7E9C400C5FB2C0146F02944AB36C42E1715B884DD47347692CE0EEB35DDFFAF50746670244989D422CC8B64D884E10D7764E8F01F2EA21D679CADDC011227835F29BB3B5CC3DFBAAE992C8E1310E7AB8349E55B996BAC1B39496EF23E0ED7DC95F3FF8E50711DEA5CC6D9238D71AC9441E0CDE02F944CEA3B398EDB0CEAAA373E0B9F6994FF46A854C8C86243D2CE91", + "037ACDA3FDC4E9098EA3364B92CD6DD02654141995CFD3500E1C33D098F618458D034D3BEF06BFB2780C527B2A16CC2819160C8569EB94AF89F16B4F81C604E094170246EC025D2E096D23F52B755D1DED61C58608B23C67087E90555F52C428AFA61B0244EEC31E9105EB71FDDD1F777889003AD61D6C3116EAACF48B23F885AA617F13037B0F4758018C2AC0DC96FF1A6F6DB042B302717D1911B65B4419CAE29D79EBD9082BB8A48B5B6088DC14C4E231236B929B68DBDA2F43842D8CCD7812812EF26C6490104A19A7CF603530A4A96E5F9A52E58A22C61B2B5B93C453BB11509CEF4662DADAF9B9E9DC0E28322D2FB7F459D3F76D8739CE5E48667B9831813B90B9EB4F0825157039DE1B1C0C9DB2F52C4F51E112A113E2F436FF61CF94254B3E2A260BCEDABBF632561F24A4BECEBE1F22F9260C830E17EFB80D88EA5919C98871B78AA91A74C8F2B20F83FEB22B9599E9B416D49706181998EC04E3FDF0AAEB789F9777334881108C5C19F548963F3BF8F5E693969A30B2F749498262D8AC956E0D91EB42FB355114D3F0589AFBF10289A015EF05D1DF365BD1A0468582C07067600266D2D26EAA94E40ECCF9C6BB24C1DDE27957FE8A82F2389EA6BBFD4B265ACAD5036DEF7297E97F0D229BBB7B004AABEC90F7E4A769934376ED220FD78D79C3095C0251B5F3A899402AAE586113B18A5B2D1F12A49330AEDD5AC2314FF07EB151A9B2024461A885A565BD85A57B3047CBEE3D70C7E9C400C5FB2C0146F02944AB36C42E1715B884DD47347692CE0EEB35DDFFAF50746670244989D422CC8B64D884E10D7764E8F01F2EA21D679CADDC011227835F29BB3B5CC3DFBAAE992C8E1310E7AB8349E55B996BAC1B39496EF23E0ED7DC95F3FF8E50711DEA5CC6D9238D71AC9441E0CDE02F944CEA3B398EDB0CEAAA373E0B9F6994FF46A854C8C86243D2CE91" + ], + "errorTestCases": [ + { + "tcId": 13, + "cmsg1Index": 0, + "cinvMsg": "C2BC4B8326FF7015CEC59F160D74C4911E39F1C703FE64ACC5D0258A814EEAE2BE24CC80C556589A0B8E155C7DA2429FD3BE69817C8E237B911EDB37605569D38BB736697045FBC5CC366FA156F25C21760942BE0ED2FE35C8AE3B24C70E83920A7D6A1780AB7000EC43EAD753D49C5A5DD08236F37B43ED82D40C97D03E8B4803AE9A37E7CABAFAFA2B0C0E1C758F754208966D0EE3AF2817E09A4BCD52E6D96302ECB1B982603A8BA43EA6D9C8E070EB917BBD40B932F28B6B8BF84B81CD2C2241039CF320073B4B23D5F155924FC598AC82C74527C8CFB64B7459A876C8F4643D100380A91AEAD1DF43C3BDE89CFEC504708560DC53DFE7BD736CBB3EA9D06654E7AB", + "expectedError": { + "type": "FaultyParticipantOrCoordinatorError", + "message": "Participant sent invalid partial secshare", + "participantId": 1 + }, + "comment": "participant 1 sent an invalid secshare for participant 0" + }, + { + "tcId": 14, + "cmsg1Index": 1, + "cinvMsg": "C2BC4B8326FF7015CEC59F160D74C4911E39F1C703FE64ACC5D0258A814EEAE2BE24CC80C556589A0B8E155C7DA2429FD3BE69817C8E237B911EDB37605569C28BB736697045FBC5CC366FA156F25C21760942BE0ED2FE35C8AE3B24C70E83920A7D6A1780AB7000EC43EAD753D49C5A5DD08236F37B43ED82D40C97D03E8B4803AE9A37E7CABAFAFA2B0C0E1C758F754208966D0EE3AF2817E09A4BCD52E6D96302ECB1B982603A8BA43EA6D9C8E070EB917BBD40B932F28B6B8BF84B81CD2C2241039CF320073B4B23D5F155924FC598AC82C74527C8CFB64B7459A876C8F4643D100380A91AEAD1DF43C3BDE89CFEC504708560DC53DFE7BD736CBB3EA9D06654E7AB", + "expectedError": { + "type": "FaultyCoordinatorError", + "message": "Sum of encrypted partial secshares not equal to encrypted secshare" + }, + "comment": "coordinator tampered with participant 0's encrypted secshare" + }, + { + "tcId": 15, + "cmsg1Index": 1, + "cinvMsg": "C2BC4B8326FF7015CEC59F160D74C4911E39F1C703FE64ACC5D0258A814EEAF3BE24CC80C556589A0B8E155C7DA2429FD3BE69817C8E237B911EDB37605569C28BB736697045FBC5CC366FA156F25C21760942BE0ED2FE35C8AE3B24C70E83920A7D6A1780AB7000EC43EAD753D49C5A5DD08236F37B43ED82D40C97D03E8B4803AE9A37E7CABAFAFA2B0C0E1C758F754208966D0EE3AF2817E09A4BCD52E6D96302ECB1B982603A8BA43EA6D9C8E070EB917BBD40B932F28B6B8BF84B81CD2C2241039CF320073B4B23D5F155924FC598AC82C74527C8CFB64B7459A876C8F4643D100380A91AEAD1DF43C3BDE89CFEC504708560DC53DFE7BD736CBB3EA9D06654E7AB", + "expectedError": { + "type": "FaultyCoordinatorError", + "message": "Coordinator fiddled with the share from me to myself" + }, + "comment": "coordinator tampered with self-encrypted partial secshare (participant 0)" + }, + { + "tcId": 16, + "cmsg1Index": 1, + "cinvMsg": "C2BC4B8326FF7015CEC59F160D74C4911E39F1C703FE64ACC5D0258A814EEAE2BE24CC80C556589A0B8E155C7DA2429FD3BE69817C8E237B911EDB37605569C28BB736697045FBC5CC366FA156F25C21760942BE0ED2FE35C8AE3B24C70E83920A7D6A1780AB7000EC43EAD753D49C5A5DD08236F37B43ED82D40C97D03E8B4803AE9A37E7CABAFAFA2B0C0E1C758F754208966D0EE3AF2817E09A4BCD52E6D9630260C301C1EEC41AD16BF53F55F97B7B6EB842D9E2B8139712BA54695FF7116073039CF320073B4B23D5F155924FC598AC82C74527C8CFB64B7459A876C8F4643D100380A91AEAD1DF43C3BDE89CFEC504708560DC53DFE7BD736CBB3EA9D06654E7AB", + "expectedError": { + "type": "FaultyCoordinatorError", + "message": "Sum of partial pubshares not equal to pubshare" + }, + "comment": "partial pubshares list in cinv_msg has an arbitrary value at index 1" + } + ] + } + ] +} \ No newline at end of file diff --git a/bip-chilldkg/vectors/participant_step1_vectors.json b/bip-chilldkg/vectors/participant_step1_vectors.json new file mode 100644 index 0000000000..3ed8fc08d4 --- /dev/null +++ b/bip-chilldkg/vectors/participant_step1_vectors.json @@ -0,0 +1,956 @@ +{ + "description": [ + "Test vectors for participant_step1(hostseckey, params, random).", + "Executes the first round of DKG from a participant's perspective.", + "Takes the participant's host secret key, session parameters, and 32 bytes of fresh randomness.", + "Returns an opaque state object and a participant message (pmsg1) to send to the coordinator.", + "", + "For each valid test case:", + " Call participant_step1(hostseckey, params, random).", + " Verify the returned pmsg1 equals expectedPmsg1.", + "", + "For each error test case:", + " Call participant_step1(hostseckey, params, random).", + " Verify it raises an exception matching expectedError." + ], + "totalTests": 52, + "testGroups": [ + { + "validTestCases": [ + { + "tcId": 1, + "hostseckey": "ADE179B2C56CB75868D44B333C16C89CB00DFDE378AD79C84D0CCE856E4F9207", + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3" + ], + "t": 2 + }, + "random": "42B53D62E27380D6F7096EDA1C28C57DDB89FCD4CE5B843EDAC220E165B5A7EC", + "expectedPmsg1": "036BEE122EF60CBBB5DFC3572FE6D3A9AC0B35BB427E250F42C38F7C8229889CAB03E99E34AA2A0646563DF90F2407D0D0E345EAE2CF7F07F9F6561302F724EFA74708AE5A94BBDD3FC53924623E0D6764E089D679048F699118D7807884AD13CF484866D8210BCB76DAEB103AD6BA1CED0DBFE78F1E8A44F26FC69D5464BF23324D0260537C74BC2B79676CDE217FFA7D48236D4A309954153DEB39A31AADF6B369CCC6C144287B356EF70378A7F1054FF00354A01352587DC076554A82CD324B66A17006774A86C116C19F2C021793EAB87DF19EE3A978C60F757BA192DA23BAE5F56A3612D0720F35828FCFBA9DFEA790883B185A8B75A27449AF23169C919B47C7", + "comment": "valid participant step1" + } + ], + "errorTestCases": [ + { + "tcId": 2, + "hostseckey": "631C047D50A67E45E27ED1FF25FCE179", + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3" + ], + "t": 2 + }, + "random": "42B53D62E27380D6F7096EDA1C28C57DDB89FCD4CE5B843EDAC220E165B5A7EC", + "expectedError": { + "type": "ValueError" + }, + "comment": "length of host secret key is not 32 bytes" + }, + { + "tcId": 3, + "hostseckey": "0000000000000000000000000000000000000000000000000000000000000000", + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3" + ], + "t": 2 + }, + "random": "42B53D62E27380D6F7096EDA1C28C57DDB89FCD4CE5B843EDAC220E165B5A7EC", + "expectedError": { + "type": "HostSeckeyError" + }, + "comment": "host secret key is zero" + }, + { + "tcId": 4, + "hostseckey": "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3" + ], + "t": 2 + }, + "random": "42B53D62E27380D6F7096EDA1C28C57DDB89FCD4CE5B843EDAC220E165B5A7EC", + "expectedError": { + "type": "HostSeckeyError" + }, + "comment": "host secret key is out of range" + }, + { + "tcId": 5, + "hostseckey": "ADE179B2C56CB75868D44B333C16C89CB00DFDE378AD79C84D0CCE856E4F9207", + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3" + ], + "t": 0 + }, + "random": "42B53D62E27380D6F7096EDA1C28C57DDB89FCD4CE5B843EDAC220E165B5A7EC", + "expectedError": { + "type": "ThresholdOrCountError" + }, + "comment": "invalid threshold value" + }, + { + "tcId": 6, + "hostseckey": "ADE179B2C56CB75868D44B333C16C89CB00DFDE378AD79C84D0CCE856E4F9207", + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3" + ], + "t": 4 + }, + "random": "42B53D62E27380D6F7096EDA1C28C57DDB89FCD4CE5B843EDAC220E165B5A7EC", + "expectedError": { + "type": "ThresholdOrCountError" + }, + "comment": "threshold exceeds the number of participants" + }, + { + "tcId": 7, + "hostseckey": "ADE179B2C56CB75868D44B333C16C89CB00DFDE378AD79C84D0CCE856E4F9207", + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "EBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEB" + ], + "t": 2 + }, + "random": "42B53D62E27380D6F7096EDA1C28C57DDB89FCD4CE5B843EDAC220E165B5A7EC", + "expectedError": { + "type": "InvalidHostPubkeyError", + "participantId": 2 + }, + "comment": "hostpubkeys list contains an invalid value with invalid prefix" + }, + { + "tcId": 8, + "hostseckey": "ADE179B2C56CB75868D44B333C16C89CB00DFDE378AD79C84D0CCE856E4F9207", + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "030000000000000000000000000000000000000000000000000000000000000005" + ], + "t": 2 + }, + "random": "42B53D62E27380D6F7096EDA1C28C57DDB89FCD4CE5B843EDAC220E165B5A7EC", + "expectedError": { + "type": "InvalidHostPubkeyError", + "participantId": 2 + }, + "comment": "hostpubkeys list contains an invalid value with invalid x-coordinate" + }, + { + "tcId": 9, + "hostseckey": "ADE179B2C56CB75868D44B333C16C89CB00DFDE378AD79C84D0CCE856E4F9207", + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "000000000000000000000000000000000000000000000000000000000000000000" + ], + "t": 2 + }, + "random": "42B53D62E27380D6F7096EDA1C28C57DDB89FCD4CE5B843EDAC220E165B5A7EC", + "expectedError": { + "type": "InvalidHostPubkeyError", + "participantId": 2 + }, + "comment": "hostpubkeys list contains an infinity point" + }, + { + "tcId": 10, + "hostseckey": "ADE179B2C56CB75868D44B333C16C89CB00DFDE378AD79C84D0CCE856E4F9207", + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3" + ], + "t": 2 + }, + "random": "42B53D62E27380D6F7096EDA1C28C57DDB89FCD4CE5B843EDAC220E165B5A7EC", + "expectedError": { + "type": "DuplicateHostPubkeyError", + "participantId1": 0, + "participantId2": 2 + }, + "comment": "hostpubkeys list contains duplicate values" + }, + { + "tcId": 11, + "hostseckey": "759DE9306FB02B3D84C455112BF1F3360401DC383ECD1FCEDE59EC809D6F9FE7", + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3" + ], + "t": 2 + }, + "random": "42B53D62E27380D6F7096EDA1C28C57DDB89FCD4CE5B843EDAC220E165B5A7EC", + "expectedError": { + "type": "HostSeckeyError", + "message": "Host secret key does not match any host public key" + }, + "comment": "host secret key doesn't match any hostpubkey" + }, + { + "tcId": 12, + "hostseckey": "ADE179B2C56CB75868D44B333C16C89CB00DFDE378AD79C84D0CCE856E4F9207", + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3" + ], + "t": 2 + }, + "random": "42B53D62E27380D6F7096EDA1C28C57D", + "expectedError": { + "type": "ValueError" + }, + "comment": "length of randomness is not 32 bytes" + }, + { + "tcId": 13, + "hostseckey": "ADE179B2C56CB75868D44B333C16C89CB00DFDE378AD79C84D0CCE856E4F9207", + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3" + ], + "t": 2 + }, + "random": "0000000000000000000000000000000000000000000000000000000000000000", + "expectedError": { + "type": "RandomnessError" + }, + "comment": "randomness is zero" + } + ] + }, + { + "validTestCases": [ + { + "tcId": 14, + "hostseckey": "ADE179B2C56CB75868D44B333C16C89CB00DFDE378AD79C84D0CCE856E4F9207", + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3" + ], + "t": 1 + }, + "random": "42B53D62E27380D6F7096EDA1C28C57DDB89FCD4CE5B843EDAC220E165B5A7EC", + "expectedPmsg1": "0216C95A7FA9079C73184E8646721F59BB0361A839FD6DB9B8CF53CA1EA5B9E1C6558B3C0BFEA2180B7449D7EF101A57D8CF9FC0E32D77FAFDC712A3897007419D6CE9F8FEA08EC9935120955BDFD2519F7B29AD3E0248DA19A0E7D3900FE14CEA02BC88DD206C8AD8C1B52F93AD147695802206D0CA6AB756BD51E5FF5BE858FD219827C7BD6ECCE374663262B90B8D2944C94F9AB1E83F38DBD12823DD7D58B8B3CF1C0AB792C1AF61FA3866CCC4221CAC3CB9D35F7F5358990AB02D7E246BCEDD2AA0622F84004D5AED9B4FC855F20AE22CE260B6E79F56DE5BC2F5B73D137DE3", + "comment": "valid participant step1" + } + ], + "errorTestCases": [ + { + "tcId": 15, + "hostseckey": "631C047D50A67E45E27ED1FF25FCE179", + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3" + ], + "t": 1 + }, + "random": "42B53D62E27380D6F7096EDA1C28C57DDB89FCD4CE5B843EDAC220E165B5A7EC", + "expectedError": { + "type": "ValueError" + }, + "comment": "length of host secret key is not 32 bytes" + }, + { + "tcId": 16, + "hostseckey": "0000000000000000000000000000000000000000000000000000000000000000", + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3" + ], + "t": 1 + }, + "random": "42B53D62E27380D6F7096EDA1C28C57DDB89FCD4CE5B843EDAC220E165B5A7EC", + "expectedError": { + "type": "HostSeckeyError" + }, + "comment": "host secret key is zero" + }, + { + "tcId": 17, + "hostseckey": "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3" + ], + "t": 1 + }, + "random": "42B53D62E27380D6F7096EDA1C28C57DDB89FCD4CE5B843EDAC220E165B5A7EC", + "expectedError": { + "type": "HostSeckeyError" + }, + "comment": "host secret key is out of range" + }, + { + "tcId": 18, + "hostseckey": "ADE179B2C56CB75868D44B333C16C89CB00DFDE378AD79C84D0CCE856E4F9207", + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3" + ], + "t": 0 + }, + "random": "42B53D62E27380D6F7096EDA1C28C57DDB89FCD4CE5B843EDAC220E165B5A7EC", + "expectedError": { + "type": "ThresholdOrCountError" + }, + "comment": "invalid threshold value" + }, + { + "tcId": 19, + "hostseckey": "ADE179B2C56CB75868D44B333C16C89CB00DFDE378AD79C84D0CCE856E4F9207", + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3" + ], + "t": 4 + }, + "random": "42B53D62E27380D6F7096EDA1C28C57DDB89FCD4CE5B843EDAC220E165B5A7EC", + "expectedError": { + "type": "ThresholdOrCountError" + }, + "comment": "threshold exceeds the number of participants" + }, + { + "tcId": 20, + "hostseckey": "ADE179B2C56CB75868D44B333C16C89CB00DFDE378AD79C84D0CCE856E4F9207", + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "EBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEB" + ], + "t": 1 + }, + "random": "42B53D62E27380D6F7096EDA1C28C57DDB89FCD4CE5B843EDAC220E165B5A7EC", + "expectedError": { + "type": "InvalidHostPubkeyError", + "participantId": 2 + }, + "comment": "hostpubkeys list contains an invalid value with invalid prefix" + }, + { + "tcId": 21, + "hostseckey": "ADE179B2C56CB75868D44B333C16C89CB00DFDE378AD79C84D0CCE856E4F9207", + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "030000000000000000000000000000000000000000000000000000000000000005" + ], + "t": 1 + }, + "random": "42B53D62E27380D6F7096EDA1C28C57DDB89FCD4CE5B843EDAC220E165B5A7EC", + "expectedError": { + "type": "InvalidHostPubkeyError", + "participantId": 2 + }, + "comment": "hostpubkeys list contains an invalid value with invalid x-coordinate" + }, + { + "tcId": 22, + "hostseckey": "ADE179B2C56CB75868D44B333C16C89CB00DFDE378AD79C84D0CCE856E4F9207", + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "000000000000000000000000000000000000000000000000000000000000000000" + ], + "t": 1 + }, + "random": "42B53D62E27380D6F7096EDA1C28C57DDB89FCD4CE5B843EDAC220E165B5A7EC", + "expectedError": { + "type": "InvalidHostPubkeyError", + "participantId": 2 + }, + "comment": "hostpubkeys list contains an infinity point" + }, + { + "tcId": 23, + "hostseckey": "ADE179B2C56CB75868D44B333C16C89CB00DFDE378AD79C84D0CCE856E4F9207", + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3" + ], + "t": 1 + }, + "random": "42B53D62E27380D6F7096EDA1C28C57DDB89FCD4CE5B843EDAC220E165B5A7EC", + "expectedError": { + "type": "DuplicateHostPubkeyError", + "participantId1": 0, + "participantId2": 2 + }, + "comment": "hostpubkeys list contains duplicate values" + }, + { + "tcId": 24, + "hostseckey": "759DE9306FB02B3D84C455112BF1F3360401DC383ECD1FCEDE59EC809D6F9FE7", + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3" + ], + "t": 1 + }, + "random": "42B53D62E27380D6F7096EDA1C28C57DDB89FCD4CE5B843EDAC220E165B5A7EC", + "expectedError": { + "type": "HostSeckeyError", + "message": "Host secret key does not match any host public key" + }, + "comment": "host secret key doesn't match any hostpubkey" + }, + { + "tcId": 25, + "hostseckey": "ADE179B2C56CB75868D44B333C16C89CB00DFDE378AD79C84D0CCE856E4F9207", + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3" + ], + "t": 1 + }, + "random": "42B53D62E27380D6F7096EDA1C28C57D", + "expectedError": { + "type": "ValueError" + }, + "comment": "length of randomness is not 32 bytes" + }, + { + "tcId": 26, + "hostseckey": "ADE179B2C56CB75868D44B333C16C89CB00DFDE378AD79C84D0CCE856E4F9207", + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3" + ], + "t": 1 + }, + "random": "0000000000000000000000000000000000000000000000000000000000000000", + "expectedError": { + "type": "RandomnessError" + }, + "comment": "randomness is zero" + } + ] + }, + { + "validTestCases": [ + { + "tcId": 27, + "hostseckey": "ADE179B2C56CB75868D44B333C16C89CB00DFDE378AD79C84D0CCE856E4F9207", + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3" + ], + "t": 3 + }, + "random": "42B53D62E27380D6F7096EDA1C28C57DDB89FCD4CE5B843EDAC220E165B5A7EC", + "expectedPmsg1": "02F659A9668C1E7FB2BF4B457CA91C9925B3AB9F3F029C489C53603EF6AF90199B03BB0D7426739F2FAB24C6A0B2693273BBC92E03D3CB1A252956D372E4484F3EFE030C43B612EC5E536EF7853FCC8744BF77AF272485349484BDAA6FB6F2222FDD5A92AE219E3FE6B306490A7510AF6D9C981983DE7E4A83127F350451986EB2AF829A8C8BF483ADD195B5C7B08073D9548FFCDFB5626F0DA6A26468AD9BE54D5C83026C6EBBC55EDC86ABA62BA55A3375A940E731228F214937BF8A2FC58B29AE8F087C47A6836F30317C4B914E33F10B72CA6EDD116A3DF9E8BFE997045F9F957D31A43BB05F6162602B96E5D8C0AE3A07C8228E41DE772B0ACD96937B6CD0115ADE0ACB3150F08EA8668C262DC091C6ACA77674E8C749023FA6FB5D6938BEC73CD8", + "comment": "valid participant step1" + } + ], + "errorTestCases": [ + { + "tcId": 28, + "hostseckey": "631C047D50A67E45E27ED1FF25FCE179", + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3" + ], + "t": 3 + }, + "random": "42B53D62E27380D6F7096EDA1C28C57DDB89FCD4CE5B843EDAC220E165B5A7EC", + "expectedError": { + "type": "ValueError" + }, + "comment": "length of host secret key is not 32 bytes" + }, + { + "tcId": 29, + "hostseckey": "0000000000000000000000000000000000000000000000000000000000000000", + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3" + ], + "t": 3 + }, + "random": "42B53D62E27380D6F7096EDA1C28C57DDB89FCD4CE5B843EDAC220E165B5A7EC", + "expectedError": { + "type": "HostSeckeyError" + }, + "comment": "host secret key is zero" + }, + { + "tcId": 30, + "hostseckey": "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3" + ], + "t": 3 + }, + "random": "42B53D62E27380D6F7096EDA1C28C57DDB89FCD4CE5B843EDAC220E165B5A7EC", + "expectedError": { + "type": "HostSeckeyError" + }, + "comment": "host secret key is out of range" + }, + { + "tcId": 31, + "hostseckey": "ADE179B2C56CB75868D44B333C16C89CB00DFDE378AD79C84D0CCE856E4F9207", + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3" + ], + "t": 0 + }, + "random": "42B53D62E27380D6F7096EDA1C28C57DDB89FCD4CE5B843EDAC220E165B5A7EC", + "expectedError": { + "type": "ThresholdOrCountError" + }, + "comment": "invalid threshold value" + }, + { + "tcId": 32, + "hostseckey": "ADE179B2C56CB75868D44B333C16C89CB00DFDE378AD79C84D0CCE856E4F9207", + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3" + ], + "t": 4 + }, + "random": "42B53D62E27380D6F7096EDA1C28C57DDB89FCD4CE5B843EDAC220E165B5A7EC", + "expectedError": { + "type": "ThresholdOrCountError" + }, + "comment": "threshold exceeds the number of participants" + }, + { + "tcId": 33, + "hostseckey": "ADE179B2C56CB75868D44B333C16C89CB00DFDE378AD79C84D0CCE856E4F9207", + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "EBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEB" + ], + "t": 3 + }, + "random": "42B53D62E27380D6F7096EDA1C28C57DDB89FCD4CE5B843EDAC220E165B5A7EC", + "expectedError": { + "type": "InvalidHostPubkeyError", + "participantId": 2 + }, + "comment": "hostpubkeys list contains an invalid value with invalid prefix" + }, + { + "tcId": 34, + "hostseckey": "ADE179B2C56CB75868D44B333C16C89CB00DFDE378AD79C84D0CCE856E4F9207", + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "030000000000000000000000000000000000000000000000000000000000000005" + ], + "t": 3 + }, + "random": "42B53D62E27380D6F7096EDA1C28C57DDB89FCD4CE5B843EDAC220E165B5A7EC", + "expectedError": { + "type": "InvalidHostPubkeyError", + "participantId": 2 + }, + "comment": "hostpubkeys list contains an invalid value with invalid x-coordinate" + }, + { + "tcId": 35, + "hostseckey": "ADE179B2C56CB75868D44B333C16C89CB00DFDE378AD79C84D0CCE856E4F9207", + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "000000000000000000000000000000000000000000000000000000000000000000" + ], + "t": 3 + }, + "random": "42B53D62E27380D6F7096EDA1C28C57DDB89FCD4CE5B843EDAC220E165B5A7EC", + "expectedError": { + "type": "InvalidHostPubkeyError", + "participantId": 2 + }, + "comment": "hostpubkeys list contains an infinity point" + }, + { + "tcId": 36, + "hostseckey": "ADE179B2C56CB75868D44B333C16C89CB00DFDE378AD79C84D0CCE856E4F9207", + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3" + ], + "t": 3 + }, + "random": "42B53D62E27380D6F7096EDA1C28C57DDB89FCD4CE5B843EDAC220E165B5A7EC", + "expectedError": { + "type": "DuplicateHostPubkeyError", + "participantId1": 0, + "participantId2": 2 + }, + "comment": "hostpubkeys list contains duplicate values" + }, + { + "tcId": 37, + "hostseckey": "759DE9306FB02B3D84C455112BF1F3360401DC383ECD1FCEDE59EC809D6F9FE7", + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3" + ], + "t": 3 + }, + "random": "42B53D62E27380D6F7096EDA1C28C57DDB89FCD4CE5B843EDAC220E165B5A7EC", + "expectedError": { + "type": "HostSeckeyError", + "message": "Host secret key does not match any host public key" + }, + "comment": "host secret key doesn't match any hostpubkey" + }, + { + "tcId": 38, + "hostseckey": "ADE179B2C56CB75868D44B333C16C89CB00DFDE378AD79C84D0CCE856E4F9207", + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3" + ], + "t": 3 + }, + "random": "42B53D62E27380D6F7096EDA1C28C57D", + "expectedError": { + "type": "ValueError" + }, + "comment": "length of randomness is not 32 bytes" + }, + { + "tcId": 39, + "hostseckey": "ADE179B2C56CB75868D44B333C16C89CB00DFDE378AD79C84D0CCE856E4F9207", + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3" + ], + "t": 3 + }, + "random": "0000000000000000000000000000000000000000000000000000000000000000", + "expectedError": { + "type": "RandomnessError" + }, + "comment": "randomness is zero" + } + ] + }, + { + "validTestCases": [ + { + "tcId": 40, + "hostseckey": "ADE179B2C56CB75868D44B333C16C89CB00DFDE378AD79C84D0CCE856E4F9207", + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3", + "02A480743556B0DC66F3F25262A0120AC231DEB6936D4BF8FA37ECA83C395DC8A8" + ], + "t": 2 + }, + "random": "42B53D62E27380D6F7096EDA1C28C57DDB89FCD4CE5B843EDAC220E165B5A7EC", + "expectedPmsg1": "037ACDA3FDC4E9098EA3364B92CD6DD02654141995CFD3500E1C33D098F618458D024F105837D6A403410B38453D712F306673B4B4DAC9F38C8FCF92A2D7BBDB2CE2082BB8A48B5B6088DC14C4E231236B929B68DBDA2F43842D8CCD7812812EF26C6490104A19A7CF603530A4A96E5F9A52E58A22C61B2B5B93C453BB11509CEF460266D2D26EAA94E40ECCF9C6BB24C1DDE27957FE8A82F2389EA6BBFD4B265ACAD5C2BC4B8326FF7015CEC59F160D74C4911E39F1C703FE64ACC5D0258A814EEAE235412ABEFA95191200CDA1F3D9F14976F2401C387A1B5FEDCEE9AE829D8DB005B6584DD5D478D2B1CEE49B2B8FB20EFCDC2BB9DD7B7F75B13ACBF819369AC43D4D8A4D5D1E3E00CB20D4CEFB1FD632C252F5D8048F295A5C5BD62B83145D9135", + "comment": "valid participant step1" + } + ], + "errorTestCases": [ + { + "tcId": 41, + "hostseckey": "631C047D50A67E45E27ED1FF25FCE179", + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3", + "02A480743556B0DC66F3F25262A0120AC231DEB6936D4BF8FA37ECA83C395DC8A8" + ], + "t": 2 + }, + "random": "42B53D62E27380D6F7096EDA1C28C57DDB89FCD4CE5B843EDAC220E165B5A7EC", + "expectedError": { + "type": "ValueError" + }, + "comment": "length of host secret key is not 32 bytes" + }, + { + "tcId": 42, + "hostseckey": "0000000000000000000000000000000000000000000000000000000000000000", + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3", + "02A480743556B0DC66F3F25262A0120AC231DEB6936D4BF8FA37ECA83C395DC8A8" + ], + "t": 2 + }, + "random": "42B53D62E27380D6F7096EDA1C28C57DDB89FCD4CE5B843EDAC220E165B5A7EC", + "expectedError": { + "type": "HostSeckeyError" + }, + "comment": "host secret key is zero" + }, + { + "tcId": 43, + "hostseckey": "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3", + "02A480743556B0DC66F3F25262A0120AC231DEB6936D4BF8FA37ECA83C395DC8A8" + ], + "t": 2 + }, + "random": "42B53D62E27380D6F7096EDA1C28C57DDB89FCD4CE5B843EDAC220E165B5A7EC", + "expectedError": { + "type": "HostSeckeyError" + }, + "comment": "host secret key is out of range" + }, + { + "tcId": 44, + "hostseckey": "ADE179B2C56CB75868D44B333C16C89CB00DFDE378AD79C84D0CCE856E4F9207", + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3", + "02A480743556B0DC66F3F25262A0120AC231DEB6936D4BF8FA37ECA83C395DC8A8" + ], + "t": 0 + }, + "random": "42B53D62E27380D6F7096EDA1C28C57DDB89FCD4CE5B843EDAC220E165B5A7EC", + "expectedError": { + "type": "ThresholdOrCountError" + }, + "comment": "invalid threshold value" + }, + { + "tcId": 45, + "hostseckey": "ADE179B2C56CB75868D44B333C16C89CB00DFDE378AD79C84D0CCE856E4F9207", + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3", + "02A480743556B0DC66F3F25262A0120AC231DEB6936D4BF8FA37ECA83C395DC8A8" + ], + "t": 5 + }, + "random": "42B53D62E27380D6F7096EDA1C28C57DDB89FCD4CE5B843EDAC220E165B5A7EC", + "expectedError": { + "type": "ThresholdOrCountError" + }, + "comment": "threshold exceeds the number of participants" + }, + { + "tcId": 46, + "hostseckey": "ADE179B2C56CB75868D44B333C16C89CB00DFDE378AD79C84D0CCE856E4F9207", + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3", + "EBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEB" + ], + "t": 2 + }, + "random": "42B53D62E27380D6F7096EDA1C28C57DDB89FCD4CE5B843EDAC220E165B5A7EC", + "expectedError": { + "type": "InvalidHostPubkeyError", + "participantId": 3 + }, + "comment": "hostpubkeys list contains an invalid value with invalid prefix" + }, + { + "tcId": 47, + "hostseckey": "ADE179B2C56CB75868D44B333C16C89CB00DFDE378AD79C84D0CCE856E4F9207", + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3", + "030000000000000000000000000000000000000000000000000000000000000005" + ], + "t": 2 + }, + "random": "42B53D62E27380D6F7096EDA1C28C57DDB89FCD4CE5B843EDAC220E165B5A7EC", + "expectedError": { + "type": "InvalidHostPubkeyError", + "participantId": 3 + }, + "comment": "hostpubkeys list contains an invalid value with invalid x-coordinate" + }, + { + "tcId": 48, + "hostseckey": "ADE179B2C56CB75868D44B333C16C89CB00DFDE378AD79C84D0CCE856E4F9207", + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3", + "000000000000000000000000000000000000000000000000000000000000000000" + ], + "t": 2 + }, + "random": "42B53D62E27380D6F7096EDA1C28C57DDB89FCD4CE5B843EDAC220E165B5A7EC", + "expectedError": { + "type": "InvalidHostPubkeyError", + "participantId": 3 + }, + "comment": "hostpubkeys list contains an infinity point" + }, + { + "tcId": 49, + "hostseckey": "ADE179B2C56CB75868D44B333C16C89CB00DFDE378AD79C84D0CCE856E4F9207", + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3", + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3" + ], + "t": 2 + }, + "random": "42B53D62E27380D6F7096EDA1C28C57DDB89FCD4CE5B843EDAC220E165B5A7EC", + "expectedError": { + "type": "DuplicateHostPubkeyError", + "participantId1": 0, + "participantId2": 3 + }, + "comment": "hostpubkeys list contains duplicate values" + }, + { + "tcId": 50, + "hostseckey": "759DE9306FB02B3D84C455112BF1F3360401DC383ECD1FCEDE59EC809D6F9FE7", + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3", + "02A480743556B0DC66F3F25262A0120AC231DEB6936D4BF8FA37ECA83C395DC8A8" + ], + "t": 2 + }, + "random": "42B53D62E27380D6F7096EDA1C28C57DDB89FCD4CE5B843EDAC220E165B5A7EC", + "expectedError": { + "type": "HostSeckeyError", + "message": "Host secret key does not match any host public key" + }, + "comment": "host secret key doesn't match any hostpubkey" + }, + { + "tcId": 51, + "hostseckey": "ADE179B2C56CB75868D44B333C16C89CB00DFDE378AD79C84D0CCE856E4F9207", + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3", + "02A480743556B0DC66F3F25262A0120AC231DEB6936D4BF8FA37ECA83C395DC8A8" + ], + "t": 2 + }, + "random": "42B53D62E27380D6F7096EDA1C28C57D", + "expectedError": { + "type": "ValueError" + }, + "comment": "length of randomness is not 32 bytes" + }, + { + "tcId": 52, + "hostseckey": "ADE179B2C56CB75868D44B333C16C89CB00DFDE378AD79C84D0CCE856E4F9207", + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3", + "02A480743556B0DC66F3F25262A0120AC231DEB6936D4BF8FA37ECA83C395DC8A8" + ], + "t": 2 + }, + "random": "0000000000000000000000000000000000000000000000000000000000000000", + "expectedError": { + "type": "RandomnessError" + }, + "comment": "randomness is zero" + } + ] + } + ] +} \ No newline at end of file diff --git a/bip-chilldkg/vectors/participant_step2_vectors.json b/bip-chilldkg/vectors/participant_step2_vectors.json new file mode 100644 index 0000000000..659f1b1f20 --- /dev/null +++ b/bip-chilldkg/vectors/participant_step2_vectors.json @@ -0,0 +1,772 @@ +{ + "description": [ + "Test vectors for participant_step2(hostseckey, pstate1, cmsg1, auxRand).", + "Executes the second round of DKG from a participant's perspective.", + "Processes the coordinator's aggregated message (cmsg1) and produces a partial signature (pmsg2).", + "", + "Harness setup (re-derive state from prior round):", + " 1. Call participant_step1(hostseckey, params, random) to obtain (pstate1, pmsg1_out).", + " 2. Assert pmsg1_out == pmsg1 (verifies your step1 implementation before testing step2).", + "", + "For each valid test case:", + " Call participant_step2(hostseckey, pstate1, cmsg1, auxRand).", + " Verify the returned pmsg2 equals expectedPmsg2.", + "", + "For each error test case:", + " Call participant_step2(hostseckey, pstate1, cmsg1, auxRand).", + " Verify it raises an exception matching expectedError.", + " Error objects contain 'type' (exception class name) and optionally:", + " - 'participantId': identifier of the blamed party (for FaultyParticipantOrCoordinatorError)", + " - 'message': human-readable description (informational, not required to match exactly)" + ], + "totalTests": 74, + "testGroups": [ + { + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3" + ], + "t": 2 + }, + "hostseckey": "ADE179B2C56CB75868D44B333C16C89CB00DFDE378AD79C84D0CCE856E4F9207", + "random": "42B53D62E27380D6F7096EDA1C28C57DDB89FCD4CE5B843EDAC220E165B5A7EC", + "auxRand": "005F5C3A69BB274F4559490AD754F1F5AFFABAED4C71AD5D8ACBAEFC2B491573", + "pmsg1": "036BEE122EF60CBBB5DFC3572FE6D3A9AC0B35BB427E250F42C38F7C8229889CAB03E99E34AA2A0646563DF90F2407D0D0E345EAE2CF7F07F9F6561302F724EFA74708AE5A94BBDD3FC53924623E0D6764E089D679048F699118D7807884AD13CF484866D8210BCB76DAEB103AD6BA1CED0DBFE78F1E8A44F26FC69D5464BF23324D0260537C74BC2B79676CDE217FFA7D48236D4A309954153DEB39A31AADF6B369CCC6C144287B356EF70378A7F1054FF00354A01352587DC076554A82CD324B66A17006774A86C116C19F2C021793EAB87DF19EE3A978C60F757BA192DA23BAE5F56A3612D0720F35828FCFBA9DFEA790883B185A8B75A27449AF23169C919B47C7", + "validTestCases": [ + { + "tcId": 1, + "cmsg1": "036BEE122EF60CBBB5DFC3572FE6D3A9AC0B35BB427E250F42C38F7C8229889CAB037C42E200B601CD439CDEAA12FB2BD8949C566E59096AA9207165C6FF0602B1D6026F714517277FB154C47E487FBE415C25C0D0F79466F1479B31A8D78319978D8A03B9C3F15E5C0E086EDE931D69BABD36167BE6CCA8705378F8849A4F00EDA2941308AE5A94BBDD3FC53924623E0D6764E089D679048F699118D7807884AD13CF484866D8210BCB76DAEB103AD6BA1CED0DBFE78F1E8A44F26FC69D5464BF23324D53EF7BE0C965DBBA2418E1FAA16FB9CA5FC990337E0E1858F263A8B88AE081D053CB4BCA7D90B63225ABA7C40F4B86B1D2E91D0EEFC52657B70695BE96FEEF3768EFCB547A2ED10F9D35C10E8FFE3A5A7F049FF7FFBF5F7400586830FBAC2B6908C2742DE20520065E89CF8CF7DBD279063967C0E1E77A97297BFDDF28B58C1A0260537C74BC2B79676CDE217FFA7D48236D4A309954153DEB39A31AADF6B369CC022E7B7DD7A0B4A72097703E60648B615A4DDDF2D715A5E0992379537A44C13AD103D1FD5A186167E5DE84B11C0F56E1FC6808D2DB35E6E3587243CF59DE605CB806E242A37A2E03838EDCB4B064425FBA5F5F8331EDDF79B46AACC70EEB714995A27B5A879577BDAF956EC9F1C234BD3942E2B2F0E372902637993C54815B69F7EAFF288C16C20D3F8D26B64E23599E6996A833E286FEAD5A7490151202E859E484", + "expectedPmsg2": "A6C2DE535556E47C8F3427B9716CFC4A66ADD0F2788AB793E2FBFF7D81DA06315C3F62CAA2B85E369922E68A94113EB839B5E3E424A4F4BF2E8D2908F517E8BE", + "comment": "valid participant step2" + } + ], + "errorTestCases": [ + { + "tcId": 2, + "auxRand": "42B53D62E27380D6F7096EDA1C28C57D", + "cmsg1": "036BEE122EF60CBBB5DFC3572FE6D3A9AC0B35BB427E250F42C38F7C8229889CAB037C42E200B601CD439CDEAA12FB2BD8949C566E59096AA9207165C6FF0602B1D6026F714517277FB154C47E487FBE415C25C0D0F79466F1479B31A8D78319978D8A03B9C3F15E5C0E086EDE931D69BABD36167BE6CCA8705378F8849A4F00EDA2941308AE5A94BBDD3FC53924623E0D6764E089D679048F699118D7807884AD13CF484866D8210BCB76DAEB103AD6BA1CED0DBFE78F1E8A44F26FC69D5464BF23324D53EF7BE0C965DBBA2418E1FAA16FB9CA5FC990337E0E1858F263A8B88AE081D053CB4BCA7D90B63225ABA7C40F4B86B1D2E91D0EEFC52657B70695BE96FEEF3768EFCB547A2ED10F9D35C10E8FFE3A5A7F049FF7FFBF5F7400586830FBAC2B6908C2742DE20520065E89CF8CF7DBD279063967C0E1E77A97297BFDDF28B58C1A0260537C74BC2B79676CDE217FFA7D48236D4A309954153DEB39A31AADF6B369CC022E7B7DD7A0B4A72097703E60648B615A4DDDF2D715A5E0992379537A44C13AD103D1FD5A186167E5DE84B11C0F56E1FC6808D2DB35E6E3587243CF59DE605CB806E242A37A2E03838EDCB4B064425FBA5F5F8331EDDF79B46AACC70EEB714995A27B5A879577BDAF956EC9F1C234BD3942E2B2F0E372902637993C54815B69F7EAFF288C16C20D3F8D26B64E23599E6996A833E286FEAD5A7490151202E859E484", + "expectedError": { + "type": "ValueError" + }, + "comment": "length of aux randomness is not 32 bytes" + }, + { + "tcId": 3, + "hostseckey": "94BB10C1DE15783C3F3E49167A0951CACD2803F13AAC456C816E88AB4AC76330", + "cmsg1": "036BEE122EF60CBBB5DFC3572FE6D3A9AC0B35BB427E250F42C38F7C8229889CAB037C42E200B601CD439CDEAA12FB2BD8949C566E59096AA9207165C6FF0602B1D6026F714517277FB154C47E487FBE415C25C0D0F79466F1479B31A8D78319978D8A03B9C3F15E5C0E086EDE931D69BABD36167BE6CCA8705378F8849A4F00EDA2941308AE5A94BBDD3FC53924623E0D6764E089D679048F699118D7807884AD13CF484866D8210BCB76DAEB103AD6BA1CED0DBFE78F1E8A44F26FC69D5464BF23324D53EF7BE0C965DBBA2418E1FAA16FB9CA5FC990337E0E1858F263A8B88AE081D053CB4BCA7D90B63225ABA7C40F4B86B1D2E91D0EEFC52657B70695BE96FEEF3768EFCB547A2ED10F9D35C10E8FFE3A5A7F049FF7FFBF5F7400586830FBAC2B6908C2742DE20520065E89CF8CF7DBD279063967C0E1E77A97297BFDDF28B58C1A0260537C74BC2B79676CDE217FFA7D48236D4A309954153DEB39A31AADF6B369CC022E7B7DD7A0B4A72097703E60648B615A4DDDF2D715A5E0992379537A44C13AD103D1FD5A186167E5DE84B11C0F56E1FC6808D2DB35E6E3587243CF59DE605CB806E242A37A2E03838EDCB4B064425FBA5F5F8331EDDF79B46AACC70EEB714995A27B5A879577BDAF956EC9F1C234BD3942E2B2F0E372902637993C54815B69F7EAFF288C16C20D3F8D26B64E23599E6996A833E286FEAD5A7490151202E859E484", + "expectedError": { + "type": "HostSeckeyError", + "message": "Host secret key does not match the one used in participant_step1" + }, + "comment": "hostseckey does not match the one used in participant_step1" + }, + { + "tcId": 4, + "cmsg1": "036BEE122EF60CBBB5DFC3572FE6D3A9AC0B35BB427E250F42C38F7C8229889CAB037C42E200B601CD439CDEAA12FB2BD8949C566E59096AA9207165C6FF0602B1D6026F714517277FB154C47E487FBE415C25C0D0F79466F1479B31A8D78319978D8A03B9C3F15E5C0E086EDE931D69BABD36167BE6CCA8705378F8849A4F00EDA2941308AE5A94BBDD3FC53924623E0D6764E089D679048F699118D7807884AD13CF484866D8210BCB76DAEB103AD6BA1CED0DBFE78F1E8A44F26FC69D5464BF23324D53EF7BE0C965DBBA2418E1FAA16FB9CA5FC990337E0E1858F263A8B88AE081D053CB4BCA7D90B63225ABA7C40F4B86B1D2E91D0EEFC52657B70695BE96FEEF3768EFCB547A2ED10F9D35C10E8FFE3A5A7F049FF7FFBF5F7400586830FBAC2B6908C2742DE20520065E89CF8CF7DBD279063967C0E1E77A97297BFDDF28B58C1AEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEB022E7B7DD7A0B4A72097703E60648B615A4DDDF2D715A5E0992379537A44C13AD103D1FD5A186167E5DE84B11C0F56E1FC6808D2DB35E6E3587243CF59DE605CB806E242A37A2E03838EDCB4B064425FBA5F5F8331EDDF79B46AACC70EEB714995A27B5A879577BDAF956EC9F1C234BD3942E2B2F0E372902637993C54815B69F7EAFF288C16C20D3F8D26B64E23599E6996A833E286FEAD5A7490151202E859E484", + "expectedError": { + "type": "FaultyCoordinatorError", + "message": "Coordinator replied with wrong pubnonce" + }, + "comment": "invalid cmsg1: pubnonces list has an invalid prefix at index 0 (own pubnonce)" + }, + { + "tcId": 5, + "cmsg1": "036BEE122EF60CBBB5DFC3572FE6D3A9AC0B35BB427E250F42C38F7C8229889CAB037C42E200B601CD439CDEAA12FB2BD8949C566E59096AA9207165C6FF0602B1D6026F714517277FB154C47E487FBE415C25C0D0F79466F1479B31A8D78319978D8A03B9C3F15E5C0E086EDE931D69BABD36167BE6CCA8705378F8849A4F00EDA2941308AE5A94BBDD3FC53924623E0D6764E089D679048F699118D7807884AD13CF484866D8210BCB76DAEB103AD6BA1CED0DBFE78F1E8A44F26FC69D5464BF23324D53EF7BE0C965DBBA2418E1FAA16FB9CA5FC990337E0E1858F263A8B88AE081D053CB4BCA7D90B63225ABA7C40F4B86B1D2E91D0EEFC52657B70695BE96FEEF3768EFCB547A2ED10F9D35C10E8FFE3A5A7F049FF7FFBF5F7400586830FBAC2B6908C2742DE20520065E89CF8CF7DBD279063967C0E1E77A97297BFDDF28B58C1A0260537C74BC2B79676CDE217FFA7D48236D4A309954153DEB39A31AADF6B369CCEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEB03D1FD5A186167E5DE84B11C0F56E1FC6808D2DB35E6E3587243CF59DE605CB806E242A37A2E03838EDCB4B064425FBA5F5F8331EDDF79B46AACC70EEB714995A27B5A879577BDAF956EC9F1C234BD3942E2B2F0E372902637993C54815B69F7EAFF288C16C20D3F8D26B64E23599E6996A833E286FEAD5A7490151202E859E484", + "expectedError": { + "type": "FaultyParticipantOrCoordinatorError", + "message": "invalid public nonce", + "participantId": 1 + }, + "comment": "invalid cmsg1: pubnonces list has an invalid prefix at index 1" + }, + { + "tcId": 6, + "cmsg1": "036BEE122EF60CBBB5DFC3572FE6D3A9AC0B35BB427E250F42C38F7C8229889CAB037C42E200B601CD439CDEAA12FB2BD8949C566E59096AA9207165C6FF0602B1D6026F714517277FB154C47E487FBE415C25C0D0F79466F1479B31A8D78319978D8A03B9C3F15E5C0E086EDE931D69BABD36167BE6CCA8705378F8849A4F00EDA2941308AE5A94BBDD3FC53924623E0D6764E089D679048F699118D7807884AD13CF484866D8210BCB76DAEB103AD6BA1CED0DBFE78F1E8A44F26FC69D5464BF23324D53EF7BE0C965DBBA2418E1FAA16FB9CA5FC990337E0E1858F263A8B88AE081D053CB4BCA7D90B63225ABA7C40F4B86B1D2E91D0EEFC52657B70695BE96FEEF3768EFCB547A2ED10F9D35C10E8FFE3A5A7F049FF7FFBF5F7400586830FBAC2B6908C2742DE20520065E89CF8CF7DBD279063967C0E1E77A97297BFDDF28B58C1A030000000000000000000000000000000000000000000000000000000000000005022E7B7DD7A0B4A72097703E60648B615A4DDDF2D715A5E0992379537A44C13AD103D1FD5A186167E5DE84B11C0F56E1FC6808D2DB35E6E3587243CF59DE605CB806E242A37A2E03838EDCB4B064425FBA5F5F8331EDDF79B46AACC70EEB714995A27B5A879577BDAF956EC9F1C234BD3942E2B2F0E372902637993C54815B69F7EAFF288C16C20D3F8D26B64E23599E6996A833E286FEAD5A7490151202E859E484", + "expectedError": { + "type": "FaultyCoordinatorError", + "message": "Coordinator replied with wrong pubnonce" + }, + "comment": "invalid cmsg1: pubnonces list has an off-curve x-coordinate at index 0 (own pubnonce)" + }, + { + "tcId": 7, + "cmsg1": "036BEE122EF60CBBB5DFC3572FE6D3A9AC0B35BB427E250F42C38F7C8229889CAB037C42E200B601CD439CDEAA12FB2BD8949C566E59096AA9207165C6FF0602B1D6026F714517277FB154C47E487FBE415C25C0D0F79466F1479B31A8D78319978D8A03B9C3F15E5C0E086EDE931D69BABD36167BE6CCA8705378F8849A4F00EDA2941308AE5A94BBDD3FC53924623E0D6764E089D679048F699118D7807884AD13CF484866D8210BCB76DAEB103AD6BA1CED0DBFE78F1E8A44F26FC69D5464BF23324D53EF7BE0C965DBBA2418E1FAA16FB9CA5FC990337E0E1858F263A8B88AE081D053CB4BCA7D90B63225ABA7C40F4B86B1D2E91D0EEFC52657B70695BE96FEEF3768EFCB547A2ED10F9D35C10E8FFE3A5A7F049FF7FFBF5F7400586830FBAC2B6908C2742DE20520065E89CF8CF7DBD279063967C0E1E77A97297BFDDF28B58C1A0260537C74BC2B79676CDE217FFA7D48236D4A309954153DEB39A31AADF6B369CC03000000000000000000000000000000000000000000000000000000000000000503D1FD5A186167E5DE84B11C0F56E1FC6808D2DB35E6E3587243CF59DE605CB806E242A37A2E03838EDCB4B064425FBA5F5F8331EDDF79B46AACC70EEB714995A27B5A879577BDAF956EC9F1C234BD3942E2B2F0E372902637993C54815B69F7EAFF288C16C20D3F8D26B64E23599E6996A833E286FEAD5A7490151202E859E484", + "expectedError": { + "type": "FaultyParticipantOrCoordinatorError", + "message": "invalid public nonce", + "participantId": 1 + }, + "comment": "invalid cmsg1: pubnonces list has an off-curve x-coordinate at index 1" + }, + { + "tcId": 8, + "cmsg1": "036BEE122EF60CBBB5DFC3572FE6D3A9AC0B35BB427E250F42C38F7C8229889CAB037C42E200B601CD439CDEAA12FB2BD8949C566E59096AA9207165C6FF0602B1D6026F714517277FB154C47E487FBE415C25C0D0F79466F1479B31A8D78319978D8A03B9C3F15E5C0E086EDE931D69BABD36167BE6CCA8705378F8849A4F00EDA2941308AE5A94BBDD3FC53924623E0D6764E089D679048F699118D7807884AD13CF484866D8210BCB76DAEB103AD6BA1CED0DBFE78F1E8A44F26FC69D5464BF23324D53EF7BE0C965DBBA2418E1FAA16FB9CA5FC990337E0E1858F263A8B88AE081D053CB4BCA7D90B63225ABA7C40F4B86B1D2E91D0EEFC52657B70695BE96FEEF3768EFCB547A2ED10F9D35C10E8FFE3A5A7F049FF7FFBF5F7400586830FBAC2B6908C2742DE20520065E89CF8CF7DBD279063967C0E1E77A97297BFDDF28B58C1A0260C301C1EEC41AD16BF53F55F97B7B6EB842D9E2B8139712BA54695FF7116073022E7B7DD7A0B4A72097703E60648B615A4DDDF2D715A5E0992379537A44C13AD103D1FD5A186167E5DE84B11C0F56E1FC6808D2DB35E6E3587243CF59DE605CB806E242A37A2E03838EDCB4B064425FBA5F5F8331EDDF79B46AACC70EEB714995A27B5A879577BDAF956EC9F1C234BD3942E2B2F0E372902637993C54815B69F7EAFF288C16C20D3F8D26B64E23599E6996A833E286FEAD5A7490151202E859E484", + "expectedError": { + "type": "FaultyCoordinatorError", + "message": "Coordinator replied with wrong pubnonce" + }, + "comment": "invalid cmsg1: pubnonces list has an arbitrary value at index 0 (own pubnonce)" + }, + { + "tcId": 9, + "cmsg1": "036BEE122EF60CBBB5DFC3572FE6D3A9AC0B35BB427E250F42C38F7C8229889CAB037C42E200B601CD439CDEAA12FB2BD8949C566E59096AA9207165C6FF0602B1D6026F714517277FB154C47E487FBE415C25C0D0F79466F1479B31A8D78319978D8A03B9C3F15E5C0E086EDE931D69BABD36167BE6CCA8705378F8849A4F00EDA2941308AE5A94BBDD3FC53924623E0D6764E089D679048F699118D7807884AD13CF484866D8210BCB76DAEB103AD6BA1CED0DBFE78F1E8A44F26FC69D5464BF23324D53EF7BE0C965DBBA2418E1FAA16FB9CA5FC990337E0E1858F263A8B88AE081D053CB4BCA7D90B63225ABA7C40F4B86B1D2E91D0EEFC52657B70695BE96FEEF3768EFCB547A2ED10F9D35C10E8FFE3A5A7F049FF7FFBF5F7400586830FBAC2B6908C2742DE20520065E89CF8CF7DBD279063967C0E1E77A97297BFDDF28B58C1A0260537C74BC2B79676CDE217FFA7D48236D4A309954153DEB39A31AADF6B369CC0260C301C1EEC41AD16BF53F55F97B7B6EB842D9E2B8139712BA54695FF711607303D1FD5A186167E5DE84B11C0F56E1FC6808D2DB35E6E3587243CF59DE605CB806E242A37A2E03838EDCB4B064425FBA5F5F8331EDDF79B46AACC70EEB714995A27B5A879577BDAF956EC9F1C234BD3942E2B2F0E372902637993C54815B69F7EAFF288C16C20D3F8D26B64E23599E6996A833E286FEAD5A7490151202E859E484", + "expectedError": { + "type": "UnknownFaultyParticipantOrCoordinatorError", + "message": "Received invalid secshare; consider using participant_investigate() to determine a faulty party" + }, + "comment": "invalid cmsg1: pubnonces list has an arbitrary value at index 1" + }, + { + "tcId": 10, + "cmsg1": "036BEE122EF60CBBB5DFC3572FE6D3A9AC0B35BB427E250F42C38F7C8229889CAB037C42E200B601CD439CDEAA12FB2BD8949C566E59096AA9207165C6FF0602B1D6026F714517277FB154C47E487FBE415C25C0D0F79466F1479B31A8D78319978D8A03B9C3F15E5C0E086EDE931D69BABD36167BE6CCA8705378F8849A4F00EDA2941308AE5A94BBDD3FC53924623E0D6764E089D679048F699118D7807884AD13CF484866D8210BCB76DAEB103AD6BA1CED0DBFE78F1E8A44F26FC69D5464BF23324D53EF7BE0C965DBBA2418E1FAA16FB9CA5FC990337E0E1858F263A8B88AE081D053CB4BCA7D90B63225ABA7C40F4B86B1D2E91D0EEFC52657B70695BE96FEEF3768EFCB547A2ED10F9D35C10E8FFE3A5A7F049FF7FFBF5F7400586830FBAC2B6908C2742DE20520065E89CF8CF7DBD279063967C0E1E77A97297BFDDF28B58C1A000000000000000000000000000000000000000000000000000000000000000000022E7B7DD7A0B4A72097703E60648B615A4DDDF2D715A5E0992379537A44C13AD103D1FD5A186167E5DE84B11C0F56E1FC6808D2DB35E6E3587243CF59DE605CB806E242A37A2E03838EDCB4B064425FBA5F5F8331EDDF79B46AACC70EEB714995A27B5A879577BDAF956EC9F1C234BD3942E2B2F0E372902637993C54815B69F7EAFF288C16C20D3F8D26B64E23599E6996A833E286FEAD5A7490151202E859E484", + "expectedError": { + "type": "FaultyCoordinatorError", + "message": "Coordinator replied with wrong pubnonce" + }, + "comment": "invalid cmsg1: pubnonces list has the infinity point at index 0 (own pubnonce)" + }, + { + "tcId": 11, + "cmsg1": "036BEE122EF60CBBB5DFC3572FE6D3A9AC0B35BB427E250F42C38F7C8229889CAB037C42E200B601CD439CDEAA12FB2BD8949C566E59096AA9207165C6FF0602B1D6026F714517277FB154C47E487FBE415C25C0D0F79466F1479B31A8D78319978D8A03B9C3F15E5C0E086EDE931D69BABD36167BE6CCA8705378F8849A4F00EDA2941308AE5A94BBDD3FC53924623E0D6764E089D679048F699118D7807884AD13CF484866D8210BCB76DAEB103AD6BA1CED0DBFE78F1E8A44F26FC69D5464BF23324D53EF7BE0C965DBBA2418E1FAA16FB9CA5FC990337E0E1858F263A8B88AE081D053CB4BCA7D90B63225ABA7C40F4B86B1D2E91D0EEFC52657B70695BE96FEEF3768EFCB547A2ED10F9D35C10E8FFE3A5A7F049FF7FFBF5F7400586830FBAC2B6908C2742DE20520065E89CF8CF7DBD279063967C0E1E77A97297BFDDF28B58C1A0260537C74BC2B79676CDE217FFA7D48236D4A309954153DEB39A31AADF6B369CC00000000000000000000000000000000000000000000000000000000000000000003D1FD5A186167E5DE84B11C0F56E1FC6808D2DB35E6E3587243CF59DE605CB806E242A37A2E03838EDCB4B064425FBA5F5F8331EDDF79B46AACC70EEB714995A27B5A879577BDAF956EC9F1C234BD3942E2B2F0E372902637993C54815B69F7EAFF288C16C20D3F8D26B64E23599E6996A833E286FEAD5A7490151202E859E484", + "expectedError": { + "type": "FaultyParticipantOrCoordinatorError", + "message": "invalid public nonce", + "participantId": 1 + }, + "comment": "invalid cmsg1: pubnonces list has the infinity point at index 1" + }, + { + "tcId": 12, + "cmsg1": "036BEE122EF60CBBB5DFC3572FE6D3A9AC0B35BB427E250F42C38F7C8229889CAB037C42E200B601CD439CDEAA12FB2BD8949C566E59096AA9207165C6FF0602B1D6026F714517277FB154C47E487FBE415C25C0D0F79466F1479B31A8D78319978D8A03B9C3F15E5C0E086EDE931D69BABD36167BE6CCA8705378F8849A4F00EDA2941308AE5A94BBDD3FC53924623E0D6764E089D679048F699118D7807884AD13CF484866D8210BCB76DAEB103AD6BA1CED0DBFE78F1E8A44F26FC69D5464BF23324D53EF7BE0C965DBBA2418E1FAA16FB9CA5FC990337E0E1858F263A8B88AE081D053CB4BCA7D90B63225ABA7C40F4B86B1D2E91D0EEFC52657B70695BE96FEEF3768EFCB547A2ED10F9D35C10E8FFE3A5A7F049FF7FFBF5F7400586830FBAC2B6908C2742DE20520065E89CF8CF7DBD279063967C0E1E77A97297BFDDF28B58C1A0260537C74BC2B79676CDE217FFA7D48236D4A309954153DEB39A31AADF6B369CC0260537C74BC2B79676CDE217FFA7D48236D4A309954153DEB39A31AADF6B369CC03D1FD5A186167E5DE84B11C0F56E1FC6808D2DB35E6E3587243CF59DE605CB806E242A37A2E03838EDCB4B064425FBA5F5F8331EDDF79B46AACC70EEB714995A27B5A879577BDAF956EC9F1C234BD3942E2B2F0E372902637993C54815B69F7EAFF288C16C20D3F8D26B64E23599E6996A833E286FEAD5A7490151202E859E484", + "expectedError": { + "type": "UnknownFaultyParticipantOrCoordinatorError", + "message": "Received invalid secshare; consider using participant_investigate() to determine a faulty party" + }, + "comment": "invalid cmsg1: pubnonces list has duplicate values" + }, + { + "tcId": 13, + "cmsg1": "036BEE122EF60CBBB5DFC3572FE6D3A9AC0B35BB427E250F42C38F7C8229889CAB037C42E200B601CD439CDEAA12FB2BD8949C566E59096AA9207165C6FF0602B1D6026F714517277FB154C47E487FBE415C25C0D0F79466F1479B31A8D78319978D8A03B9C3F15E5C0E086EDE931D69BABD36167BE6CCA8705378F8849A4F00EDA2941308AE5A94BBDD3FC53924623E0D6764E089D679048F699118D7807884AD13CF484866D8210BCB76DAEB103AD6BA1CED0DBFE78F1E8A44F26FC69D5464BF23324D53EF7BE0C965DBBA2418E1FAA16FB9CA5FC990337E0E1858F263A8B88AE081D053CB4BCA7D90B63225ABA7C40F4B86B1D2E91D0EEFC52657B70695BE96FEEF3768EFCB547A2ED10F9D35C10E8FFE3A5A7F049FF7FFBF5F7400586830FBAC2B6908C2742DE20520065E89CF8CF7DBD279063967C0E1E77A97297BFDDF28B58C1A0260537C74BC2B79676CDE217FFA7D48236D4A309954153DEB39A31AADF6B369CC022E7B7DD7A0B4A72097703E60648B615A4DDDF2D715A5E0992379537A44C13AD103D1FD5A186167E5DE84B11C0F56E1FC6808D2DB35E6E3587243CF59DE605CB806E242A37A2E03838EDCB4B064425FBA5F5F8331EDDF79B46AACC70EEB714995A27B5A879577BDAF956EC9F1C234BD3942E2B2F0E372902637993C54815B69F7EAFF288C16C20D3F8D26B64E23599E6996A833E286FEAD5A7490151202E859E4", + "expectedError": { + "type": "ValueError" + }, + "comment": "invalid cmsg1: missing encrypted secret shares" + }, + { + "tcId": 14, + "cmsg1": "0260C301C1EEC41AD16BF53F55F97B7B6EB842D9E2B8139712BA54695FF7116073037C42E200B601CD439CDEAA12FB2BD8949C566E59096AA9207165C6FF0602B1D6026F714517277FB154C47E487FBE415C25C0D0F79466F1479B31A8D78319978D8A03B9C3F15E5C0E086EDE931D69BABD36167BE6CCA8705378F8849A4F00EDA2941308AE5A94BBDD3FC53924623E0D6764E089D679048F699118D7807884AD13CF484866D8210BCB76DAEB103AD6BA1CED0DBFE78F1E8A44F26FC69D5464BF23324D53EF7BE0C965DBBA2418E1FAA16FB9CA5FC990337E0E1858F263A8B88AE081D053CB4BCA7D90B63225ABA7C40F4B86B1D2E91D0EEFC52657B70695BE96FEEF3768EFCB547A2ED10F9D35C10E8FFE3A5A7F049FF7FFBF5F7400586830FBAC2B6908C2742DE20520065E89CF8CF7DBD279063967C0E1E77A97297BFDDF28B58C1A0260537C74BC2B79676CDE217FFA7D48236D4A309954153DEB39A31AADF6B369CC022E7B7DD7A0B4A72097703E60648B615A4DDDF2D715A5E0992379537A44C13AD103D1FD5A186167E5DE84B11C0F56E1FC6808D2DB35E6E3587243CF59DE605CB806E242A37A2E03838EDCB4B064425FBA5F5F8331EDDF79B46AACC70EEB714995A27B5A879577BDAF956EC9F1C234BD3942E2B2F0E372902637993C54815B69F7EAFF288C16C20D3F8D26B64E23599E6996A833E286FEAD5A7490151202E859E484", + "expectedError": { + "type": "FaultyCoordinatorError", + "message": "Coordinator sent unexpected first group element for local participant id" + }, + "comment": "invalid cmsg1: coms_to_secrets list has an arbitrary value at index 0" + }, + { + "tcId": 15, + "cmsg1": "036BEE122EF60CBBB5DFC3572FE6D3A9AC0B35BB427E250F42C38F7C8229889CAB000000000000000000000000000000000000000000000000000000000000000000026F714517277FB154C47E487FBE415C25C0D0F79466F1479B31A8D78319978D8A03B9C3F15E5C0E086EDE931D69BABD36167BE6CCA8705378F8849A4F00EDA2941308AE5A94BBDD3FC53924623E0D6764E089D679048F699118D7807884AD13CF484866D8210BCB76DAEB103AD6BA1CED0DBFE78F1E8A44F26FC69D5464BF23324D53EF7BE0C965DBBA2418E1FAA16FB9CA5FC990337E0E1858F263A8B88AE081D053CB4BCA7D90B63225ABA7C40F4B86B1D2E91D0EEFC52657B70695BE96FEEF3768EFCB547A2ED10F9D35C10E8FFE3A5A7F049FF7FFBF5F7400586830FBAC2B6908C2742DE20520065E89CF8CF7DBD279063967C0E1E77A97297BFDDF28B58C1A0260537C74BC2B79676CDE217FFA7D48236D4A309954153DEB39A31AADF6B369CC022E7B7DD7A0B4A72097703E60648B615A4DDDF2D715A5E0992379537A44C13AD103D1FD5A186167E5DE84B11C0F56E1FC6808D2DB35E6E3587243CF59DE605CB806E242A37A2E03838EDCB4B064425FBA5F5F8331EDDF79B46AACC70EEB714995A27B5A879577BDAF956EC9F1C234BD3942E2B2F0E372902637993C54815B69F7EAFF288C16C20D3F8D26B64E23599E6996A833E286FEAD5A7490151202E859E484", + "expectedError": { + "type": "FaultyParticipantOrCoordinatorError", + "message": "Participant sent invalid commitment", + "participantId": 1 + }, + "comment": "invalid cmsg1: coms_to_secrets list has infinity at index 1" + }, + { + "tcId": 16, + "cmsg1": "036BEE122EF60CBBB5DFC3572FE6D3A9AC0B35BB427E250F42C38F7C8229889CAB037C42E200B601CD439CDEAA12FB2BD8949C566E59096AA9207165C6FF0602B1D6026F714517277FB154C47E487FBE415C25C0D0F79466F1479B31A8D78319978D8A03B9C3F15E5C0E086EDE931D69BABD36167BE6CCA8705378F8849A4F00EDA2941308AE5A94BBDD3FC53924623E0D6764E089D679048F699118D7807884AD13CF484866D8210BCB76DAEB103AD6BA1CED0DBFE78F1E8A44F26FC69D5464BF23324D09C289578B96E6283AB13E4741FB489FC147FB1A5F446A314BA73C052131EFB04B83247A0BCEDF5205202AD64188B24B0BC5B51A17AEB218BD98DBE000C843B968EFCB547A2ED10F9D35C10E8FFE3A5A7F049FF7FFBF5F7400586830FBAC2B6908C2742DE20520065E89CF8CF7DBD279063967C0E1E77A97297BFDDF28B58C1A0260537C74BC2B79676CDE217FFA7D48236D4A309954153DEB39A31AADF6B369CC022E7B7DD7A0B4A72097703E60648B615A4DDDF2D715A5E0992379537A44C13AD103D1FD5A186167E5DE84B11C0F56E1FC6808D2DB35E6E3587243CF59DE605CB806E242A37A2E03838EDCB4B064425FBA5F5F8331EDDF79B46AACC70EEB714995A27B5A879577BDAF956EC9F1C234BD3942E2B2F0E372902637993C54815B69F7EAFF288C16C20D3F8D26B64E23599E6996A833E286FEAD5A7490151202E859E484", + "expectedError": { + "type": "FaultyParticipantOrCoordinatorError", + "message": "Participant sent invalid proof-of-knowledge", + "participantId": 1 + }, + "comment": "invalid cmsg1: pop list has an invalid value at index 1" + }, + { + "tcId": 17, + "cmsg1": "036BEE122EF60CBBB5DFC3572FE6D3A9AC0B35BB427E250F42C38F7C8229889CAB037C42E200B601CD439CDEAA12FB2BD8949C566E59096AA9207165C6FF0602B1D6026F714517277FB154C47E487FBE415C25C0D0F79466F1479B31A8D78319978D8A0260C301C1EEC41AD16BF53F55F97B7B6EB842D9E2B8139712BA54695FF711607308AE5A94BBDD3FC53924623E0D6764E089D679048F699118D7807884AD13CF484866D8210BCB76DAEB103AD6BA1CED0DBFE78F1E8A44F26FC69D5464BF23324D53EF7BE0C965DBBA2418E1FAA16FB9CA5FC990337E0E1858F263A8B88AE081D053CB4BCA7D90B63225ABA7C40F4B86B1D2E91D0EEFC52657B70695BE96FEEF3768EFCB547A2ED10F9D35C10E8FFE3A5A7F049FF7FFBF5F7400586830FBAC2B6908C2742DE20520065E89CF8CF7DBD279063967C0E1E77A97297BFDDF28B58C1A0260537C74BC2B79676CDE217FFA7D48236D4A309954153DEB39A31AADF6B369CC022E7B7DD7A0B4A72097703E60648B615A4DDDF2D715A5E0992379537A44C13AD103D1FD5A186167E5DE84B11C0F56E1FC6808D2DB35E6E3587243CF59DE605CB806E242A37A2E03838EDCB4B064425FBA5F5F8331EDDF79B46AACC70EEB714995A27B5A879577BDAF956EC9F1C234BD3942E2B2F0E372902637993C54815B69F7EAFF288C16C20D3F8D26B64E23599E6996A833E286FEAD5A7490151202E859E484", + "expectedError": { + "type": "UnknownFaultyParticipantOrCoordinatorError", + "message": "Received invalid secshare; consider using participant_investigate() to determine a faulty party" + }, + "comment": "invalid cmsg1: sum_coms_to_nonconst_terms has an arbitrary value at index 0" + }, + { + "tcId": 18, + "cmsg1": "036BEE122EF60CBBB5DFC3572FE6D3A9AC0B35BB427E250F42C38F7C8229889CAB037C42E200B601CD439CDEAA12FB2BD8949C566E59096AA9207165C6FF0602B1D6026F714517277FB154C47E487FBE415C25C0D0F79466F1479B31A8D78319978D8A00000000000000000000000000000000000000000000000000000000000000000008AE5A94BBDD3FC53924623E0D6764E089D679048F699118D7807884AD13CF484866D8210BCB76DAEB103AD6BA1CED0DBFE78F1E8A44F26FC69D5464BF23324D53EF7BE0C965DBBA2418E1FAA16FB9CA5FC990337E0E1858F263A8B88AE081D053CB4BCA7D90B63225ABA7C40F4B86B1D2E91D0EEFC52657B70695BE96FEEF3768EFCB547A2ED10F9D35C10E8FFE3A5A7F049FF7FFBF5F7400586830FBAC2B6908C2742DE20520065E89CF8CF7DBD279063967C0E1E77A97297BFDDF28B58C1A0260537C74BC2B79676CDE217FFA7D48236D4A309954153DEB39A31AADF6B369CC022E7B7DD7A0B4A72097703E60648B615A4DDDF2D715A5E0992379537A44C13AD103D1FD5A186167E5DE84B11C0F56E1FC6808D2DB35E6E3587243CF59DE605CB806E242A37A2E03838EDCB4B064425FBA5F5F8331EDDF79B46AACC70EEB714995A27B5A879577BDAF956EC9F1C234BD3942E2B2F0E372902637993C54815B69F7EAFF288C16C20D3F8D26B64E23599E6996A833E286FEAD5A7490151202E859E484", + "expectedError": { + "type": "UnknownFaultyParticipantOrCoordinatorError", + "message": "Received invalid secshare; consider using participant_investigate() to determine a faulty party" + }, + "comment": "invalid cmsg1: sum_coms_to_nonconst_terms has the infinity point at index 0" + }, + { + "tcId": 19, + "cmsg1": "036BEE122EF60CBBB5DFC3572FE6D3A9AC0B35BB427E250F42C38F7C8229889CAB037C42E200B601CD439CDEAA12FB2BD8949C566E59096AA9207165C6FF0602B1D6026F714517277FB154C47E487FBE415C25C0D0F79466F1479B31A8D78319978D8A03B9C3F15E5C0E086EDE931D69BABD36167BE6CCA8705378F8849A4F00EDA2941308AE5A94BBDD3FC53924623E0D6764E089D679048F699118D7807884AD13CF484866D8210BCB76DAEB103AD6BA1CED0DBFE78F1E8A44F26FC69D5464BF23324D53EF7BE0C965DBBA2418E1FAA16FB9CA5FC990337E0E1858F263A8B88AE081D053CB4BCA7D90B63225ABA7C40F4B86B1D2E91D0EEFC52657B70695BE96FEEF3768EFCB547A2ED10F9D35C10E8FFE3A5A7F049FF7FFBF5F7400586830FBAC2B6908C2742DE20520065E89CF8CF7DBD279063967C0E1E77A97297BFDDF28B58C1A0260537C74BC2B79676CDE217FFA7D48236D4A309954153DEB39A31AADF6B369CC022E7B7DD7A0B4A72097703E60648B615A4DDDF2D715A5E0992379537A44C13AD103D1FD5A186167E5DE84B11C0F56E1FC6808D2DB35E6E3587243CF59DE605CB806E242A37A2E03838EDCB4B064425FBA5F5F8331EDDF79B46AACC70EEB714995B37B5A879577BDAF956EC9F1C234BD3942E2B2F0E372902637993C54815B69F7EAFF288C16C20D3F8D26B64E23599E6996A833E286FEAD5A7490151202E859E484", + "expectedError": { + "type": "UnknownFaultyParticipantOrCoordinatorError", + "message": "Received invalid secshare; consider using participant_investigate() to determine a faulty party" + }, + "comment": "invalid cmsg1: participant 1 sent an invalid secshare for participant 0" + } + ] + }, + { + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3" + ], + "t": 1 + }, + "hostseckey": "ADE179B2C56CB75868D44B333C16C89CB00DFDE378AD79C84D0CCE856E4F9207", + "random": "42B53D62E27380D6F7096EDA1C28C57DDB89FCD4CE5B843EDAC220E165B5A7EC", + "auxRand": "005F5C3A69BB274F4559490AD754F1F5AFFABAED4C71AD5D8ACBAEFC2B491573", + "pmsg1": "0216C95A7FA9079C73184E8646721F59BB0361A839FD6DB9B8CF53CA1EA5B9E1C6558B3C0BFEA2180B7449D7EF101A57D8CF9FC0E32D77FAFDC712A3897007419D6CE9F8FEA08EC9935120955BDFD2519F7B29AD3E0248DA19A0E7D3900FE14CEA02BC88DD206C8AD8C1B52F93AD147695802206D0CA6AB756BD51E5FF5BE858FD219827C7BD6ECCE374663262B90B8D2944C94F9AB1E83F38DBD12823DD7D58B8B3CF1C0AB792C1AF61FA3866CCC4221CAC3CB9D35F7F5358990AB02D7E246BCEDD2AA0622F84004D5AED9B4FC855F20AE22CE260B6E79F56DE5BC2F5B73D137DE3", + "validTestCases": [ + { + "tcId": 20, + "cmsg1": "0216C95A7FA9079C73184E8646721F59BB0361A839FD6DB9B8CF53CA1EA5B9E1C602DA601E3FC8D21A247C5677D9CACFFE0BB86860BC1C233BECA0D97776AAE309DA02FB246B4866569A7A495CFFB1133DAF9096FE4FA22EDA6AF9D7A8F236F9C222B6558B3C0BFEA2180B7449D7EF101A57D8CF9FC0E32D77FAFDC712A3897007419D6CE9F8FEA08EC9935120955BDFD2519F7B29AD3E0248DA19A0E7D3900FE14CEA832AE406B679DEE20F859D361B3C2A1A9EEED646F1B5B6761D1370889D2C0F4EFFFFB7792DD5B2EF16A87A3868280F745D4FAA4EB032695FF0E9285DB7155B838A8C833B35FF0445F94744CBDE8FFC29CBBBFD3B7BA7F3B00A56E96EFA24F6C582EA017BA6A173AA31E4E9F2F5F74D1C4ABA2BBA5B9FACE5F3042B5B2323AB8102BC88DD206C8AD8C1B52F93AD147695802206D0CA6AB756BD51E5FF5BE858FD2103F216E714F96E6AAE00E3FAAF5188FF6F66C71B932E10B17AA8403B17DE9A8F52038AA8962D7EBBF847CE551B1EF8D0D5E8A051B7FD6A29127709F35A6C18CED3007B7219CB404BC96C6D7444B2B85EB2F707B2B345CEA20633657AF21250A668059F6916FE21FE3408D26DDCBFA270CDC67821D88F0B203C46481157B5559F71CD018DADE4C6444BC59855410794924D7793C2ACB37221A3017369F49A1A4FEDF7", + "expectedPmsg2": "82E33ED511440D5609481CB77FDE1E385865F4F00C2E84ACF8DED01EC3532C714C28D13DD572C5808AF82272C1A782B3C0C720EC9527A7AFFE2535688D29AC06", + "comment": "valid participant step2" + } + ], + "errorTestCases": [ + { + "tcId": 21, + "auxRand": "42B53D62E27380D6F7096EDA1C28C57D", + "cmsg1": "0216C95A7FA9079C73184E8646721F59BB0361A839FD6DB9B8CF53CA1EA5B9E1C602DA601E3FC8D21A247C5677D9CACFFE0BB86860BC1C233BECA0D97776AAE309DA02FB246B4866569A7A495CFFB1133DAF9096FE4FA22EDA6AF9D7A8F236F9C222B6558B3C0BFEA2180B7449D7EF101A57D8CF9FC0E32D77FAFDC712A3897007419D6CE9F8FEA08EC9935120955BDFD2519F7B29AD3E0248DA19A0E7D3900FE14CEA832AE406B679DEE20F859D361B3C2A1A9EEED646F1B5B6761D1370889D2C0F4EFFFFB7792DD5B2EF16A87A3868280F745D4FAA4EB032695FF0E9285DB7155B838A8C833B35FF0445F94744CBDE8FFC29CBBBFD3B7BA7F3B00A56E96EFA24F6C582EA017BA6A173AA31E4E9F2F5F74D1C4ABA2BBA5B9FACE5F3042B5B2323AB8102BC88DD206C8AD8C1B52F93AD147695802206D0CA6AB756BD51E5FF5BE858FD2103F216E714F96E6AAE00E3FAAF5188FF6F66C71B932E10B17AA8403B17DE9A8F52038AA8962D7EBBF847CE551B1EF8D0D5E8A051B7FD6A29127709F35A6C18CED3007B7219CB404BC96C6D7444B2B85EB2F707B2B345CEA20633657AF21250A668059F6916FE21FE3408D26DDCBFA270CDC67821D88F0B203C46481157B5559F71CD018DADE4C6444BC59855410794924D7793C2ACB37221A3017369F49A1A4FEDF7", + "expectedError": { + "type": "ValueError" + }, + "comment": "length of aux randomness is not 32 bytes" + }, + { + "tcId": 22, + "hostseckey": "94BB10C1DE15783C3F3E49167A0951CACD2803F13AAC456C816E88AB4AC76330", + "cmsg1": "0216C95A7FA9079C73184E8646721F59BB0361A839FD6DB9B8CF53CA1EA5B9E1C602DA601E3FC8D21A247C5677D9CACFFE0BB86860BC1C233BECA0D97776AAE309DA02FB246B4866569A7A495CFFB1133DAF9096FE4FA22EDA6AF9D7A8F236F9C222B6558B3C0BFEA2180B7449D7EF101A57D8CF9FC0E32D77FAFDC712A3897007419D6CE9F8FEA08EC9935120955BDFD2519F7B29AD3E0248DA19A0E7D3900FE14CEA832AE406B679DEE20F859D361B3C2A1A9EEED646F1B5B6761D1370889D2C0F4EFFFFB7792DD5B2EF16A87A3868280F745D4FAA4EB032695FF0E9285DB7155B838A8C833B35FF0445F94744CBDE8FFC29CBBBFD3B7BA7F3B00A56E96EFA24F6C582EA017BA6A173AA31E4E9F2F5F74D1C4ABA2BBA5B9FACE5F3042B5B2323AB8102BC88DD206C8AD8C1B52F93AD147695802206D0CA6AB756BD51E5FF5BE858FD2103F216E714F96E6AAE00E3FAAF5188FF6F66C71B932E10B17AA8403B17DE9A8F52038AA8962D7EBBF847CE551B1EF8D0D5E8A051B7FD6A29127709F35A6C18CED3007B7219CB404BC96C6D7444B2B85EB2F707B2B345CEA20633657AF21250A668059F6916FE21FE3408D26DDCBFA270CDC67821D88F0B203C46481157B5559F71CD018DADE4C6444BC59855410794924D7793C2ACB37221A3017369F49A1A4FEDF7", + "expectedError": { + "type": "HostSeckeyError", + "message": "Host secret key does not match the one used in participant_step1" + }, + "comment": "hostseckey does not match the one used in participant_step1" + }, + { + "tcId": 23, + "cmsg1": "0216C95A7FA9079C73184E8646721F59BB0361A839FD6DB9B8CF53CA1EA5B9E1C602DA601E3FC8D21A247C5677D9CACFFE0BB86860BC1C233BECA0D97776AAE309DA02FB246B4866569A7A495CFFB1133DAF9096FE4FA22EDA6AF9D7A8F236F9C222B6558B3C0BFEA2180B7449D7EF101A57D8CF9FC0E32D77FAFDC712A3897007419D6CE9F8FEA08EC9935120955BDFD2519F7B29AD3E0248DA19A0E7D3900FE14CEA832AE406B679DEE20F859D361B3C2A1A9EEED646F1B5B6761D1370889D2C0F4EFFFFB7792DD5B2EF16A87A3868280F745D4FAA4EB032695FF0E9285DB7155B838A8C833B35FF0445F94744CBDE8FFC29CBBBFD3B7BA7F3B00A56E96EFA24F6C582EA017BA6A173AA31E4E9F2F5F74D1C4ABA2BBA5B9FACE5F3042B5B2323AB81EBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEB03F216E714F96E6AAE00E3FAAF5188FF6F66C71B932E10B17AA8403B17DE9A8F52038AA8962D7EBBF847CE551B1EF8D0D5E8A051B7FD6A29127709F35A6C18CED3007B7219CB404BC96C6D7444B2B85EB2F707B2B345CEA20633657AF21250A668059F6916FE21FE3408D26DDCBFA270CDC67821D88F0B203C46481157B5559F71CD018DADE4C6444BC59855410794924D7793C2ACB37221A3017369F49A1A4FEDF7", + "expectedError": { + "type": "FaultyCoordinatorError", + "message": "Coordinator replied with wrong pubnonce" + }, + "comment": "invalid cmsg1: pubnonces list has an invalid prefix at index 0 (own pubnonce)" + }, + { + "tcId": 24, + "cmsg1": "0216C95A7FA9079C73184E8646721F59BB0361A839FD6DB9B8CF53CA1EA5B9E1C602DA601E3FC8D21A247C5677D9CACFFE0BB86860BC1C233BECA0D97776AAE309DA02FB246B4866569A7A495CFFB1133DAF9096FE4FA22EDA6AF9D7A8F236F9C222B6558B3C0BFEA2180B7449D7EF101A57D8CF9FC0E32D77FAFDC712A3897007419D6CE9F8FEA08EC9935120955BDFD2519F7B29AD3E0248DA19A0E7D3900FE14CEA832AE406B679DEE20F859D361B3C2A1A9EEED646F1B5B6761D1370889D2C0F4EFFFFB7792DD5B2EF16A87A3868280F745D4FAA4EB032695FF0E9285DB7155B838A8C833B35FF0445F94744CBDE8FFC29CBBBFD3B7BA7F3B00A56E96EFA24F6C582EA017BA6A173AA31E4E9F2F5F74D1C4ABA2BBA5B9FACE5F3042B5B2323AB8102BC88DD206C8AD8C1B52F93AD147695802206D0CA6AB756BD51E5FF5BE858FD21EBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEB038AA8962D7EBBF847CE551B1EF8D0D5E8A051B7FD6A29127709F35A6C18CED3007B7219CB404BC96C6D7444B2B85EB2F707B2B345CEA20633657AF21250A668059F6916FE21FE3408D26DDCBFA270CDC67821D88F0B203C46481157B5559F71CD018DADE4C6444BC59855410794924D7793C2ACB37221A3017369F49A1A4FEDF7", + "expectedError": { + "type": "FaultyParticipantOrCoordinatorError", + "message": "invalid public nonce", + "participantId": 1 + }, + "comment": "invalid cmsg1: pubnonces list has an invalid prefix at index 1" + }, + { + "tcId": 25, + "cmsg1": "0216C95A7FA9079C73184E8646721F59BB0361A839FD6DB9B8CF53CA1EA5B9E1C602DA601E3FC8D21A247C5677D9CACFFE0BB86860BC1C233BECA0D97776AAE309DA02FB246B4866569A7A495CFFB1133DAF9096FE4FA22EDA6AF9D7A8F236F9C222B6558B3C0BFEA2180B7449D7EF101A57D8CF9FC0E32D77FAFDC712A3897007419D6CE9F8FEA08EC9935120955BDFD2519F7B29AD3E0248DA19A0E7D3900FE14CEA832AE406B679DEE20F859D361B3C2A1A9EEED646F1B5B6761D1370889D2C0F4EFFFFB7792DD5B2EF16A87A3868280F745D4FAA4EB032695FF0E9285DB7155B838A8C833B35FF0445F94744CBDE8FFC29CBBBFD3B7BA7F3B00A56E96EFA24F6C582EA017BA6A173AA31E4E9F2F5F74D1C4ABA2BBA5B9FACE5F3042B5B2323AB8103000000000000000000000000000000000000000000000000000000000000000503F216E714F96E6AAE00E3FAAF5188FF6F66C71B932E10B17AA8403B17DE9A8F52038AA8962D7EBBF847CE551B1EF8D0D5E8A051B7FD6A29127709F35A6C18CED3007B7219CB404BC96C6D7444B2B85EB2F707B2B345CEA20633657AF21250A668059F6916FE21FE3408D26DDCBFA270CDC67821D88F0B203C46481157B5559F71CD018DADE4C6444BC59855410794924D7793C2ACB37221A3017369F49A1A4FEDF7", + "expectedError": { + "type": "FaultyCoordinatorError", + "message": "Coordinator replied with wrong pubnonce" + }, + "comment": "invalid cmsg1: pubnonces list has an off-curve x-coordinate at index 0 (own pubnonce)" + }, + { + "tcId": 26, + "cmsg1": "0216C95A7FA9079C73184E8646721F59BB0361A839FD6DB9B8CF53CA1EA5B9E1C602DA601E3FC8D21A247C5677D9CACFFE0BB86860BC1C233BECA0D97776AAE309DA02FB246B4866569A7A495CFFB1133DAF9096FE4FA22EDA6AF9D7A8F236F9C222B6558B3C0BFEA2180B7449D7EF101A57D8CF9FC0E32D77FAFDC712A3897007419D6CE9F8FEA08EC9935120955BDFD2519F7B29AD3E0248DA19A0E7D3900FE14CEA832AE406B679DEE20F859D361B3C2A1A9EEED646F1B5B6761D1370889D2C0F4EFFFFB7792DD5B2EF16A87A3868280F745D4FAA4EB032695FF0E9285DB7155B838A8C833B35FF0445F94744CBDE8FFC29CBBBFD3B7BA7F3B00A56E96EFA24F6C582EA017BA6A173AA31E4E9F2F5F74D1C4ABA2BBA5B9FACE5F3042B5B2323AB8102BC88DD206C8AD8C1B52F93AD147695802206D0CA6AB756BD51E5FF5BE858FD21030000000000000000000000000000000000000000000000000000000000000005038AA8962D7EBBF847CE551B1EF8D0D5E8A051B7FD6A29127709F35A6C18CED3007B7219CB404BC96C6D7444B2B85EB2F707B2B345CEA20633657AF21250A668059F6916FE21FE3408D26DDCBFA270CDC67821D88F0B203C46481157B5559F71CD018DADE4C6444BC59855410794924D7793C2ACB37221A3017369F49A1A4FEDF7", + "expectedError": { + "type": "FaultyParticipantOrCoordinatorError", + "message": "invalid public nonce", + "participantId": 1 + }, + "comment": "invalid cmsg1: pubnonces list has an off-curve x-coordinate at index 1" + }, + { + "tcId": 27, + "cmsg1": "0216C95A7FA9079C73184E8646721F59BB0361A839FD6DB9B8CF53CA1EA5B9E1C602DA601E3FC8D21A247C5677D9CACFFE0BB86860BC1C233BECA0D97776AAE309DA02FB246B4866569A7A495CFFB1133DAF9096FE4FA22EDA6AF9D7A8F236F9C222B6558B3C0BFEA2180B7449D7EF101A57D8CF9FC0E32D77FAFDC712A3897007419D6CE9F8FEA08EC9935120955BDFD2519F7B29AD3E0248DA19A0E7D3900FE14CEA832AE406B679DEE20F859D361B3C2A1A9EEED646F1B5B6761D1370889D2C0F4EFFFFB7792DD5B2EF16A87A3868280F745D4FAA4EB032695FF0E9285DB7155B838A8C833B35FF0445F94744CBDE8FFC29CBBBFD3B7BA7F3B00A56E96EFA24F6C582EA017BA6A173AA31E4E9F2F5F74D1C4ABA2BBA5B9FACE5F3042B5B2323AB810260C301C1EEC41AD16BF53F55F97B7B6EB842D9E2B8139712BA54695FF711607303F216E714F96E6AAE00E3FAAF5188FF6F66C71B932E10B17AA8403B17DE9A8F52038AA8962D7EBBF847CE551B1EF8D0D5E8A051B7FD6A29127709F35A6C18CED3007B7219CB404BC96C6D7444B2B85EB2F707B2B345CEA20633657AF21250A668059F6916FE21FE3408D26DDCBFA270CDC67821D88F0B203C46481157B5559F71CD018DADE4C6444BC59855410794924D7793C2ACB37221A3017369F49A1A4FEDF7", + "expectedError": { + "type": "FaultyCoordinatorError", + "message": "Coordinator replied with wrong pubnonce" + }, + "comment": "invalid cmsg1: pubnonces list has an arbitrary value at index 0 (own pubnonce)" + }, + { + "tcId": 28, + "cmsg1": "0216C95A7FA9079C73184E8646721F59BB0361A839FD6DB9B8CF53CA1EA5B9E1C602DA601E3FC8D21A247C5677D9CACFFE0BB86860BC1C233BECA0D97776AAE309DA02FB246B4866569A7A495CFFB1133DAF9096FE4FA22EDA6AF9D7A8F236F9C222B6558B3C0BFEA2180B7449D7EF101A57D8CF9FC0E32D77FAFDC712A3897007419D6CE9F8FEA08EC9935120955BDFD2519F7B29AD3E0248DA19A0E7D3900FE14CEA832AE406B679DEE20F859D361B3C2A1A9EEED646F1B5B6761D1370889D2C0F4EFFFFB7792DD5B2EF16A87A3868280F745D4FAA4EB032695FF0E9285DB7155B838A8C833B35FF0445F94744CBDE8FFC29CBBBFD3B7BA7F3B00A56E96EFA24F6C582EA017BA6A173AA31E4E9F2F5F74D1C4ABA2BBA5B9FACE5F3042B5B2323AB8102BC88DD206C8AD8C1B52F93AD147695802206D0CA6AB756BD51E5FF5BE858FD210260C301C1EEC41AD16BF53F55F97B7B6EB842D9E2B8139712BA54695FF7116073038AA8962D7EBBF847CE551B1EF8D0D5E8A051B7FD6A29127709F35A6C18CED3007B7219CB404BC96C6D7444B2B85EB2F707B2B345CEA20633657AF21250A668059F6916FE21FE3408D26DDCBFA270CDC67821D88F0B203C46481157B5559F71CD018DADE4C6444BC59855410794924D7793C2ACB37221A3017369F49A1A4FEDF7", + "expectedError": { + "type": "UnknownFaultyParticipantOrCoordinatorError", + "message": "Received invalid secshare; consider using participant_investigate() to determine a faulty party" + }, + "comment": "invalid cmsg1: pubnonces list has an arbitrary value at index 1" + }, + { + "tcId": 29, + "cmsg1": "0216C95A7FA9079C73184E8646721F59BB0361A839FD6DB9B8CF53CA1EA5B9E1C602DA601E3FC8D21A247C5677D9CACFFE0BB86860BC1C233BECA0D97776AAE309DA02FB246B4866569A7A495CFFB1133DAF9096FE4FA22EDA6AF9D7A8F236F9C222B6558B3C0BFEA2180B7449D7EF101A57D8CF9FC0E32D77FAFDC712A3897007419D6CE9F8FEA08EC9935120955BDFD2519F7B29AD3E0248DA19A0E7D3900FE14CEA832AE406B679DEE20F859D361B3C2A1A9EEED646F1B5B6761D1370889D2C0F4EFFFFB7792DD5B2EF16A87A3868280F745D4FAA4EB032695FF0E9285DB7155B838A8C833B35FF0445F94744CBDE8FFC29CBBBFD3B7BA7F3B00A56E96EFA24F6C582EA017BA6A173AA31E4E9F2F5F74D1C4ABA2BBA5B9FACE5F3042B5B2323AB8100000000000000000000000000000000000000000000000000000000000000000003F216E714F96E6AAE00E3FAAF5188FF6F66C71B932E10B17AA8403B17DE9A8F52038AA8962D7EBBF847CE551B1EF8D0D5E8A051B7FD6A29127709F35A6C18CED3007B7219CB404BC96C6D7444B2B85EB2F707B2B345CEA20633657AF21250A668059F6916FE21FE3408D26DDCBFA270CDC67821D88F0B203C46481157B5559F71CD018DADE4C6444BC59855410794924D7793C2ACB37221A3017369F49A1A4FEDF7", + "expectedError": { + "type": "FaultyCoordinatorError", + "message": "Coordinator replied with wrong pubnonce" + }, + "comment": "invalid cmsg1: pubnonces list has the infinity point at index 0 (own pubnonce)" + }, + { + "tcId": 30, + "cmsg1": "0216C95A7FA9079C73184E8646721F59BB0361A839FD6DB9B8CF53CA1EA5B9E1C602DA601E3FC8D21A247C5677D9CACFFE0BB86860BC1C233BECA0D97776AAE309DA02FB246B4866569A7A495CFFB1133DAF9096FE4FA22EDA6AF9D7A8F236F9C222B6558B3C0BFEA2180B7449D7EF101A57D8CF9FC0E32D77FAFDC712A3897007419D6CE9F8FEA08EC9935120955BDFD2519F7B29AD3E0248DA19A0E7D3900FE14CEA832AE406B679DEE20F859D361B3C2A1A9EEED646F1B5B6761D1370889D2C0F4EFFFFB7792DD5B2EF16A87A3868280F745D4FAA4EB032695FF0E9285DB7155B838A8C833B35FF0445F94744CBDE8FFC29CBBBFD3B7BA7F3B00A56E96EFA24F6C582EA017BA6A173AA31E4E9F2F5F74D1C4ABA2BBA5B9FACE5F3042B5B2323AB8102BC88DD206C8AD8C1B52F93AD147695802206D0CA6AB756BD51E5FF5BE858FD21000000000000000000000000000000000000000000000000000000000000000000038AA8962D7EBBF847CE551B1EF8D0D5E8A051B7FD6A29127709F35A6C18CED3007B7219CB404BC96C6D7444B2B85EB2F707B2B345CEA20633657AF21250A668059F6916FE21FE3408D26DDCBFA270CDC67821D88F0B203C46481157B5559F71CD018DADE4C6444BC59855410794924D7793C2ACB37221A3017369F49A1A4FEDF7", + "expectedError": { + "type": "FaultyParticipantOrCoordinatorError", + "message": "invalid public nonce", + "participantId": 1 + }, + "comment": "invalid cmsg1: pubnonces list has the infinity point at index 1" + }, + { + "tcId": 31, + "cmsg1": "0216C95A7FA9079C73184E8646721F59BB0361A839FD6DB9B8CF53CA1EA5B9E1C602DA601E3FC8D21A247C5677D9CACFFE0BB86860BC1C233BECA0D97776AAE309DA02FB246B4866569A7A495CFFB1133DAF9096FE4FA22EDA6AF9D7A8F236F9C222B6558B3C0BFEA2180B7449D7EF101A57D8CF9FC0E32D77FAFDC712A3897007419D6CE9F8FEA08EC9935120955BDFD2519F7B29AD3E0248DA19A0E7D3900FE14CEA832AE406B679DEE20F859D361B3C2A1A9EEED646F1B5B6761D1370889D2C0F4EFFFFB7792DD5B2EF16A87A3868280F745D4FAA4EB032695FF0E9285DB7155B838A8C833B35FF0445F94744CBDE8FFC29CBBBFD3B7BA7F3B00A56E96EFA24F6C582EA017BA6A173AA31E4E9F2F5F74D1C4ABA2BBA5B9FACE5F3042B5B2323AB8102BC88DD206C8AD8C1B52F93AD147695802206D0CA6AB756BD51E5FF5BE858FD2102BC88DD206C8AD8C1B52F93AD147695802206D0CA6AB756BD51E5FF5BE858FD21038AA8962D7EBBF847CE551B1EF8D0D5E8A051B7FD6A29127709F35A6C18CED3007B7219CB404BC96C6D7444B2B85EB2F707B2B345CEA20633657AF21250A668059F6916FE21FE3408D26DDCBFA270CDC67821D88F0B203C46481157B5559F71CD018DADE4C6444BC59855410794924D7793C2ACB37221A3017369F49A1A4FEDF7", + "expectedError": { + "type": "UnknownFaultyParticipantOrCoordinatorError", + "message": "Received invalid secshare; consider using participant_investigate() to determine a faulty party" + }, + "comment": "invalid cmsg1: pubnonces list has duplicate values" + }, + { + "tcId": 32, + "cmsg1": "0216C95A7FA9079C73184E8646721F59BB0361A839FD6DB9B8CF53CA1EA5B9E1C602DA601E3FC8D21A247C5677D9CACFFE0BB86860BC1C233BECA0D97776AAE309DA02FB246B4866569A7A495CFFB1133DAF9096FE4FA22EDA6AF9D7A8F236F9C222B6558B3C0BFEA2180B7449D7EF101A57D8CF9FC0E32D77FAFDC712A3897007419D6CE9F8FEA08EC9935120955BDFD2519F7B29AD3E0248DA19A0E7D3900FE14CEA832AE406B679DEE20F859D361B3C2A1A9EEED646F1B5B6761D1370889D2C0F4EFFFFB7792DD5B2EF16A87A3868280F745D4FAA4EB032695FF0E9285DB7155B838A8C833B35FF0445F94744CBDE8FFC29CBBBFD3B7BA7F3B00A56E96EFA24F6C582EA017BA6A173AA31E4E9F2F5F74D1C4ABA2BBA5B9FACE5F3042B5B2323AB8102BC88DD206C8AD8C1B52F93AD147695802206D0CA6AB756BD51E5FF5BE858FD2103F216E714F96E6AAE00E3FAAF5188FF6F66C71B932E10B17AA8403B17DE9A8F52038AA8962D7EBBF847CE551B1EF8D0D5E8A051B7FD6A29127709F35A6C18CED3007B7219CB404BC96C6D7444B2B85EB2F707B2B345CEA20633657AF21250A668059F6916FE21FE3408D26DDCBFA270CDC67821D88F0B203C46481157B5559F71CD018DADE4C6444BC59855410794924D7793C2ACB37221A3017369F49A1A4FED", + "expectedError": { + "type": "ValueError" + }, + "comment": "invalid cmsg1: missing encrypted secret shares" + }, + { + "tcId": 33, + "cmsg1": "0260C301C1EEC41AD16BF53F55F97B7B6EB842D9E2B8139712BA54695FF711607302DA601E3FC8D21A247C5677D9CACFFE0BB86860BC1C233BECA0D97776AAE309DA02FB246B4866569A7A495CFFB1133DAF9096FE4FA22EDA6AF9D7A8F236F9C222B6558B3C0BFEA2180B7449D7EF101A57D8CF9FC0E32D77FAFDC712A3897007419D6CE9F8FEA08EC9935120955BDFD2519F7B29AD3E0248DA19A0E7D3900FE14CEA832AE406B679DEE20F859D361B3C2A1A9EEED646F1B5B6761D1370889D2C0F4EFFFFB7792DD5B2EF16A87A3868280F745D4FAA4EB032695FF0E9285DB7155B838A8C833B35FF0445F94744CBDE8FFC29CBBBFD3B7BA7F3B00A56E96EFA24F6C582EA017BA6A173AA31E4E9F2F5F74D1C4ABA2BBA5B9FACE5F3042B5B2323AB8102BC88DD206C8AD8C1B52F93AD147695802206D0CA6AB756BD51E5FF5BE858FD2103F216E714F96E6AAE00E3FAAF5188FF6F66C71B932E10B17AA8403B17DE9A8F52038AA8962D7EBBF847CE551B1EF8D0D5E8A051B7FD6A29127709F35A6C18CED3007B7219CB404BC96C6D7444B2B85EB2F707B2B345CEA20633657AF21250A668059F6916FE21FE3408D26DDCBFA270CDC67821D88F0B203C46481157B5559F71CD018DADE4C6444BC59855410794924D7793C2ACB37221A3017369F49A1A4FEDF7", + "expectedError": { + "type": "FaultyCoordinatorError", + "message": "Coordinator sent unexpected first group element for local participant id" + }, + "comment": "invalid cmsg1: coms_to_secrets list has an arbitrary value at index 0" + }, + { + "tcId": 34, + "cmsg1": "0216C95A7FA9079C73184E8646721F59BB0361A839FD6DB9B8CF53CA1EA5B9E1C600000000000000000000000000000000000000000000000000000000000000000002FB246B4866569A7A495CFFB1133DAF9096FE4FA22EDA6AF9D7A8F236F9C222B6558B3C0BFEA2180B7449D7EF101A57D8CF9FC0E32D77FAFDC712A3897007419D6CE9F8FEA08EC9935120955BDFD2519F7B29AD3E0248DA19A0E7D3900FE14CEA832AE406B679DEE20F859D361B3C2A1A9EEED646F1B5B6761D1370889D2C0F4EFFFFB7792DD5B2EF16A87A3868280F745D4FAA4EB032695FF0E9285DB7155B838A8C833B35FF0445F94744CBDE8FFC29CBBBFD3B7BA7F3B00A56E96EFA24F6C582EA017BA6A173AA31E4E9F2F5F74D1C4ABA2BBA5B9FACE5F3042B5B2323AB8102BC88DD206C8AD8C1B52F93AD147695802206D0CA6AB756BD51E5FF5BE858FD2103F216E714F96E6AAE00E3FAAF5188FF6F66C71B932E10B17AA8403B17DE9A8F52038AA8962D7EBBF847CE551B1EF8D0D5E8A051B7FD6A29127709F35A6C18CED3007B7219CB404BC96C6D7444B2B85EB2F707B2B345CEA20633657AF21250A668059F6916FE21FE3408D26DDCBFA270CDC67821D88F0B203C46481157B5559F71CD018DADE4C6444BC59855410794924D7793C2ACB37221A3017369F49A1A4FEDF7", + "expectedError": { + "type": "FaultyParticipantOrCoordinatorError", + "message": "Participant sent invalid commitment", + "participantId": 1 + }, + "comment": "invalid cmsg1: coms_to_secrets list has infinity at index 1" + }, + { + "tcId": 35, + "cmsg1": "0216C95A7FA9079C73184E8646721F59BB0361A839FD6DB9B8CF53CA1EA5B9E1C602DA601E3FC8D21A247C5677D9CACFFE0BB86860BC1C233BECA0D97776AAE309DA02FB246B4866569A7A495CFFB1133DAF9096FE4FA22EDA6AF9D7A8F236F9C222B6558B3C0BFEA2180B7449D7EF101A57D8CF9FC0E32D77FAFDC712A3897007419D6CE9F8FEA08EC9935120955BDFD2519F7B29AD3E0248DA19A0E7D3900FE14CEA09C289578B96E6283AB13E4741FB489FC147FB1A5F446A314BA73C052131EFB04B83247A0BCEDF5205202AD64188B24B0BC5B51A17AEB218BD98DBE000C843B98A8C833B35FF0445F94744CBDE8FFC29CBBBFD3B7BA7F3B00A56E96EFA24F6C582EA017BA6A173AA31E4E9F2F5F74D1C4ABA2BBA5B9FACE5F3042B5B2323AB8102BC88DD206C8AD8C1B52F93AD147695802206D0CA6AB756BD51E5FF5BE858FD2103F216E714F96E6AAE00E3FAAF5188FF6F66C71B932E10B17AA8403B17DE9A8F52038AA8962D7EBBF847CE551B1EF8D0D5E8A051B7FD6A29127709F35A6C18CED3007B7219CB404BC96C6D7444B2B85EB2F707B2B345CEA20633657AF21250A668059F6916FE21FE3408D26DDCBFA270CDC67821D88F0B203C46481157B5559F71CD018DADE4C6444BC59855410794924D7793C2ACB37221A3017369F49A1A4FEDF7", + "expectedError": { + "type": "FaultyParticipantOrCoordinatorError", + "message": "Participant sent invalid proof-of-knowledge", + "participantId": 1 + }, + "comment": "invalid cmsg1: pop list has an invalid value at index 1" + }, + { + "tcId": 36, + "cmsg1": "0216C95A7FA9079C73184E8646721F59BB0361A839FD6DB9B8CF53CA1EA5B9E1C602DA601E3FC8D21A247C5677D9CACFFE0BB86860BC1C233BECA0D97776AAE309DA02FB246B4866569A7A495CFFB1133DAF9096FE4FA22EDA6AF9D7A8F236F9C222B6558B3C0BFEA2180B7449D7EF101A57D8CF9FC0E32D77FAFDC712A3897007419D6CE9F8FEA08EC9935120955BDFD2519F7B29AD3E0248DA19A0E7D3900FE14CEA832AE406B679DEE20F859D361B3C2A1A9EEED646F1B5B6761D1370889D2C0F4EFFFFB7792DD5B2EF16A87A3868280F745D4FAA4EB032695FF0E9285DB7155B838A8C833B35FF0445F94744CBDE8FFC29CBBBFD3B7BA7F3B00A56E96EFA24F6C582EA017BA6A173AA31E4E9F2F5F74D1C4ABA2BBA5B9FACE5F3042B5B2323AB8102BC88DD206C8AD8C1B52F93AD147695802206D0CA6AB756BD51E5FF5BE858FD2103F216E714F96E6AAE00E3FAAF5188FF6F66C71B932E10B17AA8403B17DE9A8F52038AA8962D7EBBF847CE551B1EF8D0D5E8A051B7FD6A29127709F35A6C18CED3007B7219CB404BC96C6D7444B2B85EB2F707B2B345CEA20633657AF21250A668169F6916FE21FE3408D26DDCBFA270CDC67821D88F0B203C46481157B5559F71CD018DADE4C6444BC59855410794924D7793C2ACB37221A3017369F49A1A4FEDF7", + "expectedError": { + "type": "UnknownFaultyParticipantOrCoordinatorError", + "message": "Received invalid secshare; consider using participant_investigate() to determine a faulty party" + }, + "comment": "invalid cmsg1: participant 1 sent an invalid secshare for participant 0" + } + ] + }, + { + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3" + ], + "t": 3 + }, + "hostseckey": "ADE179B2C56CB75868D44B333C16C89CB00DFDE378AD79C84D0CCE856E4F9207", + "random": "42B53D62E27380D6F7096EDA1C28C57DDB89FCD4CE5B843EDAC220E165B5A7EC", + "auxRand": "005F5C3A69BB274F4559490AD754F1F5AFFABAED4C71AD5D8ACBAEFC2B491573", + "pmsg1": "02F659A9668C1E7FB2BF4B457CA91C9925B3AB9F3F029C489C53603EF6AF90199B03BB0D7426739F2FAB24C6A0B2693273BBC92E03D3CB1A252956D372E4484F3EFE030C43B612EC5E536EF7853FCC8744BF77AF272485349484BDAA6FB6F2222FDD5A92AE219E3FE6B306490A7510AF6D9C981983DE7E4A83127F350451986EB2AF829A8C8BF483ADD195B5C7B08073D9548FFCDFB5626F0DA6A26468AD9BE54D5C83026C6EBBC55EDC86ABA62BA55A3375A940E731228F214937BF8A2FC58B29AE8F087C47A6836F30317C4B914E33F10B72CA6EDD116A3DF9E8BFE997045F9F957D31A43BB05F6162602B96E5D8C0AE3A07C8228E41DE772B0ACD96937B6CD0115ADE0ACB3150F08EA8668C262DC091C6ACA77674E8C749023FA6FB5D6938BEC73CD8", + "validTestCases": [ + { + "tcId": 37, + "cmsg1": "02F659A9668C1E7FB2BF4B457CA91C9925B3AB9F3F029C489C53603EF6AF90199B034619FE6A2FF09FE393CACB98B0708545255926B9CB33103567AAF2EE4D23B0540250A0832ED8FECB459561A2BFE1103A29CD4B23D53B27B707376FE0E67562F20003D2BAB55842CB855189CF690BA0E2C588AEFCD985F6E6B60C3CFCBDBF42FC68F702D17C1ACB4697C8032830CFA0099BE475C8DBDC63E2CE294795C8AAAF69EF908792AE219E3FE6B306490A7510AF6D9C981983DE7E4A83127F350451986EB2AF829A8C8BF483ADD195B5C7B08073D9548FFCDFB5626F0DA6A26468AD9BE54D5C8318AB016E948B6EE152EEC9B6AD07ADC102177C6060C5FA6AB774DE136D59657C62D596F9C9477FF09A14654D48AC94B9CF381221ED27DC26B0F224C70039771FACBC3B4D52DD7A762F752250E41218F5295C3DDFCFF43350759D74A62B695352CE3C302E8718BA21C85BED769AEF3846A88E691AE945D6EBA09F51ACB0143D10026C6EBBC55EDC86ABA62BA55A3375A940E731228F214937BF8A2FC58B29AE8F080334032916F989086FB5C62722C096F0EC6A85808DB2BFA270227A72245BD7AC1B02522C52C88A6B0B1E9CF8943960E87722C1D5307069723C25B151939A58B08C10161A5552E5C1C256BA8489D1D621337C132BBECEF9840E3606582F918031F3E662B050C8F49D54C73A0F4C4D0B06DA93B81DC5463511E05913D9F2E0B5DB67D39B53A4AA164B45269B41B8F06E4254C47A55B7132E0310C626CE46DB6ABCB797", + "expectedPmsg2": "BFD15AA9C0A086B4BC83CC1FBE2864519E95360101E038B56CC6F58A13BDCBCDA5310629AD258C8FEC60DEB8ED49A2749E2EDD2204257F41713CF3181FB4F89B", + "comment": "valid participant step2" + } + ], + "errorTestCases": [ + { + "tcId": 38, + "auxRand": "42B53D62E27380D6F7096EDA1C28C57D", + "cmsg1": "02F659A9668C1E7FB2BF4B457CA91C9925B3AB9F3F029C489C53603EF6AF90199B034619FE6A2FF09FE393CACB98B0708545255926B9CB33103567AAF2EE4D23B0540250A0832ED8FECB459561A2BFE1103A29CD4B23D53B27B707376FE0E67562F20003D2BAB55842CB855189CF690BA0E2C588AEFCD985F6E6B60C3CFCBDBF42FC68F702D17C1ACB4697C8032830CFA0099BE475C8DBDC63E2CE294795C8AAAF69EF908792AE219E3FE6B306490A7510AF6D9C981983DE7E4A83127F350451986EB2AF829A8C8BF483ADD195B5C7B08073D9548FFCDFB5626F0DA6A26468AD9BE54D5C8318AB016E948B6EE152EEC9B6AD07ADC102177C6060C5FA6AB774DE136D59657C62D596F9C9477FF09A14654D48AC94B9CF381221ED27DC26B0F224C70039771FACBC3B4D52DD7A762F752250E41218F5295C3DDFCFF43350759D74A62B695352CE3C302E8718BA21C85BED769AEF3846A88E691AE945D6EBA09F51ACB0143D10026C6EBBC55EDC86ABA62BA55A3375A940E731228F214937BF8A2FC58B29AE8F080334032916F989086FB5C62722C096F0EC6A85808DB2BFA270227A72245BD7AC1B02522C52C88A6B0B1E9CF8943960E87722C1D5307069723C25B151939A58B08C10161A5552E5C1C256BA8489D1D621337C132BBECEF9840E3606582F918031F3E662B050C8F49D54C73A0F4C4D0B06DA93B81DC5463511E05913D9F2E0B5DB67D39B53A4AA164B45269B41B8F06E4254C47A55B7132E0310C626CE46DB6ABCB797", + "expectedError": { + "type": "ValueError" + }, + "comment": "length of aux randomness is not 32 bytes" + }, + { + "tcId": 39, + "hostseckey": "94BB10C1DE15783C3F3E49167A0951CACD2803F13AAC456C816E88AB4AC76330", + "cmsg1": "02F659A9668C1E7FB2BF4B457CA91C9925B3AB9F3F029C489C53603EF6AF90199B034619FE6A2FF09FE393CACB98B0708545255926B9CB33103567AAF2EE4D23B0540250A0832ED8FECB459561A2BFE1103A29CD4B23D53B27B707376FE0E67562F20003D2BAB55842CB855189CF690BA0E2C588AEFCD985F6E6B60C3CFCBDBF42FC68F702D17C1ACB4697C8032830CFA0099BE475C8DBDC63E2CE294795C8AAAF69EF908792AE219E3FE6B306490A7510AF6D9C981983DE7E4A83127F350451986EB2AF829A8C8BF483ADD195B5C7B08073D9548FFCDFB5626F0DA6A26468AD9BE54D5C8318AB016E948B6EE152EEC9B6AD07ADC102177C6060C5FA6AB774DE136D59657C62D596F9C9477FF09A14654D48AC94B9CF381221ED27DC26B0F224C70039771FACBC3B4D52DD7A762F752250E41218F5295C3DDFCFF43350759D74A62B695352CE3C302E8718BA21C85BED769AEF3846A88E691AE945D6EBA09F51ACB0143D10026C6EBBC55EDC86ABA62BA55A3375A940E731228F214937BF8A2FC58B29AE8F080334032916F989086FB5C62722C096F0EC6A85808DB2BFA270227A72245BD7AC1B02522C52C88A6B0B1E9CF8943960E87722C1D5307069723C25B151939A58B08C10161A5552E5C1C256BA8489D1D621337C132BBECEF9840E3606582F918031F3E662B050C8F49D54C73A0F4C4D0B06DA93B81DC5463511E05913D9F2E0B5DB67D39B53A4AA164B45269B41B8F06E4254C47A55B7132E0310C626CE46DB6ABCB797", + "expectedError": { + "type": "HostSeckeyError", + "message": "Host secret key does not match the one used in participant_step1" + }, + "comment": "hostseckey does not match the one used in participant_step1" + }, + { + "tcId": 40, + "cmsg1": "02F659A9668C1E7FB2BF4B457CA91C9925B3AB9F3F029C489C53603EF6AF90199B034619FE6A2FF09FE393CACB98B0708545255926B9CB33103567AAF2EE4D23B0540250A0832ED8FECB459561A2BFE1103A29CD4B23D53B27B707376FE0E67562F20003D2BAB55842CB855189CF690BA0E2C588AEFCD985F6E6B60C3CFCBDBF42FC68F702D17C1ACB4697C8032830CFA0099BE475C8DBDC63E2CE294795C8AAAF69EF908792AE219E3FE6B306490A7510AF6D9C981983DE7E4A83127F350451986EB2AF829A8C8BF483ADD195B5C7B08073D9548FFCDFB5626F0DA6A26468AD9BE54D5C8318AB016E948B6EE152EEC9B6AD07ADC102177C6060C5FA6AB774DE136D59657C62D596F9C9477FF09A14654D48AC94B9CF381221ED27DC26B0F224C70039771FACBC3B4D52DD7A762F752250E41218F5295C3DDFCFF43350759D74A62B695352CE3C302E8718BA21C85BED769AEF3846A88E691AE945D6EBA09F51ACB0143D10EBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEB0334032916F989086FB5C62722C096F0EC6A85808DB2BFA270227A72245BD7AC1B02522C52C88A6B0B1E9CF8943960E87722C1D5307069723C25B151939A58B08C10161A5552E5C1C256BA8489D1D621337C132BBECEF9840E3606582F918031F3E662B050C8F49D54C73A0F4C4D0B06DA93B81DC5463511E05913D9F2E0B5DB67D39B53A4AA164B45269B41B8F06E4254C47A55B7132E0310C626CE46DB6ABCB797", + "expectedError": { + "type": "FaultyCoordinatorError", + "message": "Coordinator replied with wrong pubnonce" + }, + "comment": "invalid cmsg1: pubnonces list has an invalid prefix at index 0 (own pubnonce)" + }, + { + "tcId": 41, + "cmsg1": "02F659A9668C1E7FB2BF4B457CA91C9925B3AB9F3F029C489C53603EF6AF90199B034619FE6A2FF09FE393CACB98B0708545255926B9CB33103567AAF2EE4D23B0540250A0832ED8FECB459561A2BFE1103A29CD4B23D53B27B707376FE0E67562F20003D2BAB55842CB855189CF690BA0E2C588AEFCD985F6E6B60C3CFCBDBF42FC68F702D17C1ACB4697C8032830CFA0099BE475C8DBDC63E2CE294795C8AAAF69EF908792AE219E3FE6B306490A7510AF6D9C981983DE7E4A83127F350451986EB2AF829A8C8BF483ADD195B5C7B08073D9548FFCDFB5626F0DA6A26468AD9BE54D5C8318AB016E948B6EE152EEC9B6AD07ADC102177C6060C5FA6AB774DE136D59657C62D596F9C9477FF09A14654D48AC94B9CF381221ED27DC26B0F224C70039771FACBC3B4D52DD7A762F752250E41218F5295C3DDFCFF43350759D74A62B695352CE3C302E8718BA21C85BED769AEF3846A88E691AE945D6EBA09F51ACB0143D10026C6EBBC55EDC86ABA62BA55A3375A940E731228F214937BF8A2FC58B29AE8F08EBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEB02522C52C88A6B0B1E9CF8943960E87722C1D5307069723C25B151939A58B08C10161A5552E5C1C256BA8489D1D621337C132BBECEF9840E3606582F918031F3E662B050C8F49D54C73A0F4C4D0B06DA93B81DC5463511E05913D9F2E0B5DB67D39B53A4AA164B45269B41B8F06E4254C47A55B7132E0310C626CE46DB6ABCB797", + "expectedError": { + "type": "FaultyParticipantOrCoordinatorError", + "message": "invalid public nonce", + "participantId": 1 + }, + "comment": "invalid cmsg1: pubnonces list has an invalid prefix at index 1" + }, + { + "tcId": 42, + "cmsg1": "02F659A9668C1E7FB2BF4B457CA91C9925B3AB9F3F029C489C53603EF6AF90199B034619FE6A2FF09FE393CACB98B0708545255926B9CB33103567AAF2EE4D23B0540250A0832ED8FECB459561A2BFE1103A29CD4B23D53B27B707376FE0E67562F20003D2BAB55842CB855189CF690BA0E2C588AEFCD985F6E6B60C3CFCBDBF42FC68F702D17C1ACB4697C8032830CFA0099BE475C8DBDC63E2CE294795C8AAAF69EF908792AE219E3FE6B306490A7510AF6D9C981983DE7E4A83127F350451986EB2AF829A8C8BF483ADD195B5C7B08073D9548FFCDFB5626F0DA6A26468AD9BE54D5C8318AB016E948B6EE152EEC9B6AD07ADC102177C6060C5FA6AB774DE136D59657C62D596F9C9477FF09A14654D48AC94B9CF381221ED27DC26B0F224C70039771FACBC3B4D52DD7A762F752250E41218F5295C3DDFCFF43350759D74A62B695352CE3C302E8718BA21C85BED769AEF3846A88E691AE945D6EBA09F51ACB0143D100300000000000000000000000000000000000000000000000000000000000000050334032916F989086FB5C62722C096F0EC6A85808DB2BFA270227A72245BD7AC1B02522C52C88A6B0B1E9CF8943960E87722C1D5307069723C25B151939A58B08C10161A5552E5C1C256BA8489D1D621337C132BBECEF9840E3606582F918031F3E662B050C8F49D54C73A0F4C4D0B06DA93B81DC5463511E05913D9F2E0B5DB67D39B53A4AA164B45269B41B8F06E4254C47A55B7132E0310C626CE46DB6ABCB797", + "expectedError": { + "type": "FaultyCoordinatorError", + "message": "Coordinator replied with wrong pubnonce" + }, + "comment": "invalid cmsg1: pubnonces list has an off-curve x-coordinate at index 0 (own pubnonce)" + }, + { + "tcId": 43, + "cmsg1": "02F659A9668C1E7FB2BF4B457CA91C9925B3AB9F3F029C489C53603EF6AF90199B034619FE6A2FF09FE393CACB98B0708545255926B9CB33103567AAF2EE4D23B0540250A0832ED8FECB459561A2BFE1103A29CD4B23D53B27B707376FE0E67562F20003D2BAB55842CB855189CF690BA0E2C588AEFCD985F6E6B60C3CFCBDBF42FC68F702D17C1ACB4697C8032830CFA0099BE475C8DBDC63E2CE294795C8AAAF69EF908792AE219E3FE6B306490A7510AF6D9C981983DE7E4A83127F350451986EB2AF829A8C8BF483ADD195B5C7B08073D9548FFCDFB5626F0DA6A26468AD9BE54D5C8318AB016E948B6EE152EEC9B6AD07ADC102177C6060C5FA6AB774DE136D59657C62D596F9C9477FF09A14654D48AC94B9CF381221ED27DC26B0F224C70039771FACBC3B4D52DD7A762F752250E41218F5295C3DDFCFF43350759D74A62B695352CE3C302E8718BA21C85BED769AEF3846A88E691AE945D6EBA09F51ACB0143D10026C6EBBC55EDC86ABA62BA55A3375A940E731228F214937BF8A2FC58B29AE8F0803000000000000000000000000000000000000000000000000000000000000000502522C52C88A6B0B1E9CF8943960E87722C1D5307069723C25B151939A58B08C10161A5552E5C1C256BA8489D1D621337C132BBECEF9840E3606582F918031F3E662B050C8F49D54C73A0F4C4D0B06DA93B81DC5463511E05913D9F2E0B5DB67D39B53A4AA164B45269B41B8F06E4254C47A55B7132E0310C626CE46DB6ABCB797", + "expectedError": { + "type": "FaultyParticipantOrCoordinatorError", + "message": "invalid public nonce", + "participantId": 1 + }, + "comment": "invalid cmsg1: pubnonces list has an off-curve x-coordinate at index 1" + }, + { + "tcId": 44, + "cmsg1": "02F659A9668C1E7FB2BF4B457CA91C9925B3AB9F3F029C489C53603EF6AF90199B034619FE6A2FF09FE393CACB98B0708545255926B9CB33103567AAF2EE4D23B0540250A0832ED8FECB459561A2BFE1103A29CD4B23D53B27B707376FE0E67562F20003D2BAB55842CB855189CF690BA0E2C588AEFCD985F6E6B60C3CFCBDBF42FC68F702D17C1ACB4697C8032830CFA0099BE475C8DBDC63E2CE294795C8AAAF69EF908792AE219E3FE6B306490A7510AF6D9C981983DE7E4A83127F350451986EB2AF829A8C8BF483ADD195B5C7B08073D9548FFCDFB5626F0DA6A26468AD9BE54D5C8318AB016E948B6EE152EEC9B6AD07ADC102177C6060C5FA6AB774DE136D59657C62D596F9C9477FF09A14654D48AC94B9CF381221ED27DC26B0F224C70039771FACBC3B4D52DD7A762F752250E41218F5295C3DDFCFF43350759D74A62B695352CE3C302E8718BA21C85BED769AEF3846A88E691AE945D6EBA09F51ACB0143D100260C301C1EEC41AD16BF53F55F97B7B6EB842D9E2B8139712BA54695FF71160730334032916F989086FB5C62722C096F0EC6A85808DB2BFA270227A72245BD7AC1B02522C52C88A6B0B1E9CF8943960E87722C1D5307069723C25B151939A58B08C10161A5552E5C1C256BA8489D1D621337C132BBECEF9840E3606582F918031F3E662B050C8F49D54C73A0F4C4D0B06DA93B81DC5463511E05913D9F2E0B5DB67D39B53A4AA164B45269B41B8F06E4254C47A55B7132E0310C626CE46DB6ABCB797", + "expectedError": { + "type": "FaultyCoordinatorError", + "message": "Coordinator replied with wrong pubnonce" + }, + "comment": "invalid cmsg1: pubnonces list has an arbitrary value at index 0 (own pubnonce)" + }, + { + "tcId": 45, + "cmsg1": "02F659A9668C1E7FB2BF4B457CA91C9925B3AB9F3F029C489C53603EF6AF90199B034619FE6A2FF09FE393CACB98B0708545255926B9CB33103567AAF2EE4D23B0540250A0832ED8FECB459561A2BFE1103A29CD4B23D53B27B707376FE0E67562F20003D2BAB55842CB855189CF690BA0E2C588AEFCD985F6E6B60C3CFCBDBF42FC68F702D17C1ACB4697C8032830CFA0099BE475C8DBDC63E2CE294795C8AAAF69EF908792AE219E3FE6B306490A7510AF6D9C981983DE7E4A83127F350451986EB2AF829A8C8BF483ADD195B5C7B08073D9548FFCDFB5626F0DA6A26468AD9BE54D5C8318AB016E948B6EE152EEC9B6AD07ADC102177C6060C5FA6AB774DE136D59657C62D596F9C9477FF09A14654D48AC94B9CF381221ED27DC26B0F224C70039771FACBC3B4D52DD7A762F752250E41218F5295C3DDFCFF43350759D74A62B695352CE3C302E8718BA21C85BED769AEF3846A88E691AE945D6EBA09F51ACB0143D10026C6EBBC55EDC86ABA62BA55A3375A940E731228F214937BF8A2FC58B29AE8F080260C301C1EEC41AD16BF53F55F97B7B6EB842D9E2B8139712BA54695FF711607302522C52C88A6B0B1E9CF8943960E87722C1D5307069723C25B151939A58B08C10161A5552E5C1C256BA8489D1D621337C132BBECEF9840E3606582F918031F3E662B050C8F49D54C73A0F4C4D0B06DA93B81DC5463511E05913D9F2E0B5DB67D39B53A4AA164B45269B41B8F06E4254C47A55B7132E0310C626CE46DB6ABCB797", + "expectedError": { + "type": "UnknownFaultyParticipantOrCoordinatorError", + "message": "Received invalid secshare; consider using participant_investigate() to determine a faulty party" + }, + "comment": "invalid cmsg1: pubnonces list has an arbitrary value at index 1" + }, + { + "tcId": 46, + "cmsg1": "02F659A9668C1E7FB2BF4B457CA91C9925B3AB9F3F029C489C53603EF6AF90199B034619FE6A2FF09FE393CACB98B0708545255926B9CB33103567AAF2EE4D23B0540250A0832ED8FECB459561A2BFE1103A29CD4B23D53B27B707376FE0E67562F20003D2BAB55842CB855189CF690BA0E2C588AEFCD985F6E6B60C3CFCBDBF42FC68F702D17C1ACB4697C8032830CFA0099BE475C8DBDC63E2CE294795C8AAAF69EF908792AE219E3FE6B306490A7510AF6D9C981983DE7E4A83127F350451986EB2AF829A8C8BF483ADD195B5C7B08073D9548FFCDFB5626F0DA6A26468AD9BE54D5C8318AB016E948B6EE152EEC9B6AD07ADC102177C6060C5FA6AB774DE136D59657C62D596F9C9477FF09A14654D48AC94B9CF381221ED27DC26B0F224C70039771FACBC3B4D52DD7A762F752250E41218F5295C3DDFCFF43350759D74A62B695352CE3C302E8718BA21C85BED769AEF3846A88E691AE945D6EBA09F51ACB0143D100000000000000000000000000000000000000000000000000000000000000000000334032916F989086FB5C62722C096F0EC6A85808DB2BFA270227A72245BD7AC1B02522C52C88A6B0B1E9CF8943960E87722C1D5307069723C25B151939A58B08C10161A5552E5C1C256BA8489D1D621337C132BBECEF9840E3606582F918031F3E662B050C8F49D54C73A0F4C4D0B06DA93B81DC5463511E05913D9F2E0B5DB67D39B53A4AA164B45269B41B8F06E4254C47A55B7132E0310C626CE46DB6ABCB797", + "expectedError": { + "type": "FaultyCoordinatorError", + "message": "Coordinator replied with wrong pubnonce" + }, + "comment": "invalid cmsg1: pubnonces list has the infinity point at index 0 (own pubnonce)" + }, + { + "tcId": 47, + "cmsg1": "02F659A9668C1E7FB2BF4B457CA91C9925B3AB9F3F029C489C53603EF6AF90199B034619FE6A2FF09FE393CACB98B0708545255926B9CB33103567AAF2EE4D23B0540250A0832ED8FECB459561A2BFE1103A29CD4B23D53B27B707376FE0E67562F20003D2BAB55842CB855189CF690BA0E2C588AEFCD985F6E6B60C3CFCBDBF42FC68F702D17C1ACB4697C8032830CFA0099BE475C8DBDC63E2CE294795C8AAAF69EF908792AE219E3FE6B306490A7510AF6D9C981983DE7E4A83127F350451986EB2AF829A8C8BF483ADD195B5C7B08073D9548FFCDFB5626F0DA6A26468AD9BE54D5C8318AB016E948B6EE152EEC9B6AD07ADC102177C6060C5FA6AB774DE136D59657C62D596F9C9477FF09A14654D48AC94B9CF381221ED27DC26B0F224C70039771FACBC3B4D52DD7A762F752250E41218F5295C3DDFCFF43350759D74A62B695352CE3C302E8718BA21C85BED769AEF3846A88E691AE945D6EBA09F51ACB0143D10026C6EBBC55EDC86ABA62BA55A3375A940E731228F214937BF8A2FC58B29AE8F0800000000000000000000000000000000000000000000000000000000000000000002522C52C88A6B0B1E9CF8943960E87722C1D5307069723C25B151939A58B08C10161A5552E5C1C256BA8489D1D621337C132BBECEF9840E3606582F918031F3E662B050C8F49D54C73A0F4C4D0B06DA93B81DC5463511E05913D9F2E0B5DB67D39B53A4AA164B45269B41B8F06E4254C47A55B7132E0310C626CE46DB6ABCB797", + "expectedError": { + "type": "FaultyParticipantOrCoordinatorError", + "message": "invalid public nonce", + "participantId": 1 + }, + "comment": "invalid cmsg1: pubnonces list has the infinity point at index 1" + }, + { + "tcId": 48, + "cmsg1": "02F659A9668C1E7FB2BF4B457CA91C9925B3AB9F3F029C489C53603EF6AF90199B034619FE6A2FF09FE393CACB98B0708545255926B9CB33103567AAF2EE4D23B0540250A0832ED8FECB459561A2BFE1103A29CD4B23D53B27B707376FE0E67562F20003D2BAB55842CB855189CF690BA0E2C588AEFCD985F6E6B60C3CFCBDBF42FC68F702D17C1ACB4697C8032830CFA0099BE475C8DBDC63E2CE294795C8AAAF69EF908792AE219E3FE6B306490A7510AF6D9C981983DE7E4A83127F350451986EB2AF829A8C8BF483ADD195B5C7B08073D9548FFCDFB5626F0DA6A26468AD9BE54D5C8318AB016E948B6EE152EEC9B6AD07ADC102177C6060C5FA6AB774DE136D59657C62D596F9C9477FF09A14654D48AC94B9CF381221ED27DC26B0F224C70039771FACBC3B4D52DD7A762F752250E41218F5295C3DDFCFF43350759D74A62B695352CE3C302E8718BA21C85BED769AEF3846A88E691AE945D6EBA09F51ACB0143D10026C6EBBC55EDC86ABA62BA55A3375A940E731228F214937BF8A2FC58B29AE8F08026C6EBBC55EDC86ABA62BA55A3375A940E731228F214937BF8A2FC58B29AE8F0802522C52C88A6B0B1E9CF8943960E87722C1D5307069723C25B151939A58B08C10161A5552E5C1C256BA8489D1D621337C132BBECEF9840E3606582F918031F3E662B050C8F49D54C73A0F4C4D0B06DA93B81DC5463511E05913D9F2E0B5DB67D39B53A4AA164B45269B41B8F06E4254C47A55B7132E0310C626CE46DB6ABCB797", + "expectedError": { + "type": "UnknownFaultyParticipantOrCoordinatorError", + "message": "Received invalid secshare; consider using participant_investigate() to determine a faulty party" + }, + "comment": "invalid cmsg1: pubnonces list has duplicate values" + }, + { + "tcId": 49, + "cmsg1": "02F659A9668C1E7FB2BF4B457CA91C9925B3AB9F3F029C489C53603EF6AF90199B034619FE6A2FF09FE393CACB98B0708545255926B9CB33103567AAF2EE4D23B0540250A0832ED8FECB459561A2BFE1103A29CD4B23D53B27B707376FE0E67562F20003D2BAB55842CB855189CF690BA0E2C588AEFCD985F6E6B60C3CFCBDBF42FC68F702D17C1ACB4697C8032830CFA0099BE475C8DBDC63E2CE294795C8AAAF69EF908792AE219E3FE6B306490A7510AF6D9C981983DE7E4A83127F350451986EB2AF829A8C8BF483ADD195B5C7B08073D9548FFCDFB5626F0DA6A26468AD9BE54D5C8318AB016E948B6EE152EEC9B6AD07ADC102177C6060C5FA6AB774DE136D59657C62D596F9C9477FF09A14654D48AC94B9CF381221ED27DC26B0F224C70039771FACBC3B4D52DD7A762F752250E41218F5295C3DDFCFF43350759D74A62B695352CE3C302E8718BA21C85BED769AEF3846A88E691AE945D6EBA09F51ACB0143D10026C6EBBC55EDC86ABA62BA55A3375A940E731228F214937BF8A2FC58B29AE8F080334032916F989086FB5C62722C096F0EC6A85808DB2BFA270227A72245BD7AC1B02522C52C88A6B0B1E9CF8943960E87722C1D5307069723C25B151939A58B08C10161A5552E5C1C256BA8489D1D621337C132BBECEF9840E3606582F918031F3E662B050C8F49D54C73A0F4C4D0B06DA93B81DC5463511E05913D9F2E0B5DB67D39B53A4AA164B45269B41B8F06E4254C47A55B7132E0310C626CE46DB6ABCB7", + "expectedError": { + "type": "ValueError" + }, + "comment": "invalid cmsg1: missing encrypted secret shares" + }, + { + "tcId": 50, + "cmsg1": "0260C301C1EEC41AD16BF53F55F97B7B6EB842D9E2B8139712BA54695FF7116073034619FE6A2FF09FE393CACB98B0708545255926B9CB33103567AAF2EE4D23B0540250A0832ED8FECB459561A2BFE1103A29CD4B23D53B27B707376FE0E67562F20003D2BAB55842CB855189CF690BA0E2C588AEFCD985F6E6B60C3CFCBDBF42FC68F702D17C1ACB4697C8032830CFA0099BE475C8DBDC63E2CE294795C8AAAF69EF908792AE219E3FE6B306490A7510AF6D9C981983DE7E4A83127F350451986EB2AF829A8C8BF483ADD195B5C7B08073D9548FFCDFB5626F0DA6A26468AD9BE54D5C8318AB016E948B6EE152EEC9B6AD07ADC102177C6060C5FA6AB774DE136D59657C62D596F9C9477FF09A14654D48AC94B9CF381221ED27DC26B0F224C70039771FACBC3B4D52DD7A762F752250E41218F5295C3DDFCFF43350759D74A62B695352CE3C302E8718BA21C85BED769AEF3846A88E691AE945D6EBA09F51ACB0143D10026C6EBBC55EDC86ABA62BA55A3375A940E731228F214937BF8A2FC58B29AE8F080334032916F989086FB5C62722C096F0EC6A85808DB2BFA270227A72245BD7AC1B02522C52C88A6B0B1E9CF8943960E87722C1D5307069723C25B151939A58B08C10161A5552E5C1C256BA8489D1D621337C132BBECEF9840E3606582F918031F3E662B050C8F49D54C73A0F4C4D0B06DA93B81DC5463511E05913D9F2E0B5DB67D39B53A4AA164B45269B41B8F06E4254C47A55B7132E0310C626CE46DB6ABCB797", + "expectedError": { + "type": "FaultyCoordinatorError", + "message": "Coordinator sent unexpected first group element for local participant id" + }, + "comment": "invalid cmsg1: coms_to_secrets list has an arbitrary value at index 0" + }, + { + "tcId": 51, + "cmsg1": "02F659A9668C1E7FB2BF4B457CA91C9925B3AB9F3F029C489C53603EF6AF90199B0000000000000000000000000000000000000000000000000000000000000000000250A0832ED8FECB459561A2BFE1103A29CD4B23D53B27B707376FE0E67562F20003D2BAB55842CB855189CF690BA0E2C588AEFCD985F6E6B60C3CFCBDBF42FC68F702D17C1ACB4697C8032830CFA0099BE475C8DBDC63E2CE294795C8AAAF69EF908792AE219E3FE6B306490A7510AF6D9C981983DE7E4A83127F350451986EB2AF829A8C8BF483ADD195B5C7B08073D9548FFCDFB5626F0DA6A26468AD9BE54D5C8318AB016E948B6EE152EEC9B6AD07ADC102177C6060C5FA6AB774DE136D59657C62D596F9C9477FF09A14654D48AC94B9CF381221ED27DC26B0F224C70039771FACBC3B4D52DD7A762F752250E41218F5295C3DDFCFF43350759D74A62B695352CE3C302E8718BA21C85BED769AEF3846A88E691AE945D6EBA09F51ACB0143D10026C6EBBC55EDC86ABA62BA55A3375A940E731228F214937BF8A2FC58B29AE8F080334032916F989086FB5C62722C096F0EC6A85808DB2BFA270227A72245BD7AC1B02522C52C88A6B0B1E9CF8943960E87722C1D5307069723C25B151939A58B08C10161A5552E5C1C256BA8489D1D621337C132BBECEF9840E3606582F918031F3E662B050C8F49D54C73A0F4C4D0B06DA93B81DC5463511E05913D9F2E0B5DB67D39B53A4AA164B45269B41B8F06E4254C47A55B7132E0310C626CE46DB6ABCB797", + "expectedError": { + "type": "FaultyParticipantOrCoordinatorError", + "message": "Participant sent invalid commitment", + "participantId": 1 + }, + "comment": "invalid cmsg1: coms_to_secrets list has infinity at index 1" + }, + { + "tcId": 52, + "cmsg1": "02F659A9668C1E7FB2BF4B457CA91C9925B3AB9F3F029C489C53603EF6AF90199B034619FE6A2FF09FE393CACB98B0708545255926B9CB33103567AAF2EE4D23B0540250A0832ED8FECB459561A2BFE1103A29CD4B23D53B27B707376FE0E67562F20003D2BAB55842CB855189CF690BA0E2C588AEFCD985F6E6B60C3CFCBDBF42FC68F702D17C1ACB4697C8032830CFA0099BE475C8DBDC63E2CE294795C8AAAF69EF908792AE219E3FE6B306490A7510AF6D9C981983DE7E4A83127F350451986EB2AF829A8C8BF483ADD195B5C7B08073D9548FFCDFB5626F0DA6A26468AD9BE54D5C8309C289578B96E6283AB13E4741FB489FC147FB1A5F446A314BA73C052131EFB04B83247A0BCEDF5205202AD64188B24B0BC5B51A17AEB218BD98DBE000C843B9ACBC3B4D52DD7A762F752250E41218F5295C3DDFCFF43350759D74A62B695352CE3C302E8718BA21C85BED769AEF3846A88E691AE945D6EBA09F51ACB0143D10026C6EBBC55EDC86ABA62BA55A3375A940E731228F214937BF8A2FC58B29AE8F080334032916F989086FB5C62722C096F0EC6A85808DB2BFA270227A72245BD7AC1B02522C52C88A6B0B1E9CF8943960E87722C1D5307069723C25B151939A58B08C10161A5552E5C1C256BA8489D1D621337C132BBECEF9840E3606582F918031F3E662B050C8F49D54C73A0F4C4D0B06DA93B81DC5463511E05913D9F2E0B5DB67D39B53A4AA164B45269B41B8F06E4254C47A55B7132E0310C626CE46DB6ABCB797", + "expectedError": { + "type": "FaultyParticipantOrCoordinatorError", + "message": "Participant sent invalid proof-of-knowledge", + "participantId": 1 + }, + "comment": "invalid cmsg1: pop list has an invalid value at index 1" + }, + { + "tcId": 53, + "cmsg1": "02F659A9668C1E7FB2BF4B457CA91C9925B3AB9F3F029C489C53603EF6AF90199B034619FE6A2FF09FE393CACB98B0708545255926B9CB33103567AAF2EE4D23B0540250A0832ED8FECB459561A2BFE1103A29CD4B23D53B27B707376FE0E67562F2000260C301C1EEC41AD16BF53F55F97B7B6EB842D9E2B8139712BA54695FF711607302D17C1ACB4697C8032830CFA0099BE475C8DBDC63E2CE294795C8AAAF69EF908792AE219E3FE6B306490A7510AF6D9C981983DE7E4A83127F350451986EB2AF829A8C8BF483ADD195B5C7B08073D9548FFCDFB5626F0DA6A26468AD9BE54D5C8318AB016E948B6EE152EEC9B6AD07ADC102177C6060C5FA6AB774DE136D59657C62D596F9C9477FF09A14654D48AC94B9CF381221ED27DC26B0F224C70039771FACBC3B4D52DD7A762F752250E41218F5295C3DDFCFF43350759D74A62B695352CE3C302E8718BA21C85BED769AEF3846A88E691AE945D6EBA09F51ACB0143D10026C6EBBC55EDC86ABA62BA55A3375A940E731228F214937BF8A2FC58B29AE8F080334032916F989086FB5C62722C096F0EC6A85808DB2BFA270227A72245BD7AC1B02522C52C88A6B0B1E9CF8943960E87722C1D5307069723C25B151939A58B08C10161A5552E5C1C256BA8489D1D621337C132BBECEF9840E3606582F918031F3E662B050C8F49D54C73A0F4C4D0B06DA93B81DC5463511E05913D9F2E0B5DB67D39B53A4AA164B45269B41B8F06E4254C47A55B7132E0310C626CE46DB6ABCB797", + "expectedError": { + "type": "UnknownFaultyParticipantOrCoordinatorError", + "message": "Received invalid secshare; consider using participant_investigate() to determine a faulty party" + }, + "comment": "invalid cmsg1: sum_coms_to_nonconst_terms has an arbitrary value at index 0" + }, + { + "tcId": 54, + "cmsg1": "02F659A9668C1E7FB2BF4B457CA91C9925B3AB9F3F029C489C53603EF6AF90199B034619FE6A2FF09FE393CACB98B0708545255926B9CB33103567AAF2EE4D23B0540250A0832ED8FECB459561A2BFE1103A29CD4B23D53B27B707376FE0E67562F20000000000000000000000000000000000000000000000000000000000000000000002D17C1ACB4697C8032830CFA0099BE475C8DBDC63E2CE294795C8AAAF69EF908792AE219E3FE6B306490A7510AF6D9C981983DE7E4A83127F350451986EB2AF829A8C8BF483ADD195B5C7B08073D9548FFCDFB5626F0DA6A26468AD9BE54D5C8318AB016E948B6EE152EEC9B6AD07ADC102177C6060C5FA6AB774DE136D59657C62D596F9C9477FF09A14654D48AC94B9CF381221ED27DC26B0F224C70039771FACBC3B4D52DD7A762F752250E41218F5295C3DDFCFF43350759D74A62B695352CE3C302E8718BA21C85BED769AEF3846A88E691AE945D6EBA09F51ACB0143D10026C6EBBC55EDC86ABA62BA55A3375A940E731228F214937BF8A2FC58B29AE8F080334032916F989086FB5C62722C096F0EC6A85808DB2BFA270227A72245BD7AC1B02522C52C88A6B0B1E9CF8943960E87722C1D5307069723C25B151939A58B08C10161A5552E5C1C256BA8489D1D621337C132BBECEF9840E3606582F918031F3E662B050C8F49D54C73A0F4C4D0B06DA93B81DC5463511E05913D9F2E0B5DB67D39B53A4AA164B45269B41B8F06E4254C47A55B7132E0310C626CE46DB6ABCB797", + "expectedError": { + "type": "UnknownFaultyParticipantOrCoordinatorError", + "message": "Received invalid secshare; consider using participant_investigate() to determine a faulty party" + }, + "comment": "invalid cmsg1: sum_coms_to_nonconst_terms has the infinity point at index 0" + }, + { + "tcId": 55, + "cmsg1": "02F659A9668C1E7FB2BF4B457CA91C9925B3AB9F3F029C489C53603EF6AF90199B034619FE6A2FF09FE393CACB98B0708545255926B9CB33103567AAF2EE4D23B0540250A0832ED8FECB459561A2BFE1103A29CD4B23D53B27B707376FE0E67562F20003D2BAB55842CB855189CF690BA0E2C588AEFCD985F6E6B60C3CFCBDBF42FC68F702D17C1ACB4697C8032830CFA0099BE475C8DBDC63E2CE294795C8AAAF69EF908792AE219E3FE6B306490A7510AF6D9C981983DE7E4A83127F350451986EB2AF829A8C8BF483ADD195B5C7B08073D9548FFCDFB5626F0DA6A26468AD9BE54D5C8318AB016E948B6EE152EEC9B6AD07ADC102177C6060C5FA6AB774DE136D59657C62D596F9C9477FF09A14654D48AC94B9CF381221ED27DC26B0F224C70039771FACBC3B4D52DD7A762F752250E41218F5295C3DDFCFF43350759D74A62B695352CE3C302E8718BA21C85BED769AEF3846A88E691AE945D6EBA09F51ACB0143D10026C6EBBC55EDC86ABA62BA55A3375A940E731228F214937BF8A2FC58B29AE8F080334032916F989086FB5C62722C096F0EC6A85808DB2BFA270227A72245BD7AC1B02522C52C88A6B0B1E9CF8943960E87722C1D5307069723C25B151939A58B08C10161A5552E5C1C256BA8489D1D621337C132BBECEF9840E3606582F918031F3F762B050C8F49D54C73A0F4C4D0B06DA93B81DC5463511E05913D9F2E0B5DB67D39B53A4AA164B45269B41B8F06E4254C47A55B7132E0310C626CE46DB6ABCB797", + "expectedError": { + "type": "UnknownFaultyParticipantOrCoordinatorError", + "message": "Received invalid secshare; consider using participant_investigate() to determine a faulty party" + }, + "comment": "invalid cmsg1: participant 1 sent an invalid secshare for participant 0" + } + ] + }, + { + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3", + "02A480743556B0DC66F3F25262A0120AC231DEB6936D4BF8FA37ECA83C395DC8A8" + ], + "t": 2 + }, + "hostseckey": "ADE179B2C56CB75868D44B333C16C89CB00DFDE378AD79C84D0CCE856E4F9207", + "random": "42B53D62E27380D6F7096EDA1C28C57DDB89FCD4CE5B843EDAC220E165B5A7EC", + "auxRand": "005F5C3A69BB274F4559490AD754F1F5AFFABAED4C71AD5D8ACBAEFC2B491573", + "pmsg1": "037ACDA3FDC4E9098EA3364B92CD6DD02654141995CFD3500E1C33D098F618458D024F105837D6A403410B38453D712F306673B4B4DAC9F38C8FCF92A2D7BBDB2CE2082BB8A48B5B6088DC14C4E231236B929B68DBDA2F43842D8CCD7812812EF26C6490104A19A7CF603530A4A96E5F9A52E58A22C61B2B5B93C453BB11509CEF460266D2D26EAA94E40ECCF9C6BB24C1DDE27957FE8A82F2389EA6BBFD4B265ACAD5C2BC4B8326FF7015CEC59F160D74C4911E39F1C703FE64ACC5D0258A814EEAE235412ABEFA95191200CDA1F3D9F14976F2401C387A1B5FEDCEE9AE829D8DB005B6584DD5D478D2B1CEE49B2B8FB20EFCDC2BB9DD7B7F75B13ACBF819369AC43D4D8A4D5D1E3E00CB20D4CEFB1FD632C252F5D8048F295A5C5BD62B83145D9135", + "validTestCases": [ + { + "tcId": 56, + "cmsg1": "037ACDA3FDC4E9098EA3364B92CD6DD02654141995CFD3500E1C33D098F618458D034D3BEF06BFB2780C527B2A16CC2819160C8569EB94AF89F16B4F81C604E094170246EC025D2E096D23F52B755D1DED61C58608B23C67087E90555F52C428AFA61B0244EEC31E9105EB71FDDD1F777889003AD61D6C3116EAACF48B23F885AA617F13037B0F4758018C2AC0DC96FF1A6F6DB042B302717D1911B65B4419CAE29D79EBD9082BB8A48B5B6088DC14C4E231236B929B68DBDA2F43842D8CCD7812812EF26C6490104A19A7CF603530A4A96E5F9A52E58A22C61B2B5B93C453BB11509CEF4662DADAF9B9E9DC0E28322D2FB7F459D3F76D8739CE5E48667B9831813B90B9EB4F0825157039DE1B1C0C9DB2F52C4F51E112A113E2F436FF61CF94254B3E2A260BCEDABBF632561F24A4BECEBE1F22F9260C830E17EFB80D88EA5919C98871B78AA91A74C8F2B20F83FEB22B9599E9B416D49706181998EC04E3FDF0AAEB789F9777334881108C5C19F548963F3BF8F5E693969A30B2F749498262D8AC956E0D91EB42FB355114D3F0589AFBF10289A015EF05D1DF365BD1A0468582C07067600266D2D26EAA94E40ECCF9C6BB24C1DDE27957FE8A82F2389EA6BBFD4B265ACAD5036DEF7297E97F0D229BBB7B004AABEC90F7E4A769934376ED220FD78D79C3095C0251B5F3A899402AAE586113B18A5B2D1F12A49330AEDD5AC2314FF07EB151A9B2024461A885A565BD85A57B3047CBEE3D70C7E9C400C5FB2C0146F02944AB36C42E1715B884DD47347692CE0EEB35DDFFAF50746670244989D422CC8B64D884E0FC7764E8F01F2EA21D679CADDC011227835F29BB3B5CC3DFBAAE992C8E1310E7AB8349E55B996BAC1B39496EF23E0ED7DC95F3FF8E50711DEA5CC6D9238D71AC9441E0CDE02F944CEA3B398EDB0CEAAA373E0B9F6994FF46A854C8C86243D2CE91", + "expectedPmsg2": "C40687CB309D263B5D52C4D782F9A66411B57627FD56FD0ABE3C87CB051CE248E19A95E68212599B8DAD04ADF7CE37DC809BBC04F27B019D5543F35223FC53E6", + "comment": "valid participant step2" + } + ], + "errorTestCases": [ + { + "tcId": 57, + "auxRand": "42B53D62E27380D6F7096EDA1C28C57D", + "cmsg1": "037ACDA3FDC4E9098EA3364B92CD6DD02654141995CFD3500E1C33D098F618458D034D3BEF06BFB2780C527B2A16CC2819160C8569EB94AF89F16B4F81C604E094170246EC025D2E096D23F52B755D1DED61C58608B23C67087E90555F52C428AFA61B0244EEC31E9105EB71FDDD1F777889003AD61D6C3116EAACF48B23F885AA617F13037B0F4758018C2AC0DC96FF1A6F6DB042B302717D1911B65B4419CAE29D79EBD9082BB8A48B5B6088DC14C4E231236B929B68DBDA2F43842D8CCD7812812EF26C6490104A19A7CF603530A4A96E5F9A52E58A22C61B2B5B93C453BB11509CEF4662DADAF9B9E9DC0E28322D2FB7F459D3F76D8739CE5E48667B9831813B90B9EB4F0825157039DE1B1C0C9DB2F52C4F51E112A113E2F436FF61CF94254B3E2A260BCEDABBF632561F24A4BECEBE1F22F9260C830E17EFB80D88EA5919C98871B78AA91A74C8F2B20F83FEB22B9599E9B416D49706181998EC04E3FDF0AAEB789F9777334881108C5C19F548963F3BF8F5E693969A30B2F749498262D8AC956E0D91EB42FB355114D3F0589AFBF10289A015EF05D1DF365BD1A0468582C07067600266D2D26EAA94E40ECCF9C6BB24C1DDE27957FE8A82F2389EA6BBFD4B265ACAD5036DEF7297E97F0D229BBB7B004AABEC90F7E4A769934376ED220FD78D79C3095C0251B5F3A899402AAE586113B18A5B2D1F12A49330AEDD5AC2314FF07EB151A9B2024461A885A565BD85A57B3047CBEE3D70C7E9C400C5FB2C0146F02944AB36C42E1715B884DD47347692CE0EEB35DDFFAF50746670244989D422CC8B64D884E0FC7764E8F01F2EA21D679CADDC011227835F29BB3B5CC3DFBAAE992C8E1310E7AB8349E55B996BAC1B39496EF23E0ED7DC95F3FF8E50711DEA5CC6D9238D71AC9441E0CDE02F944CEA3B398EDB0CEAAA373E0B9F6994FF46A854C8C86243D2CE91", + "expectedError": { + "type": "ValueError" + }, + "comment": "length of aux randomness is not 32 bytes" + }, + { + "tcId": 58, + "hostseckey": "94BB10C1DE15783C3F3E49167A0951CACD2803F13AAC456C816E88AB4AC76330", + "cmsg1": "037ACDA3FDC4E9098EA3364B92CD6DD02654141995CFD3500E1C33D098F618458D034D3BEF06BFB2780C527B2A16CC2819160C8569EB94AF89F16B4F81C604E094170246EC025D2E096D23F52B755D1DED61C58608B23C67087E90555F52C428AFA61B0244EEC31E9105EB71FDDD1F777889003AD61D6C3116EAACF48B23F885AA617F13037B0F4758018C2AC0DC96FF1A6F6DB042B302717D1911B65B4419CAE29D79EBD9082BB8A48B5B6088DC14C4E231236B929B68DBDA2F43842D8CCD7812812EF26C6490104A19A7CF603530A4A96E5F9A52E58A22C61B2B5B93C453BB11509CEF4662DADAF9B9E9DC0E28322D2FB7F459D3F76D8739CE5E48667B9831813B90B9EB4F0825157039DE1B1C0C9DB2F52C4F51E112A113E2F436FF61CF94254B3E2A260BCEDABBF632561F24A4BECEBE1F22F9260C830E17EFB80D88EA5919C98871B78AA91A74C8F2B20F83FEB22B9599E9B416D49706181998EC04E3FDF0AAEB789F9777334881108C5C19F548963F3BF8F5E693969A30B2F749498262D8AC956E0D91EB42FB355114D3F0589AFBF10289A015EF05D1DF365BD1A0468582C07067600266D2D26EAA94E40ECCF9C6BB24C1DDE27957FE8A82F2389EA6BBFD4B265ACAD5036DEF7297E97F0D229BBB7B004AABEC90F7E4A769934376ED220FD78D79C3095C0251B5F3A899402AAE586113B18A5B2D1F12A49330AEDD5AC2314FF07EB151A9B2024461A885A565BD85A57B3047CBEE3D70C7E9C400C5FB2C0146F02944AB36C42E1715B884DD47347692CE0EEB35DDFFAF50746670244989D422CC8B64D884E0FC7764E8F01F2EA21D679CADDC011227835F29BB3B5CC3DFBAAE992C8E1310E7AB8349E55B996BAC1B39496EF23E0ED7DC95F3FF8E50711DEA5CC6D9238D71AC9441E0CDE02F944CEA3B398EDB0CEAAA373E0B9F6994FF46A854C8C86243D2CE91", + "expectedError": { + "type": "HostSeckeyError", + "message": "Host secret key does not match the one used in participant_step1" + }, + "comment": "hostseckey does not match the one used in participant_step1" + }, + { + "tcId": 59, + "cmsg1": "037ACDA3FDC4E9098EA3364B92CD6DD02654141995CFD3500E1C33D098F618458D034D3BEF06BFB2780C527B2A16CC2819160C8569EB94AF89F16B4F81C604E094170246EC025D2E096D23F52B755D1DED61C58608B23C67087E90555F52C428AFA61B0244EEC31E9105EB71FDDD1F777889003AD61D6C3116EAACF48B23F885AA617F13037B0F4758018C2AC0DC96FF1A6F6DB042B302717D1911B65B4419CAE29D79EBD9082BB8A48B5B6088DC14C4E231236B929B68DBDA2F43842D8CCD7812812EF26C6490104A19A7CF603530A4A96E5F9A52E58A22C61B2B5B93C453BB11509CEF4662DADAF9B9E9DC0E28322D2FB7F459D3F76D8739CE5E48667B9831813B90B9EB4F0825157039DE1B1C0C9DB2F52C4F51E112A113E2F436FF61CF94254B3E2A260BCEDABBF632561F24A4BECEBE1F22F9260C830E17EFB80D88EA5919C98871B78AA91A74C8F2B20F83FEB22B9599E9B416D49706181998EC04E3FDF0AAEB789F9777334881108C5C19F548963F3BF8F5E693969A30B2F749498262D8AC956E0D91EB42FB355114D3F0589AFBF10289A015EF05D1DF365BD1A0468582C0706760EBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEB036DEF7297E97F0D229BBB7B004AABEC90F7E4A769934376ED220FD78D79C3095C0251B5F3A899402AAE586113B18A5B2D1F12A49330AEDD5AC2314FF07EB151A9B2024461A885A565BD85A57B3047CBEE3D70C7E9C400C5FB2C0146F02944AB36C42E1715B884DD47347692CE0EEB35DDFFAF50746670244989D422CC8B64D884E0FC7764E8F01F2EA21D679CADDC011227835F29BB3B5CC3DFBAAE992C8E1310E7AB8349E55B996BAC1B39496EF23E0ED7DC95F3FF8E50711DEA5CC6D9238D71AC9441E0CDE02F944CEA3B398EDB0CEAAA373E0B9F6994FF46A854C8C86243D2CE91", + "expectedError": { + "type": "FaultyCoordinatorError", + "message": "Coordinator replied with wrong pubnonce" + }, + "comment": "invalid cmsg1: pubnonces list has an invalid prefix at index 0 (own pubnonce)" + }, + { + "tcId": 60, + "cmsg1": "037ACDA3FDC4E9098EA3364B92CD6DD02654141995CFD3500E1C33D098F618458D034D3BEF06BFB2780C527B2A16CC2819160C8569EB94AF89F16B4F81C604E094170246EC025D2E096D23F52B755D1DED61C58608B23C67087E90555F52C428AFA61B0244EEC31E9105EB71FDDD1F777889003AD61D6C3116EAACF48B23F885AA617F13037B0F4758018C2AC0DC96FF1A6F6DB042B302717D1911B65B4419CAE29D79EBD9082BB8A48B5B6088DC14C4E231236B929B68DBDA2F43842D8CCD7812812EF26C6490104A19A7CF603530A4A96E5F9A52E58A22C61B2B5B93C453BB11509CEF4662DADAF9B9E9DC0E28322D2FB7F459D3F76D8739CE5E48667B9831813B90B9EB4F0825157039DE1B1C0C9DB2F52C4F51E112A113E2F436FF61CF94254B3E2A260BCEDABBF632561F24A4BECEBE1F22F9260C830E17EFB80D88EA5919C98871B78AA91A74C8F2B20F83FEB22B9599E9B416D49706181998EC04E3FDF0AAEB789F9777334881108C5C19F548963F3BF8F5E693969A30B2F749498262D8AC956E0D91EB42FB355114D3F0589AFBF10289A015EF05D1DF365BD1A0468582C07067600266D2D26EAA94E40ECCF9C6BB24C1DDE27957FE8A82F2389EA6BBFD4B265ACAD5EBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEBEB0251B5F3A899402AAE586113B18A5B2D1F12A49330AEDD5AC2314FF07EB151A9B2024461A885A565BD85A57B3047CBEE3D70C7E9C400C5FB2C0146F02944AB36C42E1715B884DD47347692CE0EEB35DDFFAF50746670244989D422CC8B64D884E0FC7764E8F01F2EA21D679CADDC011227835F29BB3B5CC3DFBAAE992C8E1310E7AB8349E55B996BAC1B39496EF23E0ED7DC95F3FF8E50711DEA5CC6D9238D71AC9441E0CDE02F944CEA3B398EDB0CEAAA373E0B9F6994FF46A854C8C86243D2CE91", + "expectedError": { + "type": "FaultyParticipantOrCoordinatorError", + "message": "invalid public nonce", + "participantId": 1 + }, + "comment": "invalid cmsg1: pubnonces list has an invalid prefix at index 1" + }, + { + "tcId": 61, + "cmsg1": "037ACDA3FDC4E9098EA3364B92CD6DD02654141995CFD3500E1C33D098F618458D034D3BEF06BFB2780C527B2A16CC2819160C8569EB94AF89F16B4F81C604E094170246EC025D2E096D23F52B755D1DED61C58608B23C67087E90555F52C428AFA61B0244EEC31E9105EB71FDDD1F777889003AD61D6C3116EAACF48B23F885AA617F13037B0F4758018C2AC0DC96FF1A6F6DB042B302717D1911B65B4419CAE29D79EBD9082BB8A48B5B6088DC14C4E231236B929B68DBDA2F43842D8CCD7812812EF26C6490104A19A7CF603530A4A96E5F9A52E58A22C61B2B5B93C453BB11509CEF4662DADAF9B9E9DC0E28322D2FB7F459D3F76D8739CE5E48667B9831813B90B9EB4F0825157039DE1B1C0C9DB2F52C4F51E112A113E2F436FF61CF94254B3E2A260BCEDABBF632561F24A4BECEBE1F22F9260C830E17EFB80D88EA5919C98871B78AA91A74C8F2B20F83FEB22B9599E9B416D49706181998EC04E3FDF0AAEB789F9777334881108C5C19F548963F3BF8F5E693969A30B2F749498262D8AC956E0D91EB42FB355114D3F0589AFBF10289A015EF05D1DF365BD1A0468582C0706760030000000000000000000000000000000000000000000000000000000000000005036DEF7297E97F0D229BBB7B004AABEC90F7E4A769934376ED220FD78D79C3095C0251B5F3A899402AAE586113B18A5B2D1F12A49330AEDD5AC2314FF07EB151A9B2024461A885A565BD85A57B3047CBEE3D70C7E9C400C5FB2C0146F02944AB36C42E1715B884DD47347692CE0EEB35DDFFAF50746670244989D422CC8B64D884E0FC7764E8F01F2EA21D679CADDC011227835F29BB3B5CC3DFBAAE992C8E1310E7AB8349E55B996BAC1B39496EF23E0ED7DC95F3FF8E50711DEA5CC6D9238D71AC9441E0CDE02F944CEA3B398EDB0CEAAA373E0B9F6994FF46A854C8C86243D2CE91", + "expectedError": { + "type": "FaultyCoordinatorError", + "message": "Coordinator replied with wrong pubnonce" + }, + "comment": "invalid cmsg1: pubnonces list has an off-curve x-coordinate at index 0 (own pubnonce)" + }, + { + "tcId": 62, + "cmsg1": "037ACDA3FDC4E9098EA3364B92CD6DD02654141995CFD3500E1C33D098F618458D034D3BEF06BFB2780C527B2A16CC2819160C8569EB94AF89F16B4F81C604E094170246EC025D2E096D23F52B755D1DED61C58608B23C67087E90555F52C428AFA61B0244EEC31E9105EB71FDDD1F777889003AD61D6C3116EAACF48B23F885AA617F13037B0F4758018C2AC0DC96FF1A6F6DB042B302717D1911B65B4419CAE29D79EBD9082BB8A48B5B6088DC14C4E231236B929B68DBDA2F43842D8CCD7812812EF26C6490104A19A7CF603530A4A96E5F9A52E58A22C61B2B5B93C453BB11509CEF4662DADAF9B9E9DC0E28322D2FB7F459D3F76D8739CE5E48667B9831813B90B9EB4F0825157039DE1B1C0C9DB2F52C4F51E112A113E2F436FF61CF94254B3E2A260BCEDABBF632561F24A4BECEBE1F22F9260C830E17EFB80D88EA5919C98871B78AA91A74C8F2B20F83FEB22B9599E9B416D49706181998EC04E3FDF0AAEB789F9777334881108C5C19F548963F3BF8F5E693969A30B2F749498262D8AC956E0D91EB42FB355114D3F0589AFBF10289A015EF05D1DF365BD1A0468582C07067600266D2D26EAA94E40ECCF9C6BB24C1DDE27957FE8A82F2389EA6BBFD4B265ACAD50300000000000000000000000000000000000000000000000000000000000000050251B5F3A899402AAE586113B18A5B2D1F12A49330AEDD5AC2314FF07EB151A9B2024461A885A565BD85A57B3047CBEE3D70C7E9C400C5FB2C0146F02944AB36C42E1715B884DD47347692CE0EEB35DDFFAF50746670244989D422CC8B64D884E0FC7764E8F01F2EA21D679CADDC011227835F29BB3B5CC3DFBAAE992C8E1310E7AB8349E55B996BAC1B39496EF23E0ED7DC95F3FF8E50711DEA5CC6D9238D71AC9441E0CDE02F944CEA3B398EDB0CEAAA373E0B9F6994FF46A854C8C86243D2CE91", + "expectedError": { + "type": "FaultyParticipantOrCoordinatorError", + "message": "invalid public nonce", + "participantId": 1 + }, + "comment": "invalid cmsg1: pubnonces list has an off-curve x-coordinate at index 1" + }, + { + "tcId": 63, + "cmsg1": "037ACDA3FDC4E9098EA3364B92CD6DD02654141995CFD3500E1C33D098F618458D034D3BEF06BFB2780C527B2A16CC2819160C8569EB94AF89F16B4F81C604E094170246EC025D2E096D23F52B755D1DED61C58608B23C67087E90555F52C428AFA61B0244EEC31E9105EB71FDDD1F777889003AD61D6C3116EAACF48B23F885AA617F13037B0F4758018C2AC0DC96FF1A6F6DB042B302717D1911B65B4419CAE29D79EBD9082BB8A48B5B6088DC14C4E231236B929B68DBDA2F43842D8CCD7812812EF26C6490104A19A7CF603530A4A96E5F9A52E58A22C61B2B5B93C453BB11509CEF4662DADAF9B9E9DC0E28322D2FB7F459D3F76D8739CE5E48667B9831813B90B9EB4F0825157039DE1B1C0C9DB2F52C4F51E112A113E2F436FF61CF94254B3E2A260BCEDABBF632561F24A4BECEBE1F22F9260C830E17EFB80D88EA5919C98871B78AA91A74C8F2B20F83FEB22B9599E9B416D49706181998EC04E3FDF0AAEB789F9777334881108C5C19F548963F3BF8F5E693969A30B2F749498262D8AC956E0D91EB42FB355114D3F0589AFBF10289A015EF05D1DF365BD1A0468582C07067600260C301C1EEC41AD16BF53F55F97B7B6EB842D9E2B8139712BA54695FF7116073036DEF7297E97F0D229BBB7B004AABEC90F7E4A769934376ED220FD78D79C3095C0251B5F3A899402AAE586113B18A5B2D1F12A49330AEDD5AC2314FF07EB151A9B2024461A885A565BD85A57B3047CBEE3D70C7E9C400C5FB2C0146F02944AB36C42E1715B884DD47347692CE0EEB35DDFFAF50746670244989D422CC8B64D884E0FC7764E8F01F2EA21D679CADDC011227835F29BB3B5CC3DFBAAE992C8E1310E7AB8349E55B996BAC1B39496EF23E0ED7DC95F3FF8E50711DEA5CC6D9238D71AC9441E0CDE02F944CEA3B398EDB0CEAAA373E0B9F6994FF46A854C8C86243D2CE91", + "expectedError": { + "type": "FaultyCoordinatorError", + "message": "Coordinator replied with wrong pubnonce" + }, + "comment": "invalid cmsg1: pubnonces list has an arbitrary value at index 0 (own pubnonce)" + }, + { + "tcId": 64, + "cmsg1": "037ACDA3FDC4E9098EA3364B92CD6DD02654141995CFD3500E1C33D098F618458D034D3BEF06BFB2780C527B2A16CC2819160C8569EB94AF89F16B4F81C604E094170246EC025D2E096D23F52B755D1DED61C58608B23C67087E90555F52C428AFA61B0244EEC31E9105EB71FDDD1F777889003AD61D6C3116EAACF48B23F885AA617F13037B0F4758018C2AC0DC96FF1A6F6DB042B302717D1911B65B4419CAE29D79EBD9082BB8A48B5B6088DC14C4E231236B929B68DBDA2F43842D8CCD7812812EF26C6490104A19A7CF603530A4A96E5F9A52E58A22C61B2B5B93C453BB11509CEF4662DADAF9B9E9DC0E28322D2FB7F459D3F76D8739CE5E48667B9831813B90B9EB4F0825157039DE1B1C0C9DB2F52C4F51E112A113E2F436FF61CF94254B3E2A260BCEDABBF632561F24A4BECEBE1F22F9260C830E17EFB80D88EA5919C98871B78AA91A74C8F2B20F83FEB22B9599E9B416D49706181998EC04E3FDF0AAEB789F9777334881108C5C19F548963F3BF8F5E693969A30B2F749498262D8AC956E0D91EB42FB355114D3F0589AFBF10289A015EF05D1DF365BD1A0468582C07067600266D2D26EAA94E40ECCF9C6BB24C1DDE27957FE8A82F2389EA6BBFD4B265ACAD50260C301C1EEC41AD16BF53F55F97B7B6EB842D9E2B8139712BA54695FF71160730251B5F3A899402AAE586113B18A5B2D1F12A49330AEDD5AC2314FF07EB151A9B2024461A885A565BD85A57B3047CBEE3D70C7E9C400C5FB2C0146F02944AB36C42E1715B884DD47347692CE0EEB35DDFFAF50746670244989D422CC8B64D884E0FC7764E8F01F2EA21D679CADDC011227835F29BB3B5CC3DFBAAE992C8E1310E7AB8349E55B996BAC1B39496EF23E0ED7DC95F3FF8E50711DEA5CC6D9238D71AC9441E0CDE02F944CEA3B398EDB0CEAAA373E0B9F6994FF46A854C8C86243D2CE91", + "expectedError": { + "type": "UnknownFaultyParticipantOrCoordinatorError", + "message": "Received invalid secshare; consider using participant_investigate() to determine a faulty party" + }, + "comment": "invalid cmsg1: pubnonces list has an arbitrary value at index 1" + }, + { + "tcId": 65, + "cmsg1": "037ACDA3FDC4E9098EA3364B92CD6DD02654141995CFD3500E1C33D098F618458D034D3BEF06BFB2780C527B2A16CC2819160C8569EB94AF89F16B4F81C604E094170246EC025D2E096D23F52B755D1DED61C58608B23C67087E90555F52C428AFA61B0244EEC31E9105EB71FDDD1F777889003AD61D6C3116EAACF48B23F885AA617F13037B0F4758018C2AC0DC96FF1A6F6DB042B302717D1911B65B4419CAE29D79EBD9082BB8A48B5B6088DC14C4E231236B929B68DBDA2F43842D8CCD7812812EF26C6490104A19A7CF603530A4A96E5F9A52E58A22C61B2B5B93C453BB11509CEF4662DADAF9B9E9DC0E28322D2FB7F459D3F76D8739CE5E48667B9831813B90B9EB4F0825157039DE1B1C0C9DB2F52C4F51E112A113E2F436FF61CF94254B3E2A260BCEDABBF632561F24A4BECEBE1F22F9260C830E17EFB80D88EA5919C98871B78AA91A74C8F2B20F83FEB22B9599E9B416D49706181998EC04E3FDF0AAEB789F9777334881108C5C19F548963F3BF8F5E693969A30B2F749498262D8AC956E0D91EB42FB355114D3F0589AFBF10289A015EF05D1DF365BD1A0468582C0706760000000000000000000000000000000000000000000000000000000000000000000036DEF7297E97F0D229BBB7B004AABEC90F7E4A769934376ED220FD78D79C3095C0251B5F3A899402AAE586113B18A5B2D1F12A49330AEDD5AC2314FF07EB151A9B2024461A885A565BD85A57B3047CBEE3D70C7E9C400C5FB2C0146F02944AB36C42E1715B884DD47347692CE0EEB35DDFFAF50746670244989D422CC8B64D884E0FC7764E8F01F2EA21D679CADDC011227835F29BB3B5CC3DFBAAE992C8E1310E7AB8349E55B996BAC1B39496EF23E0ED7DC95F3FF8E50711DEA5CC6D9238D71AC9441E0CDE02F944CEA3B398EDB0CEAAA373E0B9F6994FF46A854C8C86243D2CE91", + "expectedError": { + "type": "FaultyCoordinatorError", + "message": "Coordinator replied with wrong pubnonce" + }, + "comment": "invalid cmsg1: pubnonces list has the infinity point at index 0 (own pubnonce)" + }, + { + "tcId": 66, + "cmsg1": "037ACDA3FDC4E9098EA3364B92CD6DD02654141995CFD3500E1C33D098F618458D034D3BEF06BFB2780C527B2A16CC2819160C8569EB94AF89F16B4F81C604E094170246EC025D2E096D23F52B755D1DED61C58608B23C67087E90555F52C428AFA61B0244EEC31E9105EB71FDDD1F777889003AD61D6C3116EAACF48B23F885AA617F13037B0F4758018C2AC0DC96FF1A6F6DB042B302717D1911B65B4419CAE29D79EBD9082BB8A48B5B6088DC14C4E231236B929B68DBDA2F43842D8CCD7812812EF26C6490104A19A7CF603530A4A96E5F9A52E58A22C61B2B5B93C453BB11509CEF4662DADAF9B9E9DC0E28322D2FB7F459D3F76D8739CE5E48667B9831813B90B9EB4F0825157039DE1B1C0C9DB2F52C4F51E112A113E2F436FF61CF94254B3E2A260BCEDABBF632561F24A4BECEBE1F22F9260C830E17EFB80D88EA5919C98871B78AA91A74C8F2B20F83FEB22B9599E9B416D49706181998EC04E3FDF0AAEB789F9777334881108C5C19F548963F3BF8F5E693969A30B2F749498262D8AC956E0D91EB42FB355114D3F0589AFBF10289A015EF05D1DF365BD1A0468582C07067600266D2D26EAA94E40ECCF9C6BB24C1DDE27957FE8A82F2389EA6BBFD4B265ACAD50000000000000000000000000000000000000000000000000000000000000000000251B5F3A899402AAE586113B18A5B2D1F12A49330AEDD5AC2314FF07EB151A9B2024461A885A565BD85A57B3047CBEE3D70C7E9C400C5FB2C0146F02944AB36C42E1715B884DD47347692CE0EEB35DDFFAF50746670244989D422CC8B64D884E0FC7764E8F01F2EA21D679CADDC011227835F29BB3B5CC3DFBAAE992C8E1310E7AB8349E55B996BAC1B39496EF23E0ED7DC95F3FF8E50711DEA5CC6D9238D71AC9441E0CDE02F944CEA3B398EDB0CEAAA373E0B9F6994FF46A854C8C86243D2CE91", + "expectedError": { + "type": "FaultyParticipantOrCoordinatorError", + "message": "invalid public nonce", + "participantId": 1 + }, + "comment": "invalid cmsg1: pubnonces list has the infinity point at index 1" + }, + { + "tcId": 67, + "cmsg1": "037ACDA3FDC4E9098EA3364B92CD6DD02654141995CFD3500E1C33D098F618458D034D3BEF06BFB2780C527B2A16CC2819160C8569EB94AF89F16B4F81C604E094170246EC025D2E096D23F52B755D1DED61C58608B23C67087E90555F52C428AFA61B0244EEC31E9105EB71FDDD1F777889003AD61D6C3116EAACF48B23F885AA617F13037B0F4758018C2AC0DC96FF1A6F6DB042B302717D1911B65B4419CAE29D79EBD9082BB8A48B5B6088DC14C4E231236B929B68DBDA2F43842D8CCD7812812EF26C6490104A19A7CF603530A4A96E5F9A52E58A22C61B2B5B93C453BB11509CEF4662DADAF9B9E9DC0E28322D2FB7F459D3F76D8739CE5E48667B9831813B90B9EB4F0825157039DE1B1C0C9DB2F52C4F51E112A113E2F436FF61CF94254B3E2A260BCEDABBF632561F24A4BECEBE1F22F9260C830E17EFB80D88EA5919C98871B78AA91A74C8F2B20F83FEB22B9599E9B416D49706181998EC04E3FDF0AAEB789F9777334881108C5C19F548963F3BF8F5E693969A30B2F749498262D8AC956E0D91EB42FB355114D3F0589AFBF10289A015EF05D1DF365BD1A0468582C07067600266D2D26EAA94E40ECCF9C6BB24C1DDE27957FE8A82F2389EA6BBFD4B265ACAD50266D2D26EAA94E40ECCF9C6BB24C1DDE27957FE8A82F2389EA6BBFD4B265ACAD50251B5F3A899402AAE586113B18A5B2D1F12A49330AEDD5AC2314FF07EB151A9B2024461A885A565BD85A57B3047CBEE3D70C7E9C400C5FB2C0146F02944AB36C42E1715B884DD47347692CE0EEB35DDFFAF50746670244989D422CC8B64D884E0FC7764E8F01F2EA21D679CADDC011227835F29BB3B5CC3DFBAAE992C8E1310E7AB8349E55B996BAC1B39496EF23E0ED7DC95F3FF8E50711DEA5CC6D9238D71AC9441E0CDE02F944CEA3B398EDB0CEAAA373E0B9F6994FF46A854C8C86243D2CE91", + "expectedError": { + "type": "UnknownFaultyParticipantOrCoordinatorError", + "message": "Received invalid secshare; consider using participant_investigate() to determine a faulty party" + }, + "comment": "invalid cmsg1: pubnonces list has duplicate values" + }, + { + "tcId": 68, + "cmsg1": "037ACDA3FDC4E9098EA3364B92CD6DD02654141995CFD3500E1C33D098F618458D034D3BEF06BFB2780C527B2A16CC2819160C8569EB94AF89F16B4F81C604E094170246EC025D2E096D23F52B755D1DED61C58608B23C67087E90555F52C428AFA61B0244EEC31E9105EB71FDDD1F777889003AD61D6C3116EAACF48B23F885AA617F13037B0F4758018C2AC0DC96FF1A6F6DB042B302717D1911B65B4419CAE29D79EBD9082BB8A48B5B6088DC14C4E231236B929B68DBDA2F43842D8CCD7812812EF26C6490104A19A7CF603530A4A96E5F9A52E58A22C61B2B5B93C453BB11509CEF4662DADAF9B9E9DC0E28322D2FB7F459D3F76D8739CE5E48667B9831813B90B9EB4F0825157039DE1B1C0C9DB2F52C4F51E112A113E2F436FF61CF94254B3E2A260BCEDABBF632561F24A4BECEBE1F22F9260C830E17EFB80D88EA5919C98871B78AA91A74C8F2B20F83FEB22B9599E9B416D49706181998EC04E3FDF0AAEB789F9777334881108C5C19F548963F3BF8F5E693969A30B2F749498262D8AC956E0D91EB42FB355114D3F0589AFBF10289A015EF05D1DF365BD1A0468582C07067600266D2D26EAA94E40ECCF9C6BB24C1DDE27957FE8A82F2389EA6BBFD4B265ACAD5036DEF7297E97F0D229BBB7B004AABEC90F7E4A769934376ED220FD78D79C3095C0251B5F3A899402AAE586113B18A5B2D1F12A49330AEDD5AC2314FF07EB151A9B2024461A885A565BD85A57B3047CBEE3D70C7E9C400C5FB2C0146F02944AB36C42E1715B884DD47347692CE0EEB35DDFFAF50746670244989D422CC8B64D884E0FC7764E8F01F2EA21D679CADDC011227835F29BB3B5CC3DFBAAE992C8E1310E7AB8349E55B996BAC1B39496EF23E0ED7DC95F3FF8E50711DEA5CC6D9238D71AC9441E0CDE02F944CEA3B398EDB0CEAAA373E0B9F6994FF46A854C8C86243D2CE", + "expectedError": { + "type": "ValueError" + }, + "comment": "invalid cmsg1: missing encrypted secret shares" + }, + { + "tcId": 69, + "cmsg1": "0260C301C1EEC41AD16BF53F55F97B7B6EB842D9E2B8139712BA54695FF7116073034D3BEF06BFB2780C527B2A16CC2819160C8569EB94AF89F16B4F81C604E094170246EC025D2E096D23F52B755D1DED61C58608B23C67087E90555F52C428AFA61B0244EEC31E9105EB71FDDD1F777889003AD61D6C3116EAACF48B23F885AA617F13037B0F4758018C2AC0DC96FF1A6F6DB042B302717D1911B65B4419CAE29D79EBD9082BB8A48B5B6088DC14C4E231236B929B68DBDA2F43842D8CCD7812812EF26C6490104A19A7CF603530A4A96E5F9A52E58A22C61B2B5B93C453BB11509CEF4662DADAF9B9E9DC0E28322D2FB7F459D3F76D8739CE5E48667B9831813B90B9EB4F0825157039DE1B1C0C9DB2F52C4F51E112A113E2F436FF61CF94254B3E2A260BCEDABBF632561F24A4BECEBE1F22F9260C830E17EFB80D88EA5919C98871B78AA91A74C8F2B20F83FEB22B9599E9B416D49706181998EC04E3FDF0AAEB789F9777334881108C5C19F548963F3BF8F5E693969A30B2F749498262D8AC956E0D91EB42FB355114D3F0589AFBF10289A015EF05D1DF365BD1A0468582C07067600266D2D26EAA94E40ECCF9C6BB24C1DDE27957FE8A82F2389EA6BBFD4B265ACAD5036DEF7297E97F0D229BBB7B004AABEC90F7E4A769934376ED220FD78D79C3095C0251B5F3A899402AAE586113B18A5B2D1F12A49330AEDD5AC2314FF07EB151A9B2024461A885A565BD85A57B3047CBEE3D70C7E9C400C5FB2C0146F02944AB36C42E1715B884DD47347692CE0EEB35DDFFAF50746670244989D422CC8B64D884E0FC7764E8F01F2EA21D679CADDC011227835F29BB3B5CC3DFBAAE992C8E1310E7AB8349E55B996BAC1B39496EF23E0ED7DC95F3FF8E50711DEA5CC6D9238D71AC9441E0CDE02F944CEA3B398EDB0CEAAA373E0B9F6994FF46A854C8C86243D2CE91", + "expectedError": { + "type": "FaultyCoordinatorError", + "message": "Coordinator sent unexpected first group element for local participant id" + }, + "comment": "invalid cmsg1: coms_to_secrets list has an arbitrary value at index 0" + }, + { + "tcId": 70, + "cmsg1": "037ACDA3FDC4E9098EA3364B92CD6DD02654141995CFD3500E1C33D098F618458D0000000000000000000000000000000000000000000000000000000000000000000246EC025D2E096D23F52B755D1DED61C58608B23C67087E90555F52C428AFA61B0244EEC31E9105EB71FDDD1F777889003AD61D6C3116EAACF48B23F885AA617F13037B0F4758018C2AC0DC96FF1A6F6DB042B302717D1911B65B4419CAE29D79EBD9082BB8A48B5B6088DC14C4E231236B929B68DBDA2F43842D8CCD7812812EF26C6490104A19A7CF603530A4A96E5F9A52E58A22C61B2B5B93C453BB11509CEF4662DADAF9B9E9DC0E28322D2FB7F459D3F76D8739CE5E48667B9831813B90B9EB4F0825157039DE1B1C0C9DB2F52C4F51E112A113E2F436FF61CF94254B3E2A260BCEDABBF632561F24A4BECEBE1F22F9260C830E17EFB80D88EA5919C98871B78AA91A74C8F2B20F83FEB22B9599E9B416D49706181998EC04E3FDF0AAEB789F9777334881108C5C19F548963F3BF8F5E693969A30B2F749498262D8AC956E0D91EB42FB355114D3F0589AFBF10289A015EF05D1DF365BD1A0468582C07067600266D2D26EAA94E40ECCF9C6BB24C1DDE27957FE8A82F2389EA6BBFD4B265ACAD5036DEF7297E97F0D229BBB7B004AABEC90F7E4A769934376ED220FD78D79C3095C0251B5F3A899402AAE586113B18A5B2D1F12A49330AEDD5AC2314FF07EB151A9B2024461A885A565BD85A57B3047CBEE3D70C7E9C400C5FB2C0146F02944AB36C42E1715B884DD47347692CE0EEB35DDFFAF50746670244989D422CC8B64D884E0FC7764E8F01F2EA21D679CADDC011227835F29BB3B5CC3DFBAAE992C8E1310E7AB8349E55B996BAC1B39496EF23E0ED7DC95F3FF8E50711DEA5CC6D9238D71AC9441E0CDE02F944CEA3B398EDB0CEAAA373E0B9F6994FF46A854C8C86243D2CE91", + "expectedError": { + "type": "FaultyParticipantOrCoordinatorError", + "message": "Participant sent invalid commitment", + "participantId": 1 + }, + "comment": "invalid cmsg1: coms_to_secrets list has infinity at index 1" + }, + { + "tcId": 71, + "cmsg1": "037ACDA3FDC4E9098EA3364B92CD6DD02654141995CFD3500E1C33D098F618458D034D3BEF06BFB2780C527B2A16CC2819160C8569EB94AF89F16B4F81C604E094170246EC025D2E096D23F52B755D1DED61C58608B23C67087E90555F52C428AFA61B0244EEC31E9105EB71FDDD1F777889003AD61D6C3116EAACF48B23F885AA617F13037B0F4758018C2AC0DC96FF1A6F6DB042B302717D1911B65B4419CAE29D79EBD9082BB8A48B5B6088DC14C4E231236B929B68DBDA2F43842D8CCD7812812EF26C6490104A19A7CF603530A4A96E5F9A52E58A22C61B2B5B93C453BB11509CEF4609C289578B96E6283AB13E4741FB489FC147FB1A5F446A314BA73C052131EFB04B83247A0BCEDF5205202AD64188B24B0BC5B51A17AEB218BD98DBE000C843B90BCEDABBF632561F24A4BECEBE1F22F9260C830E17EFB80D88EA5919C98871B78AA91A74C8F2B20F83FEB22B9599E9B416D49706181998EC04E3FDF0AAEB789F9777334881108C5C19F548963F3BF8F5E693969A30B2F749498262D8AC956E0D91EB42FB355114D3F0589AFBF10289A015EF05D1DF365BD1A0468582C07067600266D2D26EAA94E40ECCF9C6BB24C1DDE27957FE8A82F2389EA6BBFD4B265ACAD5036DEF7297E97F0D229BBB7B004AABEC90F7E4A769934376ED220FD78D79C3095C0251B5F3A899402AAE586113B18A5B2D1F12A49330AEDD5AC2314FF07EB151A9B2024461A885A565BD85A57B3047CBEE3D70C7E9C400C5FB2C0146F02944AB36C42E1715B884DD47347692CE0EEB35DDFFAF50746670244989D422CC8B64D884E0FC7764E8F01F2EA21D679CADDC011227835F29BB3B5CC3DFBAAE992C8E1310E7AB8349E55B996BAC1B39496EF23E0ED7DC95F3FF8E50711DEA5CC6D9238D71AC9441E0CDE02F944CEA3B398EDB0CEAAA373E0B9F6994FF46A854C8C86243D2CE91", + "expectedError": { + "type": "FaultyParticipantOrCoordinatorError", + "message": "Participant sent invalid proof-of-knowledge", + "participantId": 1 + }, + "comment": "invalid cmsg1: pop list has an invalid value at index 1" + }, + { + "tcId": 72, + "cmsg1": "037ACDA3FDC4E9098EA3364B92CD6DD02654141995CFD3500E1C33D098F618458D034D3BEF06BFB2780C527B2A16CC2819160C8569EB94AF89F16B4F81C604E094170246EC025D2E096D23F52B755D1DED61C58608B23C67087E90555F52C428AFA61B0244EEC31E9105EB71FDDD1F777889003AD61D6C3116EAACF48B23F885AA617F130260C301C1EEC41AD16BF53F55F97B7B6EB842D9E2B8139712BA54695FF7116073082BB8A48B5B6088DC14C4E231236B929B68DBDA2F43842D8CCD7812812EF26C6490104A19A7CF603530A4A96E5F9A52E58A22C61B2B5B93C453BB11509CEF4662DADAF9B9E9DC0E28322D2FB7F459D3F76D8739CE5E48667B9831813B90B9EB4F0825157039DE1B1C0C9DB2F52C4F51E112A113E2F436FF61CF94254B3E2A260BCEDABBF632561F24A4BECEBE1F22F9260C830E17EFB80D88EA5919C98871B78AA91A74C8F2B20F83FEB22B9599E9B416D49706181998EC04E3FDF0AAEB789F9777334881108C5C19F548963F3BF8F5E693969A30B2F749498262D8AC956E0D91EB42FB355114D3F0589AFBF10289A015EF05D1DF365BD1A0468582C07067600266D2D26EAA94E40ECCF9C6BB24C1DDE27957FE8A82F2389EA6BBFD4B265ACAD5036DEF7297E97F0D229BBB7B004AABEC90F7E4A769934376ED220FD78D79C3095C0251B5F3A899402AAE586113B18A5B2D1F12A49330AEDD5AC2314FF07EB151A9B2024461A885A565BD85A57B3047CBEE3D70C7E9C400C5FB2C0146F02944AB36C42E1715B884DD47347692CE0EEB35DDFFAF50746670244989D422CC8B64D884E0FC7764E8F01F2EA21D679CADDC011227835F29BB3B5CC3DFBAAE992C8E1310E7AB8349E55B996BAC1B39496EF23E0ED7DC95F3FF8E50711DEA5CC6D9238D71AC9441E0CDE02F944CEA3B398EDB0CEAAA373E0B9F6994FF46A854C8C86243D2CE91", + "expectedError": { + "type": "UnknownFaultyParticipantOrCoordinatorError", + "message": "Received invalid secshare; consider using participant_investigate() to determine a faulty party" + }, + "comment": "invalid cmsg1: sum_coms_to_nonconst_terms has an arbitrary value at index 0" + }, + { + "tcId": 73, + "cmsg1": "037ACDA3FDC4E9098EA3364B92CD6DD02654141995CFD3500E1C33D098F618458D034D3BEF06BFB2780C527B2A16CC2819160C8569EB94AF89F16B4F81C604E094170246EC025D2E096D23F52B755D1DED61C58608B23C67087E90555F52C428AFA61B0244EEC31E9105EB71FDDD1F777889003AD61D6C3116EAACF48B23F885AA617F13000000000000000000000000000000000000000000000000000000000000000000082BB8A48B5B6088DC14C4E231236B929B68DBDA2F43842D8CCD7812812EF26C6490104A19A7CF603530A4A96E5F9A52E58A22C61B2B5B93C453BB11509CEF4662DADAF9B9E9DC0E28322D2FB7F459D3F76D8739CE5E48667B9831813B90B9EB4F0825157039DE1B1C0C9DB2F52C4F51E112A113E2F436FF61CF94254B3E2A260BCEDABBF632561F24A4BECEBE1F22F9260C830E17EFB80D88EA5919C98871B78AA91A74C8F2B20F83FEB22B9599E9B416D49706181998EC04E3FDF0AAEB789F9777334881108C5C19F548963F3BF8F5E693969A30B2F749498262D8AC956E0D91EB42FB355114D3F0589AFBF10289A015EF05D1DF365BD1A0468582C07067600266D2D26EAA94E40ECCF9C6BB24C1DDE27957FE8A82F2389EA6BBFD4B265ACAD5036DEF7297E97F0D229BBB7B004AABEC90F7E4A769934376ED220FD78D79C3095C0251B5F3A899402AAE586113B18A5B2D1F12A49330AEDD5AC2314FF07EB151A9B2024461A885A565BD85A57B3047CBEE3D70C7E9C400C5FB2C0146F02944AB36C42E1715B884DD47347692CE0EEB35DDFFAF50746670244989D422CC8B64D884E0FC7764E8F01F2EA21D679CADDC011227835F29BB3B5CC3DFBAAE992C8E1310E7AB8349E55B996BAC1B39496EF23E0ED7DC95F3FF8E50711DEA5CC6D9238D71AC9441E0CDE02F944CEA3B398EDB0CEAAA373E0B9F6994FF46A854C8C86243D2CE91", + "expectedError": { + "type": "UnknownFaultyParticipantOrCoordinatorError", + "message": "Received invalid secshare; consider using participant_investigate() to determine a faulty party" + }, + "comment": "invalid cmsg1: sum_coms_to_nonconst_terms has the infinity point at index 0" + }, + { + "tcId": 74, + "cmsg1": "037ACDA3FDC4E9098EA3364B92CD6DD02654141995CFD3500E1C33D098F618458D034D3BEF06BFB2780C527B2A16CC2819160C8569EB94AF89F16B4F81C604E094170246EC025D2E096D23F52B755D1DED61C58608B23C67087E90555F52C428AFA61B0244EEC31E9105EB71FDDD1F777889003AD61D6C3116EAACF48B23F885AA617F13037B0F4758018C2AC0DC96FF1A6F6DB042B302717D1911B65B4419CAE29D79EBD9082BB8A48B5B6088DC14C4E231236B929B68DBDA2F43842D8CCD7812812EF26C6490104A19A7CF603530A4A96E5F9A52E58A22C61B2B5B93C453BB11509CEF4662DADAF9B9E9DC0E28322D2FB7F459D3F76D8739CE5E48667B9831813B90B9EB4F0825157039DE1B1C0C9DB2F52C4F51E112A113E2F436FF61CF94254B3E2A260BCEDABBF632561F24A4BECEBE1F22F9260C830E17EFB80D88EA5919C98871B78AA91A74C8F2B20F83FEB22B9599E9B416D49706181998EC04E3FDF0AAEB789F9777334881108C5C19F548963F3BF8F5E693969A30B2F749498262D8AC956E0D91EB42FB355114D3F0589AFBF10289A015EF05D1DF365BD1A0468582C07067600266D2D26EAA94E40ECCF9C6BB24C1DDE27957FE8A82F2389EA6BBFD4B265ACAD5036DEF7297E97F0D229BBB7B004AABEC90F7E4A769934376ED220FD78D79C3095C0251B5F3A899402AAE586113B18A5B2D1F12A49330AEDD5AC2314FF07EB151A9B2024461A885A565BD85A57B3047CBEE3D70C7E9C400C5FB2C0146F02944AB36C42E1715B884DD47347692CE0EEB35DDFFAF50746670244989D422CC8B64D884E10D7764E8F01F2EA21D679CADDC011227835F29BB3B5CC3DFBAAE992C8E1310E7AB8349E55B996BAC1B39496EF23E0ED7DC95F3FF8E50711DEA5CC6D9238D71AC9441E0CDE02F944CEA3B398EDB0CEAAA373E0B9F6994FF46A854C8C86243D2CE91", + "expectedError": { + "type": "UnknownFaultyParticipantOrCoordinatorError", + "message": "Received invalid secshare; consider using participant_investigate() to determine a faulty party" + }, + "comment": "invalid cmsg1: participant 1 sent an invalid secshare for participant 0" + } + ] + } + ] +} \ No newline at end of file diff --git a/bip-chilldkg/vectors/recover_vectors.json b/bip-chilldkg/vectors/recover_vectors.json new file mode 100644 index 0000000000..c050a065ce --- /dev/null +++ b/bip-chilldkg/vectors/recover_vectors.json @@ -0,0 +1,181 @@ +{ + "description": [ + "Test vectors for participant_recover(hostseckey, recovery_data) and", + "coordinator_recover(recovery_data).", + "Recovers a DKG output and session parameters from serialized recovery data.", + "If hostseckey is null, recovery is performed as coordinator (secshare will be null).", + "If hostseckey is a 32-byte hex string, recovery is performed as the corresponding participant.", + "", + "For each valid test case:", + " Call participant_recover(hostseckey, recoveryData) or coordinator_recover(recoveryData)", + " (if hostseckey is null) and verify the result matches expectedOutput.", + " expectedOutput contains 'dkgOutput' (with secshare, threshPk, pubshares)", + " and 'params' (with hostpubkeys, t).", + "", + "For each error test case:", + " Call participant_recover(hostseckey, recoveryData) or coordinator_recover(recoveryData)", + " (if hostseckey is null) and verify it raises an exception matching expectedError." + ], + "totalTests": 13, + "validTestCases": [ + { + "tcId": 1, + "hostseckey": "ADE179B2C56CB75868D44B333C16C89CB00DFDE378AD79C84D0CCE856E4F9207", + "recoveryData": "0000000203B74C6FD38288DDF94300E887B12DD40F655791144A07C32487FBD81CC228C07903B9C3F15E5C0E086EDE931D69BABD36167BE6CCA8705378F8849A4F00EDA2941303AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F303AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A30260537C74BC2B79676CDE217FFA7D48236D4A309954153DEB39A31AADF6B369CC022E7B7DD7A0B4A72097703E60648B615A4DDDF2D715A5E0992379537A44C13AD103D1FD5A186167E5DE84B11C0F56E1FC6808D2DB35E6E3587243CF59DE605CB806E242A37A2E03838EDCB4B064425FBA5F5F8331EDDF79B46AACC70EEB714995A27B5A879577BDAF956EC9F1C234BD3942E2B2F0E372902637993C54815B69F7EAFF288C16C20D3F8D26B64E23599E6996A833E286FEAD5A7490151202E859E484A6C2DE535556E47C8F3427B9716CFC4A66ADD0F2788AB793E2FBFF7D81DA06315C3F62CAA2B85E369922E68A94113EB839B5E3E424A4F4BF2E8D2908F517E8BE3B1B96C40D7F9A378F2A61FACF8469447344B5280F5E7B3C66B560709A6FA4D7E083E650F3C109995ACB96EEA2EE4EF252257E1BF92FFF121CE916DE2D7C4B5479195E7CBE22A8B53C41012ABEA37F0B59579E43EDC6477E8CC5C7EB9F3C8BF7AEC850C63A659FADD91C9063908054FC4D193594E132ADFBE1F24BE74C8BEA7A", + "expectedOutput": { + "dkgOutput": { + "secshare": "78F979492EF00DFD84069C2E8367753A712447527C02A2887D5AF86A6F4D02BA", + "threshPk": "03DF2E2C605ACE90BFAAE275614FDA6D6233B1438EE6D8CE1EA74111887E3110F7", + "pubshares": [ + "025C8EC2A3D153823BD2820F6943FD4010B9A9C48D779A6845D2CDDB0EFE36E1EC", + "03397F3E8A16CCF5BD71E084163A9370C290F2915BC4BF80FDEA1C0E713B91F9C3", + "037709A9E38D16A7C57821BF5D97DEBA9588BB0C3DAC8B29BB732D088AB70FCFB6" + ] + }, + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3" + ], + "t": 2 + } + }, + "comment": "participant recovery" + }, + { + "tcId": 2, + "hostseckey": null, + "recoveryData": "0000000203B74C6FD38288DDF94300E887B12DD40F655791144A07C32487FBD81CC228C07903B9C3F15E5C0E086EDE931D69BABD36167BE6CCA8705378F8849A4F00EDA2941303AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F303AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A30260537C74BC2B79676CDE217FFA7D48236D4A309954153DEB39A31AADF6B369CC022E7B7DD7A0B4A72097703E60648B615A4DDDF2D715A5E0992379537A44C13AD103D1FD5A186167E5DE84B11C0F56E1FC6808D2DB35E6E3587243CF59DE605CB806E242A37A2E03838EDCB4B064425FBA5F5F8331EDDF79B46AACC70EEB714995A27B5A879577BDAF956EC9F1C234BD3942E2B2F0E372902637993C54815B69F7EAFF288C16C20D3F8D26B64E23599E6996A833E286FEAD5A7490151202E859E484A6C2DE535556E47C8F3427B9716CFC4A66ADD0F2788AB793E2FBFF7D81DA06315C3F62CAA2B85E369922E68A94113EB839B5E3E424A4F4BF2E8D2908F517E8BE3B1B96C40D7F9A378F2A61FACF8469447344B5280F5E7B3C66B560709A6FA4D7E083E650F3C109995ACB96EEA2EE4EF252257E1BF92FFF121CE916DE2D7C4B5479195E7CBE22A8B53C41012ABEA37F0B59579E43EDC6477E8CC5C7EB9F3C8BF7AEC850C63A659FADD91C9063908054FC4D193594E132ADFBE1F24BE74C8BEA7A", + "expectedOutput": { + "dkgOutput": { + "secshare": null, + "threshPk": "03DF2E2C605ACE90BFAAE275614FDA6D6233B1438EE6D8CE1EA74111887E3110F7", + "pubshares": [ + "025C8EC2A3D153823BD2820F6943FD4010B9A9C48D779A6845D2CDDB0EFE36E1EC", + "03397F3E8A16CCF5BD71E084163A9370C290F2915BC4BF80FDEA1C0E713B91F9C3", + "037709A9E38D16A7C57821BF5D97DEBA9588BB0C3DAC8B29BB732D088AB70FCFB6" + ] + }, + "params": { + "hostpubkeys": [ + "03AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F3", + "03AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7", + "021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A3" + ], + "t": 2 + } + }, + "comment": "coordinator recovery" + } + ], + "errorTestCases": [ + { + "tcId": 3, + "hostseckey": null, + "recoveryData": "00000203B74C6FD38288DDF94300E887B12DD40F655791144A07C32487FBD81CC228C07903B9C3F15E5C0E086EDE931D69BABD36167BE6CCA8705378F8849A4F00EDA2941303AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F303AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A30260537C74BC2B79676CDE217FFA7D48236D4A309954153DEB39A31AADF6B369CC022E7B7DD7A0B4A72097703E60648B615A4DDDF2D715A5E0992379537A44C13AD103D1FD5A186167E5DE84B11C0F56E1FC6808D2DB35E6E3587243CF59DE605CB806E242A37A2E03838EDCB4B064425FBA5F5F8331EDDF79B46AACC70EEB714995A27B5A879577BDAF956EC9F1C234BD3942E2B2F0E372902637993C54815B69F7EAFF288C16C20D3F8D26B64E23599E6996A833E286FEAD5A7490151202E859E484A6C2DE535556E47C8F3427B9716CFC4A66ADD0F2788AB793E2FBFF7D81DA06315C3F62CAA2B85E369922E68A94113EB839B5E3E424A4F4BF2E8D2908F517E8BE3B1B96C40D7F9A378F2A61FACF8469447344B5280F5E7B3C66B560709A6FA4D7E083E650F3C109995ACB96EEA2EE4EF252257E1BF92FFF121CE916DE2D7C4B5479195E7CBE22A8B53C41012ABEA37F0B59579E43EDC6477E8CC5C7EB9F3C8BF7AEC850C63A659FADD91C9063908054FC4D193594E132ADFBE1F24BE74C8BEA7A", + "expectedError": { + "type": "RecoveryDataError", + "message": "Failed to deserialize recovery data" + }, + "comment": "recovery data of invalid length" + }, + { + "tcId": 4, + "hostseckey": null, + "recoveryData": "0000000203000000000000000000000000000000000000000000000000000000000000000503B9C3F15E5C0E086EDE931D69BABD36167BE6CCA8705378F8849A4F00EDA2941303AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F303AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A30260537C74BC2B79676CDE217FFA7D48236D4A309954153DEB39A31AADF6B369CC022E7B7DD7A0B4A72097703E60648B615A4DDDF2D715A5E0992379537A44C13AD103D1FD5A186167E5DE84B11C0F56E1FC6808D2DB35E6E3587243CF59DE605CB806E242A37A2E03838EDCB4B064425FBA5F5F8331EDDF79B46AACC70EEB714995A27B5A879577BDAF956EC9F1C234BD3942E2B2F0E372902637993C54815B69F7EAFF288C16C20D3F8D26B64E23599E6996A833E286FEAD5A7490151202E859E484A6C2DE535556E47C8F3427B9716CFC4A66ADD0F2788AB793E2FBFF7D81DA06315C3F62CAA2B85E369922E68A94113EB839B5E3E424A4F4BF2E8D2908F517E8BE3B1B96C40D7F9A378F2A61FACF8469447344B5280F5E7B3C66B560709A6FA4D7E083E650F3C109995ACB96EEA2EE4EF252257E1BF92FFF121CE916DE2D7C4B5479195E7CBE22A8B53C41012ABEA37F0B59579E43EDC6477E8CC5C7EB9F3C8BF7AEC850C63A659FADD91C9063908054FC4D193594E132ADFBE1F24BE74C8BEA7A", + "expectedError": { + "type": "RecoveryDataError", + "message": "Failed to deserialize recovery data" + }, + "comment": "first coefficient of sum_coms is invalid" + }, + { + "tcId": 5, + "hostseckey": null, + "recoveryData": "0000000203B74C6FD38288DDF94300E887B12DD40F655791144A07C32487FBD81CC228C07903B9C3F15E5C0E086EDE931D69BABD36167BE6CCA8705378F8849A4F00EDA2941303AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F303AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A30260537C74BC2B79676CDE217FFA7D48236D4A309954153DEB39A31AADF6B369CC022E7B7DD7A0B4A72097703E60648B615A4DDDF2D715A5E0992379537A44C13AD103D1FD5A186167E5DE84B11C0F56E1FC6808D2DB35E6E3587243CF59DE605CB806E242A37A2E03838EDCB4B064425FBA5F5F8331EDDF79B46AACC70EEB714995A27B5A879577BDAF956EC9F1C234BD3942E2B2F0E372902637993C54815B69F7EAFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141A6C2DE535556E47C8F3427B9716CFC4A66ADD0F2788AB793E2FBFF7D81DA06315C3F62CAA2B85E369922E68A94113EB839B5E3E424A4F4BF2E8D2908F517E8BE3B1B96C40D7F9A378F2A61FACF8469447344B5280F5E7B3C66B560709A6FA4D7E083E650F3C109995ACB96EEA2EE4EF252257E1BF92FFF121CE916DE2D7C4B5479195E7CBE22A8B53C41012ABEA37F0B59579E43EDC6477E8CC5C7EB9F3C8BF7AEC850C63A659FADD91C9063908054FC4D193594E132ADFBE1F24BE74C8BEA7A", + "expectedError": { + "type": "RecoveryDataError", + "message": "Failed to deserialize recovery data" + }, + "comment": "last share in enc_secshare list is invalid" + }, + { + "tcId": 6, + "hostseckey": null, + "recoveryData": "0000000003AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F303AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A30260537C74BC2B79676CDE217FFA7D48236D4A309954153DEB39A31AADF6B369CC022E7B7DD7A0B4A72097703E60648B615A4DDDF2D715A5E0992379537A44C13AD103D1FD5A186167E5DE84B11C0F56E1FC6808D2DB35E6E3587243CF59DE605CB806E242A37A2E03838EDCB4B064425FBA5F5F8331EDDF79B46AACC70EEB714995A27B5A879577BDAF956EC9F1C234BD3942E2B2F0E372902637993C54815B69F7EAFF288C16C20D3F8D26B64E23599E6996A833E286FEAD5A7490151202E859E484A6C2DE535556E47C8F3427B9716CFC4A66ADD0F2788AB793E2FBFF7D81DA06315C3F62CAA2B85E369922E68A94113EB839B5E3E424A4F4BF2E8D2908F517E8BE3B1B96C40D7F9A378F2A61FACF8469447344B5280F5E7B3C66B560709A6FA4D7E083E650F3C109995ACB96EEA2EE4EF252257E1BF92FFF121CE916DE2D7C4B5479195E7CBE22A8B53C41012ABEA37F0B59579E43EDC6477E8CC5C7EB9F3C8BF7AEC850C63A659FADD91C9063908054FC4D193594E132ADFBE1F24BE74C8BEA7A", + "expectedError": { + "type": "RecoveryDataError", + "message": "Invalid session parameters in recovery data" + }, + "comment": "invalid threshold" + }, + { + "tcId": 7, + "hostseckey": null, + "recoveryData": "0000000203B74C6FD38288DDF94300E887B12DD40F655791144A07C32487FBD81CC228C07903B9C3F15E5C0E086EDE931D69BABD36167BE6CCA8705378F8849A4F00EDA2941303000000000000000000000000000000000000000000000000000000000000000503AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A30260537C74BC2B79676CDE217FFA7D48236D4A309954153DEB39A31AADF6B369CC022E7B7DD7A0B4A72097703E60648B615A4DDDF2D715A5E0992379537A44C13AD103D1FD5A186167E5DE84B11C0F56E1FC6808D2DB35E6E3587243CF59DE605CB806E242A37A2E03838EDCB4B064425FBA5F5F8331EDDF79B46AACC70EEB714995A27B5A879577BDAF956EC9F1C234BD3942E2B2F0E372902637993C54815B69F7EAFF288C16C20D3F8D26B64E23599E6996A833E286FEAD5A7490151202E859E484A6C2DE535556E47C8F3427B9716CFC4A66ADD0F2788AB793E2FBFF7D81DA06315C3F62CAA2B85E369922E68A94113EB839B5E3E424A4F4BF2E8D2908F517E8BE3B1B96C40D7F9A378F2A61FACF8469447344B5280F5E7B3C66B560709A6FA4D7E083E650F3C109995ACB96EEA2EE4EF252257E1BF92FFF121CE916DE2D7C4B5479195E7CBE22A8B53C41012ABEA37F0B59579E43EDC6477E8CC5C7EB9F3C8BF7AEC850C63A659FADD91C9063908054FC4D193594E132ADFBE1F24BE74C8BEA7A", + "expectedError": { + "type": "RecoveryDataError", + "message": "Invalid session parameters in recovery data" + }, + "comment": "first pubkey in the hostpubkey list is invalid" + }, + { + "tcId": 8, + "hostseckey": null, + "recoveryData": "0000000203B74C6FD38288DDF94300E887B12DD40F655791144A07C32487FBD81CC228C07903B9C3F15E5C0E086EDE931D69BABD36167BE6CCA8705378F8849A4F00EDA2941303AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F303AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A30260537C74BC2B79676CDE217FFA7D48236D4A309954153DEB39A31AADF6B369CC022E7B7DD7A0B4A72097703E60648B615A4DDDF2D715A5E0992379537A44C13AD103421F5FC9A21065445C96FDB91C0C1E2F2431741C72713B4B99DDCB316F31E9FCE242A37A2E03838EDCB4B064425FBA5F5F8331EDDF79B46AACC70EEB714995A27B5A879577BDAF956EC9F1C234BD3942E2B2F0E372902637993C54815B69F7EAFF288C16C20D3F8D26B64E23599E6996A833E286FEAD5A7490151202E859E484A6C2DE535556E47C8F3427B9716CFC4A66ADD0F2788AB793E2FBFF7D81DA06315C3F62CAA2B85E369922E68A94113EB839B5E3E424A4F4BF2E8D2908F517E8BE3B1B96C40D7F9A378F2A61FACF8469447344B5280F5E7B3C66B560709A6FA4D7E083E650F3C109995ACB96EEA2EE4EF252257E1BF92FFF121CE916DE2D7C4B5479195E7CBE22A8B53C41012ABEA37F0B59579E43EDC6477E8CC5C7EB9F3C8BF7AEC850C63A659FADD91C9063908054FC4D193594E132ADFBE1F24BE74C8BEA7A", + "expectedError": { + "type": "RecoveryDataError", + "message": "Invalid certificate in recovery data" + }, + "comment": "last pubnonce in the pubnonces list was tampered with (doesn't match signed certificate)" + }, + { + "tcId": 9, + "hostseckey": null, + "recoveryData": "0000000203B74C6FD38288DDF94300E887B12DD40F655791144A07C32487FBD81CC228C07903B9C3F15E5C0E086EDE931D69BABD36167BE6CCA8705378F8849A4F00EDA2941303AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F303AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A30260537C74BC2B79676CDE217FFA7D48236D4A309954153DEB39A31AADF6B369CC022E7B7DD7A0B4A72097703E60648B615A4DDDF2D715A5E0992379537A44C13AD103D1FD5A186167E5DE84B11C0F56E1FC6808D2DB35E6E3587243CF59DE605CB806E242A37A2E03838EDCB4B064425FBA5F5F8331EDDF79B46AACC70EEB714995A27B5A879577BDAF956EC9F1C234BD3942E2B2F0E372902637993C54815B69F7EAFF288C16C20D3F8D26B64E23599E6996A833E286FEAD5A7490151202E859E484A6C2DE535556E47C8F3427B9716CFC4A66ADD0F2788AB793E2FBFF7D81DA06315C3F62CAA2B85E369922E68A94113EB839B5E3E424A4F4BF2E8D2908F517E8BE3B1B96C40D7F9A378F2A61FACF8469447344B5280F5E7B3C66B560709A6FA4D7E083E650F3C109995ACB96EEA2EE4EF252257E1BF92FFF121CE916DE2D7C4B5409C289578B96E6283AB13E4741FB489FC147FB1A5F446A314BA73C052131EFB04B83247A0BCEDF5205202AD64188B24B0BC5B51A17AEB218BD98DBE000C843B9", + "expectedError": { + "type": "RecoveryDataError", + "message": "Invalid certificate in recovery data" + }, + "comment": "last signature in the certificate is invalid" + }, + { + "tcId": 10, + "hostseckey": "631C047D50A67E45E27ED1FF25FCE179", + "recoveryData": "0000000203B74C6FD38288DDF94300E887B12DD40F655791144A07C32487FBD81CC228C07903B9C3F15E5C0E086EDE931D69BABD36167BE6CCA8705378F8849A4F00EDA2941303AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F303AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A30260537C74BC2B79676CDE217FFA7D48236D4A309954153DEB39A31AADF6B369CC022E7B7DD7A0B4A72097703E60648B615A4DDDF2D715A5E0992379537A44C13AD103D1FD5A186167E5DE84B11C0F56E1FC6808D2DB35E6E3587243CF59DE605CB806E242A37A2E03838EDCB4B064425FBA5F5F8331EDDF79B46AACC70EEB714995A27B5A879577BDAF956EC9F1C234BD3942E2B2F0E372902637993C54815B69F7EAFF288C16C20D3F8D26B64E23599E6996A833E286FEAD5A7490151202E859E484A6C2DE535556E47C8F3427B9716CFC4A66ADD0F2788AB793E2FBFF7D81DA06315C3F62CAA2B85E369922E68A94113EB839B5E3E424A4F4BF2E8D2908F517E8BE3B1B96C40D7F9A378F2A61FACF8469447344B5280F5E7B3C66B560709A6FA4D7E083E650F3C109995ACB96EEA2EE4EF252257E1BF92FFF121CE916DE2D7C4B5479195E7CBE22A8B53C41012ABEA37F0B59579E43EDC6477E8CC5C7EB9F3C8BF7AEC850C63A659FADD91C9063908054FC4D193594E132ADFBE1F24BE74C8BEA7A", + "expectedError": { + "type": "ValueError" + }, + "comment": "invalid hostseckey" + }, + { + "tcId": 11, + "hostseckey": "759DE9306FB02B3D84C455112BF1F3360401DC383ECD1FCEDE59EC809D6F9FE7", + "recoveryData": "0000000203B74C6FD38288DDF94300E887B12DD40F655791144A07C32487FBD81CC228C07903B9C3F15E5C0E086EDE931D69BABD36167BE6CCA8705378F8849A4F00EDA2941303AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F303AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A30260537C74BC2B79676CDE217FFA7D48236D4A309954153DEB39A31AADF6B369CC022E7B7DD7A0B4A72097703E60648B615A4DDDF2D715A5E0992379537A44C13AD103D1FD5A186167E5DE84B11C0F56E1FC6808D2DB35E6E3587243CF59DE605CB806E242A37A2E03838EDCB4B064425FBA5F5F8331EDDF79B46AACC70EEB714995A27B5A879577BDAF956EC9F1C234BD3942E2B2F0E372902637993C54815B69F7EAFF288C16C20D3F8D26B64E23599E6996A833E286FEAD5A7490151202E859E484A6C2DE535556E47C8F3427B9716CFC4A66ADD0F2788AB793E2FBFF7D81DA06315C3F62CAA2B85E369922E68A94113EB839B5E3E424A4F4BF2E8D2908F517E8BE3B1B96C40D7F9A378F2A61FACF8469447344B5280F5E7B3C66B560709A6FA4D7E083E650F3C109995ACB96EEA2EE4EF252257E1BF92FFF121CE916DE2D7C4B5479195E7CBE22A8B53C41012ABEA37F0B59579E43EDC6477E8CC5C7EB9F3C8BF7AEC850C63A659FADD91C9063908054FC4D193594E132ADFBE1F24BE74C8BEA7A", + "expectedError": { + "type": "HostSeckeyError", + "message": "Host secret key does not match any host public key in the recovery data" + }, + "comment": "host secret key doesn't match any hostpubkey" + }, + { + "tcId": 12, + "hostseckey": "0000000000000000000000000000000000000000000000000000000000000000", + "recoveryData": "0000000203B74C6FD38288DDF94300E887B12DD40F655791144A07C32487FBD81CC228C07903B9C3F15E5C0E086EDE931D69BABD36167BE6CCA8705378F8849A4F00EDA2941303AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F303AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A30260537C74BC2B79676CDE217FFA7D48236D4A309954153DEB39A31AADF6B369CC022E7B7DD7A0B4A72097703E60648B615A4DDDF2D715A5E0992379537A44C13AD103D1FD5A186167E5DE84B11C0F56E1FC6808D2DB35E6E3587243CF59DE605CB806E242A37A2E03838EDCB4B064425FBA5F5F8331EDDF79B46AACC70EEB714995A27B5A879577BDAF956EC9F1C234BD3942E2B2F0E372902637993C54815B69F7EAFF288C16C20D3F8D26B64E23599E6996A833E286FEAD5A7490151202E859E484A6C2DE535556E47C8F3427B9716CFC4A66ADD0F2788AB793E2FBFF7D81DA06315C3F62CAA2B85E369922E68A94113EB839B5E3E424A4F4BF2E8D2908F517E8BE3B1B96C40D7F9A378F2A61FACF8469447344B5280F5E7B3C66B560709A6FA4D7E083E650F3C109995ACB96EEA2EE4EF252257E1BF92FFF121CE916DE2D7C4B5479195E7CBE22A8B53C41012ABEA37F0B59579E43EDC6477E8CC5C7EB9F3C8BF7AEC850C63A659FADD91C9063908054FC4D193594E132ADFBE1F24BE74C8BEA7A", + "expectedError": { + "type": "HostSeckeyError" + }, + "comment": "host secret key is zero" + }, + { + "tcId": 13, + "hostseckey": "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", + "recoveryData": "0000000203B74C6FD38288DDF94300E887B12DD40F655791144A07C32487FBD81CC228C07903B9C3F15E5C0E086EDE931D69BABD36167BE6CCA8705378F8849A4F00EDA2941303AED316469060698D774150EFD7F8F406A2BAB516DD7D22CB258323C59C6417F303AEB5AE20783D4858F6767747963F144C7DB8ABA328625CC8A87F7676D8CDEEE7021A48BBCCAC751AE9EC1EA7A7F8D421D5FD60AAB44E6D2F37B31873098A77B7A30260537C74BC2B79676CDE217FFA7D48236D4A309954153DEB39A31AADF6B369CC022E7B7DD7A0B4A72097703E60648B615A4DDDF2D715A5E0992379537A44C13AD103D1FD5A186167E5DE84B11C0F56E1FC6808D2DB35E6E3587243CF59DE605CB806E242A37A2E03838EDCB4B064425FBA5F5F8331EDDF79B46AACC70EEB714995A27B5A879577BDAF956EC9F1C234BD3942E2B2F0E372902637993C54815B69F7EAFF288C16C20D3F8D26B64E23599E6996A833E286FEAD5A7490151202E859E484A6C2DE535556E47C8F3427B9716CFC4A66ADD0F2788AB793E2FBFF7D81DA06315C3F62CAA2B85E369922E68A94113EB839B5E3E424A4F4BF2E8D2908F517E8BE3B1B96C40D7F9A378F2A61FACF8469447344B5280F5E7B3C66B560709A6FA4D7E083E650F3C109995ACB96EEA2EE4EF252257E1BF92FFF121CE916DE2D7C4B5479195E7CBE22A8B53C41012ABEA37F0B59579E43EDC6477E8CC5C7EB9F3C8BF7AEC850C63A659FADD91C9063908054FC4D193594E132ADFBE1F24BE74C8BEA7A", + "expectedError": { + "type": "HostSeckeyError" + }, + "comment": "host secret key is out of range" + } + ] +} \ No newline at end of file