From b7e42f93b5651055a4e9a1202e70b1b43246d4d1 Mon Sep 17 00:00:00 2001 From: siv2r Date: Sat, 3 Jan 2026 18:50:16 +0530 Subject: [PATCH 1/6] Add BIP FROST Signing for Schnorr threshold signatures --- bip-frost-signing.md | 862 ++++++++++ bip-frost-signing/.markdownlint.json | 9 + bip-frost-signing/all.sh | 18 + bip-frost-signing/docs/frost-signing-flow.png | Bin 0 -> 453113 bytes bip-frost-signing/docs/partialsig_forgery.md | 63 + bip-frost-signing/python/.ruff.toml | 3 + bip-frost-signing/python/example.py | 320 ++++ .../python/frost_ref/__init__.py | 43 + bip-frost-signing/python/frost_ref/signing.py | 500 ++++++ bip-frost-signing/python/gen_vectors.py | 1466 +++++++++++++++++ bip-frost-signing/python/mypy.ini | 4 + .../secp256k1lab/.github/workflows/main.yml | 34 + .../python/secp256k1lab/.gitignore | 10 + .../python/secp256k1lab/.python-version | 1 + .../python/secp256k1lab/CHANGELOG.md | 25 + bip-frost-signing/python/secp256k1lab/COPYING | 23 + .../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 | 475 ++++++ .../src/secp256k1lab/secp256k1.pyi | 133 ++ .../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-frost-signing/python/tests.py | 669 ++++++++ bip-frost-signing/python/tests.sh | 24 + bip-frost-signing/python/trusted_dealer.py | 140 ++ .../python/vectors/det_sign_vectors.json | 400 +++++ .../python/vectors/nonce_agg_vectors.json | 86 + .../python/vectors/nonce_gen_vectors.json | 48 + .../python/vectors/sig_agg_vectors.json | 186 +++ .../python/vectors/sign_verify_vectors.json | 509 ++++++ .../python/vectors/tweak_vectors.json | 277 ++++ 40 files changed, 6777 insertions(+) create mode 100644 bip-frost-signing.md create mode 100644 bip-frost-signing/.markdownlint.json create mode 100755 bip-frost-signing/all.sh create mode 100644 bip-frost-signing/docs/frost-signing-flow.png create mode 100644 bip-frost-signing/docs/partialsig_forgery.md create mode 100644 bip-frost-signing/python/.ruff.toml create mode 100755 bip-frost-signing/python/example.py create mode 100644 bip-frost-signing/python/frost_ref/__init__.py create mode 100644 bip-frost-signing/python/frost_ref/signing.py create mode 100755 bip-frost-signing/python/gen_vectors.py create mode 100644 bip-frost-signing/python/mypy.ini create mode 100644 bip-frost-signing/python/secp256k1lab/.github/workflows/main.yml create mode 100644 bip-frost-signing/python/secp256k1lab/.gitignore create mode 100644 bip-frost-signing/python/secp256k1lab/.python-version create mode 100644 bip-frost-signing/python/secp256k1lab/CHANGELOG.md create mode 100644 bip-frost-signing/python/secp256k1lab/COPYING create mode 100644 bip-frost-signing/python/secp256k1lab/README.md create mode 100644 bip-frost-signing/python/secp256k1lab/pyproject.toml create mode 100644 bip-frost-signing/python/secp256k1lab/src/secp256k1lab/__init__.py create mode 100644 bip-frost-signing/python/secp256k1lab/src/secp256k1lab/bip340.py create mode 100644 bip-frost-signing/python/secp256k1lab/src/secp256k1lab/ecdh.py create mode 100644 bip-frost-signing/python/secp256k1lab/src/secp256k1lab/keys.py create mode 100644 bip-frost-signing/python/secp256k1lab/src/secp256k1lab/py.typed create mode 100644 bip-frost-signing/python/secp256k1lab/src/secp256k1lab/secp256k1.py create mode 100644 bip-frost-signing/python/secp256k1lab/src/secp256k1lab/secp256k1.pyi create mode 100644 bip-frost-signing/python/secp256k1lab/src/secp256k1lab/util.py create mode 100644 bip-frost-signing/python/secp256k1lab/test/__init__.py create mode 100644 bip-frost-signing/python/secp256k1lab/test/test_bip340.py create mode 100644 bip-frost-signing/python/secp256k1lab/test/test_ecdh.py create mode 100644 bip-frost-signing/python/secp256k1lab/test/test_secp256k1.py create mode 100644 bip-frost-signing/python/secp256k1lab/test/vectors/bip340.csv create mode 100755 bip-frost-signing/python/tests.py create mode 100755 bip-frost-signing/python/tests.sh create mode 100644 bip-frost-signing/python/trusted_dealer.py create mode 100644 bip-frost-signing/python/vectors/det_sign_vectors.json create mode 100644 bip-frost-signing/python/vectors/nonce_agg_vectors.json create mode 100644 bip-frost-signing/python/vectors/nonce_gen_vectors.json create mode 100644 bip-frost-signing/python/vectors/sig_agg_vectors.json create mode 100644 bip-frost-signing/python/vectors/sign_verify_vectors.json create mode 100644 bip-frost-signing/python/vectors/tweak_vectors.json diff --git a/bip-frost-signing.md b/bip-frost-signing.md new file mode 100644 index 0000000000..af109a5cb6 --- /dev/null +++ b/bip-frost-signing.md @@ -0,0 +1,862 @@ +```yaml +BIP: +Title: FROST Signing Protocol for BIP340 Schnorr Signatures +Author: Sivaram Dhakshinamoorthy +Status: Draft +License: CC0-1.0 +License-Code: MIT +Type: Informational +Created: +Post-History: https://groups.google.com/g/bitcoindev/c/PeMp2HQl-H4/m/AcJtK0aKAwAJ +Comments-URI: +``` + +- [Abstract](#abstract) +- [Copyright](#copyright) +- [Motivation](#motivation) +- [Overview](#overview) + - [Optionality of Features](#optionality-of-features) + - [Key Material and Setup](#key-material-and-setup) + - [Protocol Parties and Network Setup](#protocol-parties-and-network-setup) + - [Signing Inputs and Outputs](#signing-inputs-and-outputs) + - [General Signing Flow](#general-signing-flow) + - [Nonce Generation](#nonce-generation) + - [Identifying Disruptive Signers](#identifying-disruptive-signers) + - [Further Remarks](#further-remarks) + - [Tweaking the Threshold Public Key](#tweaking-the-threshold-public-key) +- [Algorithms](#algorithms) + - [Notation](#notation) + - [Cryptographic Types and Operations](#cryptographic-types-and-operations) + - [Auxiliary and Byte-string Operations](#auxiliary-and-byte-string-operations) + - [Key Material and Setup](#key-material-and-setup-1) + - [Signers Context](#signers-context) + - [Tweaking the Threshold Public Key](#tweaking-the-threshold-public-key-1) + - [Tweak Context](#tweak-context) + - [Applying Tweaks](#applying-tweaks) + - [Nonce Generation](#nonce-generation-1) + - [Nonce Aggregation](#nonce-aggregation) + - [Session Context](#session-context) + - [Signing](#signing) + - [Partial Signature Verification](#partial-signature-verification) + - [Partial Signature Aggregation](#partial-signature-aggregation) + - [Test Vectors \& Reference Code](#test-vectors--reference-code) +- [Remarks on Security and Correctness](#remarks-on-security-and-correctness) + - [Modifications to Nonce Generation](#modifications-to-nonce-generation) + - [Deterministic and Stateless Signing for a Single Signer](#deterministic-and-stateless-signing-for-a-single-signer) + - [Tweaking Definition](#tweaking-definition) + - [Negation of the Secret Share when Signing](#negation-of-the-secret-share-when-signing) + - [Negation of the Pubshare when Partially Verifying](#negation-of-the-pubshare-when-partially-verifying) + - [Dealing with Infinity in Nonce Aggregation](#dealing-with-infinity-in-nonce-aggregation) +- [Backwards Compatibility](#backwards-compatibility) +- [Changelog](#changelog) +- [Acknowledgments](#acknowledgments) + +## Abstract + +This document proposes a standard for the Flexible Round-Optimized Schnorr Threshold (FROST) signing protocol. The standard is compatible with [BIP340][bip340] public keys and signatures. It supports *tweaking*, which allows deriving [BIP32][bip32] child keys from the threshold public key and creating [BIP341][bip341] Taproot outputs with key and script paths. + +## Copyright + +This document is made available under [CC0 1.0 Universal](https://creativecommons.org/publicdomain/zero/1.0/). +The accompanying source code is licensed under the [MIT license](https://opensource.org/license/mit). + +## Motivation + + + +The FROST signature scheme enables threshold Schnorr signatures. In a `t`-of-`n` threshold configuration, any `t`[^t-edge-cases] participants can cooperatively produce a Schnorr signature that is indistinguishable from a signature produced by a single signer. FROST signatures are unforgeable as long as fewer than `t` participants are corrupted. The signing protocol remains functional provided that at least `t` honest participants retain access to their secret key shares. + +[^t-edge-cases]: While `t = n` and `t = 1` are in principle supported, simpler alternatives are available in these cases. In the case `t = n`, using a dedicated `n`-of-`n` multi-signature scheme such as MuSig2 (see [BIP327][bip327]) instead of FROST avoids the need for an interactive DKG. The case `t = 1` can be realized by letting one signer generate an ordinary [BIP340][bip340] key pair and transmitting the key pair to every other signer, who can check its consistency and then simply use the ordinary [BIP340][bip340] signing algorithm. Signers still need to ensure that they agree on a key pair. + +The IRTF has published [RFC 9591][rfc9591], which specifies the FROST signing protocol for several elliptic curve and hash function combinations, including secp256k1 with SHA-256, the cryptographic primitives used in Bitcoin. However, the signatures produced by RFC 9591 are incompatible with BIP340 Schnorr signatures due to the X-only public keys introduced in BIP340. Additionally, RFC 9591 does not specify key tweaking mechanisms, which are essential for Bitcoin applications such as [BIP32][bip32] key derivation and [BIP341][bip341] Taproot. This document addresses these limitations by specifying a BIP340-compatible variant of FROST signing protocol that supports key tweaking. + +Following the initial publication of the FROST protocol[[KG20][frost1]], several optimized variants have been proposed to improve computational efficiency and bandwidth optimization: FROST2[[CKM21][frost2]], FROST2-BTZ[[BTZ21][stronger-security-frost]], and FROST3[[CGRS23][olaf]]. Among these variants, FROST3 is the most efficient variant to date. + +This document specifies the FROST3 variant[^frost3-security]. The FROST3 signing protocol shares substantial similarities with the MuSig2 signing protocol specified in [BIP327][bip327]. Accordingly, this specification adopts several design principles from BIP327, including support for key tweaking, partial signature verification, and identifiable abort mechanisms. We note that significant portions of this document have been directly adapted from BIP327 due to the similarities in the signing protocols. Key generation for FROST signing is out of scope for this document. + +[^frost3-security]: The FROST3 signing scheme has been proven existentially unforgeable for both trusted dealer and distributed key generation setups. When using a trusted dealer for key generation, security reduces to the standard One-More Discrete Logarithm (OMDL) assumption. When instantiated with a distributed key generation protocol such as SimplPedPoP, security reduces to the Algebraic One-More Discrete Logarithm (AOMDL) assumption. + +## Overview + +Implementers must make sure to understand this section thoroughly to avoid subtle mistakes that may lead to catastrophic failure. + +### Optionality of Features + +The goal of this proposal is to support a wide range of possible application scenarios. +Given a specific application scenario, some features may be unnecessary or not desirable, and implementers can choose not to support them. +Such optional features include: + +- Applying plain tweaks after x-only tweaks. +- Applying tweaks at all. +- Dealing with messages that are not exactly 32 bytes. +- Identifying a disruptive signer after aborting (aborting itself remains mandatory). +If applicable, the corresponding algorithms should simply fail when encountering inputs unsupported by a particular implementation. (For example, the signing algorithm may fail when given a message which is not 32 bytes.) +Similarly, the test vectors that exercise the unimplemented features should be re-interpreted to expect an error, or be skipped if appropriate. + +### Key Material and Setup + + +A FROST key generation protocol configures a group of `n` participants with a *threshold public key* (representing a `t`-of-`n` threshold policy). +The corresponding *threshold secret key* is Shamir secret-shared among all `n` participants, where each participant holds a distinct long-term *secret share*. +This ensures that any subset of at least `t` participants can jointly run the FROST signing protocol to produce a signature under the *threshold secret key*. + +Key generation for FROST signing is out of scope for this document. Implementations can use either a trusted dealer setup, as specified in [Appendix C of RFC 9591](https://www.rfc-editor.org/rfc/rfc9591.html#name-trusted-dealer-key-generati), or a distributed key generation (DKG) protocol such as [ChillDKG](https://github.com/BlockstreamResearch/bip-frost-dkg). The appropriate choice depends on the implementations's trust model and operational requirements. + +This protocol distinguishes between two public key formats: *plain public keys* are 33-byte compressed public keys traditionally used in Bitcoin, while *X-only public keys* are 32-byte keys defined in [BIP340][bip340]. +Key generation protocols produce *public shares* and *threshold public keys* in the plain format. During signing, we conditionally negates *secret shares* to ensure the resulting threshold-signature verifies under the corresponding *X-only threshold public key*. + +> [!WARNING] +> Key generation protocols must commit the *threshold public key* to an unspendable script path as recommended in [BIP341](https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki#cite_note-23). This prevents a malicious party from embedding a hidden script path during key generation that would allow them to bypass the `t`-of-`n` threshold policy. + +#### Protocol Parties and Network Setup + +There are `u` (where `t <= u <= n < 2^32`) participants and one coordinator initiating the FROST signing protocol. +Each participant has a point-to-point communication link to the coordinator (but participants do not have direct communication links to each other). + +If there is no dedicated coordinator, one of the participants can act as the coordinator. + +#### Signing Inputs and Outputs + +Each signing session requires two inputs: a participant's long-term *secret share* `secshare_i` (individual to each participant, not shared with the coordinator) and a [*Signers Context*](#signers-context)[^signers-ctx-struct] data structure (common to all participants and the coordinator). + +[^signers-ctx-struct]: The *Signers Context* represents the public data of signing participants: their identifiers (*id1..u*) and public shares (*pubshare1..u*). +Implementations may represent this as simply as two separate lists passed to signing APIs. +The threshold public key *thresh_pk* can be stored for efficiency or recomputed when needed using *DeriveThreshPubkey*. +Similarly, the values *n* and *t* are used only for validation, and can be omitted if validation is not performed. + +This signing protocol is compatible with any key generation protocol that produces valid FROST keys. +Valid keys satisfy: (1) each *secret share* is a Shamir share of the *threshold secret key*, and (2) each *public share* equals the scalar multiplication `secshare * G`. +Implementations may **optionally** validate key compatibility for a signing session using the *ValidateSignersCtx* function. +For comprehensive validation of the entire key material, *ValidateSignersCtx* can be run on all possible `u` signing participants. + +> [!IMPORTANT] +> Passing *ValidateSignersCtx* ensures functional compatibility with the signing protocol but does not guarantee the security of the key generation protocol itself. + +The output of the FROST signing protocol is a BIP340 Schnorr signature that verifies under the *threshold public key* as if it were produced by a single signer using the *threshold secret key*. + +### General Signing Flow + +We assume that the coordinator and the signing participants (in the algorithms specified below, is stored in a data structure called [Signers Context](#signers-context)) are selected externally to the signing protocol before it is initiated. They could also optionally tweak the *threshold public key* now, by initializing [Tweak Context](#tweak-context) with it. + +The coordinator and signing participants must be determined before initiating the signing protocol. +This information is stored in a [*Signers Context*](#signers-context) data structure. +The *threshold public key* may optionally be tweaked by initializing a [*Tweak Context*](#tweak-context) at this stage. + +Whenever the signing participants want to sign a message, the basic order of operations to create a threshold-signature is as follows: + +**First broadcast round:** +Signers begin the signing session by running *NonceGen* to compute their *secnonce* and *pubnonce*.[^nonce-serialization-detail] +Each signer sends their *pubnonce* to the coordinator, who aggregates them using *NonceAgg* to produce an aggregate nonce and sends it back to all signers. + +[^nonce-serialization-detail]: We treat the *secnonce* and *pubnonce* as grammatically singular even though they include serializations of two scalars and two elliptic curve points, respectively. +This treatment may be confusing for readers familiar with the MuSig2 paper. +However, serialization is a technical detail that is irrelevant for users of MuSig2 interfaces. + +**Second broadcast round:** +At this point, every signer has the required data to sign, which, in the algorithms specified below, is stored in a data structure called [Session Context](#session-context). +Every signer computes a partial signature by running *Sign* with their long-term *secret share*, *secnonce* and the session context. +Then, the signers broadcast their partial signatures to the coordinator, who runs *PartialSigAgg* to produce the final signature. +If all parties behaved honestly, the result passes [BIP340][bip340] verification. + +![Frost signing flow](./bip-frost-signing/docs/frost-signing-flow.png) + +A malicious coordinator can cause the signing session to fail but cannot compromise the unforgeability of the scheme. Even when colluding with up to `t-1` signers, a malicious coordinator cannot forge a signature. + +> [!TIP] +> The *Sign* algorithm must **not** be executed twice with the same *secnonce*. +> Otherwise, it is possible to extract the secret signing key from the two partial signatures output by the two executions of *Sign*. +> To avoid accidental reuse of *secnonce*, an implementation may securely erase the *secnonce* argument by overwriting it with 64 zero bytes after it has been read by *Sign*. +> A *secnonce* consisting of only zero bytes is invalid for *Sign* and will cause it to fail. + +To simplify the specification of the algorithms, some intermediary values are unnecessarily recomputed from scratch, e.g., when executing *GetSessionValues* multiple times. +Actual implementations can cache these values. +As a result, the [Session Context](#session-context) may look very different in implementations or may not exist at all. +However, computation of *GetSessionValues* and storage of the result must be protected against modification from an untrusted third party. +This party would have complete control over the aggregate public key and message to be signed. + +### Nonce Generation + +*NonceGen* must have access to a high-quality random generator to draw an unbiased, uniformly random value *rand'*. +In contrast to BIP340 signing, the values *k1* and *k2* **must not be derived deterministically** from the session parameters because deriving nonces deterministically allows for a [complete key-recovery attack in multi-party discrete logarithm-based signatures](https://medium.com/blockstream/musig-dn-schnorr-multisignatures-with-verifiably-deterministic-nonces-27424b5df9d6#e3b6). + + +The optional arguments to *NonceGen* enable a defense-in-depth mechanism that may prevent secret share exposure if *rand'* is accidentally not drawn uniformly at random. +If the value *rand'* was identical in two *NonceGen* invocations, but any other argument was different, the *secnonce* would still be guaranteed to be different as well (with overwhelming probability), and thus accidentally using the same *secnonce* for *Sign* in both sessions would be avoided. +Therefore, it is recommended to provide the optional arguments *secshare*, *pubshare*, *thresh_pk*, and *m* if these session parameters are already determined during nonce generation. +The auxiliary input *extra_in* can contain additional contextual data that has a chance of changing between *NonceGen* runs, +e.g., a supposedly unique session id (taken from the application), a session counter wide enough not to repeat in practice, any nonces by other signers (if already known), or the serialization of a data structure containing multiple of the above. +However, the protection provided by the optional arguments should only be viewed as a last resort. +In most conceivable scenarios, the assumption that the arguments are different between two executions of *NonceGen* is relatively strong, particularly when facing an active adversary. + +In some applications, the coordinator may enable preprocessing of nonce generation to reduce signing latency. +Participants run *NonceGen* to generate a batch of *pubnonce* values before the message or *Signers Context*[^preprocess-round1] is known, which are stored with the coordinator (e.g., on a centralized server). +During this preprocessing phase, only the available arguments are provided to *NonceGen*. +When a signing session begins, the coordinator selects and aggregates *pubnonces* of the signing participants, enabling them to run *Sign* immediately once the message is determined. +This way, the final signature is created quicker and with fewer round trips. +However, applications that use this method presumably store the nonces for a longer time and must therefore be even more careful not to reuse them. +Moreover, this method is not compatible with the defense-in-depth mechanism described in the previous paragraph. + + +[^preprocess-round1]: When preprocessing *NonceGen* round, the *Signers Context* can be extended to include the *pubnonces* of the signing participants, as these are generated and stored before the signing session begins. + +FROST signers are typically stateful: they generate *secnonce*, store it, and later use it to produce a partial signature after receiving the aggregated nonce. +However, stateless signing is possible when one signer receives the aggregate nonce of all OTHER signers before generating their own nonce. +In coordinator-based setups, the coordinator facilitates this by collecting pubnonces from the other signers, computing their aggregate (*aggothernonce*), and providing it to the stateless signer. +The stateless signer then runs *NonceGen*, *NonceAgg*, and *Sign* in sequence, sending its *pubnonce* and partial signature simultaneously to the coordinator, who computes the final aggregate nonce for all participants. +In coordinator-less setups, any one signer can achieve stateless operation by generating their nonce after seeing all other signers' *pubnonces*. +Stateless signers may want to consider signing deterministically (see [Modifications to Nonce Generation](#modifications-to-nonce-generation)) to remove the reliance on the random number generator in the *NonceGen* algorithm. + + +### Identifying Disruptive Signers + +The signing protocol makes it possible to identify malicious signers who send invalid contributions to a signing session in order to make the signing session abort and prevent the honest signers from obtaining a valid signature. +This property is called "identifiable aborts" and ensures that honest parties can assign blame to malicious signers who cause an abort in the signing protocol. + +Aborts are identifiable for an honest party if the following conditions hold in a signing session: + +- The contributions received from all signers have not been tampered with (e.g., because they were sent over authenticated connections). +- Nonce aggregation is performed honestly (e.g., because the honest signer performs nonce aggregation on its own or because the coordinator is trusted). +- The partial signatures received from all signers are verified using the algorithm *PartialSigVerify*. + +If these conditions hold and an honest party (signer or coordinator) runs an algorithm that fails due to invalid protocol contributions from malicious signers, then the algorithm run by the honest party will output the participant identifier of exactly one malicious signer. +Additionally, if the honest parties agree on the contributions sent by all signers in the signing session, all the honest parties who run the aborting algorithm will identify the same malicious signer. + +#### Further Remarks + +Some of the algorithms specified below may also assign blame to a malicious coordinator. +While this is possible for some particular misbehavior of the coordinator, it is not guaranteed that a malicious coordinator can be identified. +More specifically, a malicious coordinator (whose existence violates the second condition above) can always make signing abort and wrongly hold honest signers accountable for the abort (e.g., by claiming to have received an invalid contribution from a particular honest signer). + +The only purpose of the algorithm *PartialSigVerify* is to ensure identifiable aborts, and it is not necessary to use it when identifiable aborts are not desired. +In particular, partial signatures are *not* signatures. +An adversary can forge a partial signature, i.e., create a partial signature without knowing the secret share for that particular participant public share.[^partialsig-forgery] +However, if *PartialSigVerify* succeeds for all partial signatures then *PartialSigAgg* will return a valid Schnorr signature. + +[^partialsig-forgery]: Assume a malicious participant intends to forge a partial signature for the participant with public share *P*. It participates in the signing session pretending to be two distinct signers: one with the public share *P* and the other with its own public share. The adversary then sets the nonce for the second signer in such a way that allows it to generate a partial signature for *P*. As a side effect, it cannot generate a valid partial signature for its own public share. An explanation of the steps required to create a partial signature forgery can be found in [this document](./bip-frost-signing/docs/partialsig_forgery.md). + +### Tweaking the Threshold Public Key + +The threshold public key can be *tweaked*, which modifies the key as defined in the [Tweaking Definition](#tweaking-definition) subsection. +In order to apply a tweak, the Tweak Context output by *TweakCtxInit* is provided to the *ApplyTweak* algorithm with the *is_xonly_t* argument set to false for plain tweaking and true for X-only tweaking. +The resulting Tweak Context can be used to apply another tweak with *ApplyTweak* or obtain the threshold public key with *GetXonlyPubkey* or *GetPlainPubkey*. + +The purpose of supporting tweaking is to ensure compatibility with existing uses of tweaking, i.e., that the result of signing is a valid signature for the tweaked public key. +The FROST signing algorithms take arbitrary tweaks as input but accepting arbitrary tweaks may negatively affect the security of the scheme.[^arbitrary-tweaks] +Instead, signers should obtain the tweaks according to other specifications. +This typically involves deriving the tweaks from a hash of the threshold public key and some other information. +Depending on the specific scheme that is used for tweaking, either the plain or the X-only threshold public key is required. +For example, to do [BIP32][bip32] derivation, you call *GetPlainPubkey* to be able to compute the tweak, whereas [BIP341][bip341] TapTweaks require X-only public keys that are obtained with *GetXonlyPubkey*. + +[^arbitrary-tweaks]: It is an open question whether allowing arbitrary tweaks from an adversary affects the unforgeability of FROST. + +The tweak mode provided to *ApplyTweak* depends on the application: +Plain tweaking can be used to derive child public keys from a threshold public key using [BIP32][bip32]. +On the other hand, X-only tweaking is required for Taproot tweaking per [BIP341][bip341]. +A Taproot-tweaked public key commits to a *script path*, allowing users to create transaction outputs that are spendable either with a FROST threshold-signature or by providing inputs that satisfy the script path. +Script path spends require a control block that contains a parity bit for the tweaked X-only public key. +The bit can be obtained with `GetPlainPubkey(tweak_ctx)[0] & 1`. + +## Algorithms + +The following specification of the algorithms has been written with a focus on clarity. As a result, the specified algorithms are not always optimal in terms of computation and space. In particular, some values are recomputed but can be cached in actual implementations (see [General Signing Flow](#general-signing-flow)). + +### Notation + +The algorithms are defined over the **[secp256k1](https://www.secg.org/sec2-v2.pdf) group and its associated scalar field**. We note that adapting this proposal to other elliptic curves is not straightforward and can result in an insecure scheme. + +#### Cryptographic Types and Operations + +We rely on the following types and conventions throughout this document: + +- **Types:** Points on the curve are represented by the object *GE*, and scalars are represented by *Scalar*. +- **Naming:** Points are denoted using uppercase letters (e.g., *P*, *Q*), while scalars are denoted using lowercase letters (e.g., *r*, *s*). +- **Mathematical Context:** Points are group elements under elliptic curve addition. The group includes all points on the secp256k1 curve plus the point at infinity (the identity element). +- **Arithmetic:** The operators +, -, and · are overloaded depending on their operands: + - **Scalar Arithmetic:**[^implicit-mod] When applied to two *Scalar* operands, +, -, and · denote integer addition, subtraction, and multiplication modulo the group order. + - **Point Addition:** When applied to two *GE* operands, + denotes the elliptic curve [group addition operation](https://en.wikipedia.org/wiki/Elliptic_curve#The_group_law). + - **Scalar Multiplication:** The notation r · P denotes [scalar multiplication](https://en.wikipedia.org/wiki/Elliptic_curve_point_multiplication) (the repeated addition of point P, r times). + +[^implicit-mod]: The algorithms in upcoming sections, when a scalar arithmetic is performed, the *mod order* is implicit. They don't spell it out. For example, no a · b mod order, they just say a · b. + +The reference code vendors the secp256k1lab library to handle underlying arithmetic, serialization, deserialization, and auxiliary functions. To improve the readability of this specification, we utilize simplified notation aliases for the library's internal methods, as mapped below: + + +| Notation | secp256k1lab | Description | +| --- | --- | --- | +| *p* | *FE.SIZE* | Field element size | +| *order* | *GE.ORDER* | Group order | +| *G* | *G* | The secp256k1 generator point | +| *inf_point* | *GE()* | The infinity point | +| *is_infinity(P)* | *P.infinity()* | Returns whether *P* is the point at infinity | +| *x(P)* | *P.x* | Returns the x-coordinate of a non-infinity point *P*, in the range *[0, p−1]* | +| *y(P)* | *P.y* | Returns the y-coordinate of a non-infinity point *P*, in the range *[0, p-1]* | +| *has_even_y(P)* | *P.has_even_y()* | Returns whether *P* has an even y-coordinate | +| *with_even_y(P)* | - | Returns the version of point *P* that has an even y-coordinate. If *P* already has an even y-coordinate (or is infinity), it is returned unchanged. Otherwise, its negation *-P* is returned | +| *xbytes(P)* | *P.to_bytes_xonly()* | Returns the 32-byte x-only serialization of a non-infinity point *P* | +| *cbytes(P)* | *P.to_bytes_compressed()* | Returns the 33-byte compressed serialization of a non-infinity point *P* | +| *cbytes_ext(P)* | *P.to_bytes_compressed
_with_infinity()* | Returns the 33-byte compressed serialization of a point *P*. If *P* is the point at infinity, it is encoded as a 33-byte array of zeros. | +| *lift_x(x)*[^liftx-soln] | *GE.lift_x(x)* | Decodes a 32-byte x-only serialization *x* into a non-infinity point P. The resulting point always has an even y-coordinate. | +| *cpoint(b)* | *GE.from_bytes_compressed(b)* | Decodes a 33-byte compressed serialization *b* into a non-infinity point | +| *cpoint_ext(b)* | *GE.from_bytes_compressed
_with_infinity(b)* | Decodes a 33-byte compressed serialization *b* into a point. If *b* is a 33-byte array of zeros, it returns the point at infinity | +| *scalar_from_bytes_checked(b)* | *Scalar.from_bytes_checked(b)* | Deserializes a 32-byte array *b* to a scalar, fails if the value is ≥ *order* | +| *scalar_from_bytes
_nonzero_checked(b)* | *Scalar.from_bytes
_nonzero_checked(b)* | Deserializes a 32-byte array *b* to a scalar, fails if the value is zero or ≥ *order* | +| *scalar_from_bytes_wrapping(b)* | *Scalar.from_bytes_wrapping(b)* | Deserializes a 32-byte array *b* to a scalar, reducing the value modulo *order* | +| *scalar_to_bytes(s)* | *s.to_bytes()* | Returns the 32-byte serialization of a scalar *s* | +| *hashtag(x)* | *tagged_hash(x)* | Computes a 32-byte domain-separated hash of the byte array *x*. The output is *SHA256(SHA256(tag) \|\| SHA256(tag) \|\| x)*, where *tag* is UTF-8 encoded string unique to the context | +| *random_bytes(n)* | - | Returns *n* bytes, sampled uniformly at random using a cryptographically secure pseudorandom number generator (CSPRNG) | +| *xor_bytes(a, b)* | *xor_bytes(a, b)* | Returns byte-wise xor of *a* and *b* | + + +[^liftx-soln]: Given a candidate X coordinate *x* in the range *0..p-1*, there exist either exactly two or exactly zero valid Y coordinates. If no valid Y coordinate exists, then *x* is not a valid X coordinate either, i.e., no point *P* exists for which *x(P) = x*. The valid Y coordinates for a given candidate *x* are the square roots of *c = x3 + 7 mod p* and they can be computed as *y = ±c(p+1)/4 mod p* (see [Quadratic residue](https://en.wikipedia.org/wiki/Quadratic_residue#Prime_or_prime_power_modulus)) if they exist, which can be checked by squaring and comparing with *c*. + +#### Auxiliary and Byte-string Operations + +The following helper functions and notation are used for operations on standard integers and byte arrays, independent of curve arithmetic. Note that like Scalars, these variables are denoted by lowercase letters (e.g., *x*, *n*); the intended type is implied by context. + +| Notation | Description | +| --- | --- | +| *\|\|* | Refers to byte array concatenation | +| *len(x)* | Returns the length of the byte array *x* in bytes | +| *x[i:j]* | Returns the sub-array of the byte array *x* starting at index *i* (inclusive) and ending at *j* (exclusive). The result has length *j - i* | +| *empty_bytestring* | A constant representing an empty byte array where length is 0 | +| *bytes(n, x)* | Returns the big-endian *n*-byte encoding of the integer *x* | +| *count(x, lst)* | Returns the number of times the element *x* occurs in the list *lst* | +| *has_duplicates(lst)* | Returns *True* if any element in *lst* appears more than once, *False* otherwise | +| *sorted(lst)* | Returns a new list containing the elements of *lst* arranged in ascending order | +| *(a, b, ...)* | Refers to a tuple containing the listed elements | + +> [!NOTE] +> In the following algorithms, all scalar arithmetic is understood to be modulo the group order. For example, *a · b* implicitly means *a · b mod order* + +### Key Material and Setup + +#### Signers Context + +The Signers Context is a data structure consisting of the following elements: + +- The total number *n* of participants involved in key generation: an integer with *2 ≤ n < 232* +- The threshold number *t* of participants required to issue a signature: an integer with *1 ≤ t ≤ n* +- The number *u* of signing participants: an integer with *t ≤ u ≤ n* +- The list of participant identifiers *id1..u*: *u* distinct integers, each with *0 ≤ idi ≤ n - 1* +- The list of participant public shares *pubshare1..u*: *u* 33-byte arrays, each a compressed serialized point +- The threshold public key *thresh_pk*: a 33-byte array, compressed serialized point + +We write "Let *(n, t, u, id1..u, pubshare1..u, thresh_pk) = signers_ctx*" to assign names to the elements of Signers Context. + +Algorithm *ValidateSignersCtx(signers_ctx)*: + +- Inputs: + - The *signers_ctx*: a [Signers Context](#signers-context) data structure +- *(n, t, u, id1..u, pubshare1..u, thresh_pk) = signers_ctx* +- Fail if *t > n* +- Fail if not *t ≤ u ≤ n* +- For *i = 1 .. u*: + - Fail if not *0 ≤ idi ≤ n - 1* + - Fail if *cpoint(pubsharei)* fails +- Fail if *has_duplicates(id1..u)* +- Fail if *DeriveThreshPubkey(id1..u, pubshare1..u) ≠ thresh_pk* +- No return + +Internal Algorithm *DeriveThreshPubkey(id1..u, pubshare1..u)*[^derive-thresh-no-validate-inputs] + +- *Q = inf_point* +- For *i = 1..u*: + - *P* = cpoint(pubsharei); fail if that fails + - *λ = DeriveInterpolatingValue(id1..u, idi)* + - *Q = Q + λ · P* +- Return *cbytes(Q)* + +[^derive-thresh-no-validate-inputs]: *DeriveThreshPubkey* does not check that its inputs are in range. This validation is performed by *ValidateSignersCtx*, which is its only caller. + +Internal Algorithm *DeriveInterpolatingValue(id1..u, my_id):* + +- Fail if *my_id* not in *id1..u* +- Fail if *has_duplicates(id1..u)* +- Let *num = Scalar(1)* +- Let *deno = Scalar(1)* +- For *i = 1..u*: + - If *idi ≠ my_id*: + - Let *num = num · Scalar(idi + 1)* + - Let *deno = deno · Scalar(idi - my_id)* +- *λ = num · deno-1* +- Return *λ* + +### Tweaking the Threshold Public Key + +#### Tweak Context + +The Tweak Context is a data structure consisting of the following elements: + +- The point *Q* representing the potentially tweaked threshold public key: a *GE* +- The accumulated tweak *tacc*: a *Scalar* +- The value *gacc*: *Scalar(1)* or *Scalar(-1)* + +We write "Let *(Q, gacc, tacc) = tweak_ctx*" to assign names to the elements of a Tweak Context. + +Algorithm *TweakCtxInit(thresh_pk):* + +- Input: + - The threshold public key *thresh_pk*: a 33-byte array, compressed serialized point +- Let *Q = cpoint(thresh_pk)*; fail if that fails +- Fail if *is_infinity(Q)* +- Let *gacc = Scalar(1)* +- Let *tacc = Scalar(0)* +- Return *tweak_ctx = (Q, gacc, tacc)* + +Algorithm *GetXonlyPubkey(tweak_ctx)*: + +- Inputs: + - The *tweak_ctx*: a [Tweak Context](#tweak-context) data structure +- Let *(Q, _, _) = tweak_ctx* +- Return *xbytes(Q)* + +Algorithm *GetPlainPubkey(tweak_ctx)*: + +- Inputs: + - The *tweak_ctx*: a [Tweak Context](#tweak-context) data structure +- Let *(Q, _, _) = tweak_ctx* +- Return *cbytes(Q)* + +#### Applying Tweaks + +Algorithm *ApplyTweak(tweak_ctx, tweak, is_xonly_t)*: + +- Inputs: + - The *tweak_ctx*: a [Tweak Context](#tweak-context) data structure + - The *tweak*: a 32-byte array, serialized scalar + - The tweak mode *is_xonly_t*: a boolean +- Let *(Q, gacc, tacc) = tweak_ctx* +- If *is_xonly_t* and not *has_even_y(Q)*: + - Let *g = Scalar(-1)* +- Else: + - Let *g = Scalar(1)* +- Let *t = scalar_from_bytes_nonzero_checked(tweak)*; fail if that fails +- Let *Q' = g · Q + t · G* + - Fail if *is_infinity(Q')* +- Let *gacc' = g · gacc* +- Let *tacc' = t + g · tacc* +- Return *tweak_ctx' = (Q', gacc', tacc')* + +### Nonce Generation + +Algorithm *NonceGen(secshare, pubshare, thresh_pk, m, extra_in)*: + +- Inputs: + - The participant secret signing share *secshare*: a 32-byte array, serialized scalar (optional argument) + - The participant public share *pubshare*: a 33-byte array, compressed serialized point (optional argument) + + - The x-only threshold public key *thresh_pk*: a 32-byte array, X-only serialized point (optional argument) + - The message *m*: a byte array (optional argument)[^max-msg-len] + - The auxiliary input *extra_in*: a byte array with *0 ≤ len(extra_in) ≤ 232-1* (optional argument) +- Let *rand' = random_bytes(32)* +- If the optional argument *secshare* is present: + - Let *rand = xor_bytes(secshare, hashFROST/aux(rand'))*[^sk-xor-rand] +- Else: + - Let *rand = rand'* +- If the optional argument *pubshare* is not present: + - Let *pubshare* = *empty_bytestring* +- If the optional argument *thresh_pk* is not present: + - Let *thresh_pk* = *empty_bytestring* +- If the optional argument *m* is not present: + - Let *m_prefixed = bytes(1, 0)* +- Else: + - Let *m_prefixed = bytes(1, 1) || bytes(8, len(m)) || m* +- If the optional argument *extra_in* is not present: + - Let *extra_in = empty_bytestring* +- Let *ki = scalar_from_bytes_wrapping(hashFROST/nonce(rand || bytes(1, len(pubshare)) || pubshare || bytes(1, len(thresh_pk)) || thresh_pk || m_prefixed || bytes(4, len(extra_in)) || extra_in || bytes(1, i - 1)))* for *i = 1,2* +- Fail if *k1 = Scalar(0)* or *k2 = Scalar(0)* +- Let *R\*,1 = k1 · G*, *R\*,2 = k2 · G* +- Let *pubnonce = cbytes(R\*,1) || cbytes(R\*,2)* +- Let *secnonce = bytes(32, k1) || bytes(32, k2)*[^secnonce-ser] +- Return *(secnonce, pubnonce)* + +[^sk-xor-rand]: The random data is hashed (with a unique tag) as a precaution against situations where the randomness may be correlated with the secret signing share itself. It is xored with the secret share (rather than combined with it in a hash) to reduce the number of operations exposed to the actual secret share. + +[^secnonce-ser]: The algorithms as specified here assume that the *secnonce* is stored as a 64-byte array using the serialization *secnonce = bytes(32, k1) || bytes(32, k2)*. The same format is used in the reference implementation and in the test vectors. However, since the *secnonce* is (obviously) not meant to be sent over the wire, compatibility between implementations is not a concern, and this method of storing the *secnonce* is merely a suggestion. The *secnonce* is effectively a local data structure of the signer which comprises the value triple *(k1, k2)*, and implementations may choose any suitable method to carry it from *NonceGen* (first communication round) to *Sign* (second communication round). In particular, implementations may choose to hide the *secnonce* in internal state without exposing it in an API explicitly, e.g., in an effort to prevent callers from reusing a *secnonce* accidentally. + +[^max-msg-len]: In theory, the allowed message size is restricted because SHA256 accepts byte strings only up to size of 2^61-1 bytes (and because of the 8-byte length encoding). + +### Nonce Aggregation + +Algorithm *NonceAgg(pubnonce1..u, id1..u)*: + +- Inputs: + - The number *u* of signing participants: an integer with *t ≤ u ≤ n* + - The list of participant public nonces *pubnonce1..u*: *u* 66-byte array, each an output of *NonceGen* + - The list of participant identifiers *id1..u*: *u* integers, each with 0 ≤ *idi* < *n* +- For *j = 1 .. 2*: + - For *i = 1 .. u*: + - Let *Ri,j = cpoint(pubnoncei[(j-1)*33:j*33])*; fail if that fails and blame signer *idi* for invalid *pubnonce* + - Let *Rj = R1,j + R2,j + ... + Ru,j* +- Return *aggnonce = cbytes_ext(R1) || cbytes_ext(R2)* + +### Session Context + +The Session Context is a data structure consisting of the following elements: + +- The *signers_ctx*: a [Signers Context](#signers-context) data structure +- The aggregate public nonce *aggnonce*: a 66-byte array, output of *NonceAgg* +- The number *v* of tweaks with *0 ≤ v < 2^32* +- The list of tweaks *tweak1..v*: *v* 32-byte arrays, each a serialized scalar +- The list of tweak modes *is_xonly_t1..v* : *v* booleans +- The message *m*: a byte array[^max-msg-len] + +We write "Let *(signers_ctx, aggnonce, v, tweak1..v, is_xonly_t1..v, m) = session_ctx*" to assign names to the elements of a Session Context. + +Algorithm *GetSessionValues(session_ctx)*: + +- Let *(signers_ctx, aggnonce, v, tweak1..v, is_xonly_t1..v, m) = session_ctx* +- *ValidateSignersCtx(signers_ctx)*; fail if that fails +- Let *(_, _, u, id1..u, pubshare1..u, thresh_pk) = signers_ctx* +- Let *tweak_ctx0 = TweakCtxInit(thresh_pk)*; fail if that fails +- For *i = 1 .. v*: + - Let *tweak_ctxi = ApplyTweak(tweak_ctxi-1, tweaki, is_xonly_ti)*; fail if that fails +- Let *(Q, gacc, tacc) = tweak_ctxv* +- Let *ser_ids* = *SerializeIds(id1..u)* +- Let *b* = *scalar_from_bytes_wrapping(hashFROST/noncecoef(ser_ids || aggnonce || xbytes(Q) || m))* +- Fail if *b = Scalar(0)* +- Let *R1 = cpoint_ext(aggnonce[0:33]), R2 = cpoint_ext(aggnonce[33:66])*; fail if that fails and blame the coordinator for invalid *aggnonce*. +- Let *R' = R1 + b · R2* +- If *is_infinity(R'):* + - Let final nonce *R = G* ([see Dealing with Infinity in Nonce Aggregation](#dealing-with-infinity-in-nonce-aggregation)) +- Else: + - Let final nonce *R = R'* +- Let *e = scalar_from_bytes_wrapping(hashBIP0340/challenge((xbytes(R) || xbytes(Q) || m)))* +- Fail if *e = Scalar(0)* +- Return (Q, gacc, tacc, id1..u, pubshare1..u, b, R, e) + +Internal Algorithm *SerializeIds(id1..u)*: + +- *res = empty_bytestring* +- For *id* in *sorted(id1..u)*: + - *res = res || bytes(4, id)* +- Return *res* + +### Signing + +Algorithm *Sign(secnonce, secshare, my_id, session_ctx)*: + +- Inputs: + - The secret nonce *secnonce* that has never been used as input to *Sign* before: a 64-byte array[^secnonce-ser] + - The participant secret signing share *secshare*: a 32-byte array, serialized scalar + - The participant identifier *my_id*: an integer with *0 ≤ my_id ≤ n-1* + - The *session_ctx*: a [Session Context](#session-context) data structure +- Let *(Q, gacc, _, id1..u, pubshare1..u, b, R, e) = GetSessionValues(session_ctx)*; fail if that fails +- Let *k1' = scalar_from_bytes_nonzero_checked(secnonce[0:32])*; fail if that fails +- Let *k2' = scalar_from_bytes_nonzero_checked(secnonce[32:64])*; fail if that fails +- Let *k1 = k1', k2 = k2'* if *has_even_y(R)*, otherwise let *k1 = -k1', k2 = -k2'* +- Let *d' = scalar_from_bytes_nonzero_checked(secshare)*; fail if that fails +- Let *P = d' · G* +- Let *pubshare = cbytes(P)* +- Fail if *pubshare* not in *pubshare1..u* +- Fail if *my_id* not in *id1..u* +- Let *λ = DeriveInterpolatingValue(id1..u, my_id)*; fail if that fails +- Let *g = Scalar(1)* if *has_even_y(Q)*, otherwise let *g = Scalar(-1)* +- Let *d = g · gacc · d'* (See [*Negation of Secret Share When Signing*](#negation-of-the-secret-share-when-signing)) +- Let *s = k1 + b · k2 + e · λ · d* +- Let *psig = scalar_to_bytes(s)* +- Let *pubnonce = cbytes(k1' · G) || cbytes(k2' · G)* +- If *PartialSigVerifyInternal(psig, my_id, pubnonce, pubshare, session_ctx)* (see below) returns failure, fail[^why-verify-partialsig] +- Return partial signature *psig* + +[^why-verify-partialsig]: Verifying the signature before leaving the signer prevents random or adversarially provoked computation errors. This prevents publishing invalid signatures which may leak information about the secret share. It is recommended but can be omitted if the computation cost is prohibitive. + +### Partial Signature Verification + +Algorithm *PartialSigVerify(psig, pubnonce1..u, signers_ctx, tweak1..v, is_xonly_t1..v, m, i)*: + +- Inputs: + - The partial signature *psig*: a 32-byte array, serialized scalar + - The list public nonces *pubnonce1..u*: *u* 66-byte arrays, each an output of *NonceGen* + - The *signers_ctx*: a [Signers Context](#signers-context) data structure + - The number *v* of tweaks with *0 ≤ v < 2^32* + - The list of tweaks *tweak1..v*: *v* 32-byte arrays, each a serialized scalar + - The list of tweak modes *is_xonly_t1..v* : *v* booleans + - The message *m*: a byte array[^max-msg-len] + - The index *i* of the signer in the list of public nonces where *0 < i ≤ u* +- Let *(_, _, u, id1..u, pubshare1..u, thresh_pk) = signers_ctx* +- Let *aggnonce = NonceAgg(pubnonce1..u, id1..u)*; fail if that fails +- Let *session_ctx = (signers_ctx, aggnonce, v, tweak1..v, is_xonly_t1..v, m)* +- Run *PartialSigVerifyInternal(psig, idi, pubnoncei, pubsharei, session_ctx)* +- Return success iff no failure occurred before reaching this point. + +Internal Algorithm *PartialSigVerifyInternal(psig, my_id, pubnonce, pubshare, session_ctx)*: + +- Let *(Q, gacc, _, id1..u, pubshare1..u, b, R, e) = GetSessionValues(session_ctx)*; fail if that fails +- Let *s = scalar_from_bytes_nonzero_checked(psig)*; fail if that fails +- Fail if *pubshare* not in *pubshare1..u* +- Fail if *my_id* not in *id1..u* +- Let *R\*,1 = cpoint(pubnonce[0:33]), R\*,2 = cpoint(pubnonce[33:66])* +- Let *Re\*' = R\*,1 + b · R\*,2* +- Let effective nonce *Re\* = Re\*'* if *has_even_y(R)*, otherwise let *Re\* = -Re\*'* +- Let *P = cpoint(pubshare)*; fail if that fails +- Let *λ = DeriveInterpolatingValue(id1..u, my_id)*[^lambda-cant-fail] +- Let *g = Scalar(1)* if *has_even_y(Q)*, otherwise let *g = Scalar(-1)* +- Let *g' = g · gacc* (See [*Negation of Pubshare When Partially Verifying*](#negation-of-the-pubshare-when-partially-verifying)) +- Fail if *s · G ≠ Re\* + e · λ · g' · P* +- Return success iff no failure occurred before reaching this point. + +[^lambda-cant-fail]: *DeriveInterpolatingValue(id1..u, my_id)* cannot fail when called from *PartialSigVerifyInternal* as *PartialSigVerify* picks *my_id* from *id1..u* + +### Partial Signature Aggregation + +Algorithm *PartialSigAgg(psig1..u, id1..u, session_ctx)*: + +- Inputs: + - The number *u* of signatures with *t ≤ u ≤ n* + - The list of partial signatures *psig1..u*: *u* 32-byte arrays, each an output of *Sign* + - The list of participant identifiers *id1..u*: *u* distinct integers, each with *0 ≤ idi ≤ n-1* + - The *session_ctx*: a [Session Context](#session-context) data structure +- Let *(Q, _, tacc, _, _, _, R, e) = GetSessionValues(session_ctx)*; fail if that fails +- For *i = 1 .. u*: + - Let *si = scalar_from_bytes_nonzero_checked(psigi)*; fail if that fails and blame signer *idi* for invalid partial signature. +- Let *g = Scalar(1)* if *has_even_y(Q)*, otherwise let *g = Scalar(-1)* +- Let *s = s1 + ... + su + e · g · tacc* +- Return *sig = xbytes(R) || scalar_to_bytes(s)* + +### Test Vectors & Reference Code + +We provide a naive, highly inefficient, and non-constant time [pure Python 3 reference implementation of the threshold public key tweaking, nonce generation, partial signing, and partial signature verification algorithms](./reference/reference.py). + +Standalone JSON test vectors are also available in the [same directory](./reference/vectors/), to facilitate porting the test vectors into other implementations. + +> [!CAUTION] +> The reference implementation is for demonstration purposes only and not to be used in production environments. + +## Remarks on Security and Correctness + +### Modifications to Nonce Generation + +Implementers must avoid modifying the *NonceGen* algorithm without being fully aware of the implications. +We provide two modifications to *NonceGen* that are secure when applied correctly and may be useful in special circumstances, summarized in the following table. + +| | needs secure randomness | needs secure counter | needs to keep state securely | needs aggregate nonce of all other signers (only possible for one signer) | +| --- | --- | --- | --- | --- | +| **NonceGen** | ✓ | | ✓ | | +| **CounterNonceGen** | | ✓ | ✓ | | +| **DeterministicSign** | | | | ✓ | + +First, on systems where obtaining uniformly random values is much harder than maintaining a global atomic counter, it can be beneficial to modify *NonceGen*. +The resulting algorithm *CounterNonceGen* does not draw *rand'* uniformly at random but instead sets *rand'* to the value of an atomic counter that is incremented whenever it is read. +With this modification, the secret share *secshare* of the signer generating the nonce is **not** an optional argument and must be provided to *NonceGen*. +The security of the resulting scheme then depends on the requirement that reading the counter must never yield the same counter value in two *NonceGen* invocations with the same *secshare*. + +Second, if there is a unique signer who generates their nonce last (i.e., after receiving the aggregate nonce from all other signers), it is possible to modify nonce generation for this single signer to not require high-quality randomness. +Such a nonce generation algorithm *DeterministicSign* is specified below. +Note that the only optional argument is *rand*, which can be omitted if randomness is entirely unavailable. +*DeterministicSign* requires the argument *aggothernonce* which should be set to the output of *NonceAgg* run on the *pubnonce* value of **all** other signers (but can be provided by an untrusted party). +Hence, using *DeterministicSign* is only possible for the last signer to generate a nonce and makes the signer stateless, similar to the stateless signer described in the [Nonce Generation](#nonce-generation) section. + + +#### Deterministic and Stateless Signing for a Single Signer + +Algorithm *DeterministicSign(secshare, my_id, aggothernonce, signers_ctx, tweak1..v, is_xonly_t1..v, m, rand)*: + +- Inputs: + - The participant secret signing share *secshare*: a 32-byte array, serialized scalar + - The participant identifier *my_id*: an integer with *0 ≤ my_id ≤ n-1* + - The aggregate public nonce *aggothernonce* (see [above](#modifications-to-nonce-generation)): a 66-byte array, output of *NonceAgg* + - The *signers_ctx*: a [Signers Context](#signers-context) data structure + - The number *v* of tweaks with *0 ≤ v < 2^32* + - The list of tweaks *tweak1..v*: *v* 32-byte arrays, each a serialized scalar + - The list of tweak methods *is_xonly_t1..v*: *v* booleans + - The message *m*: a byte array[^max-msg-len] + - The auxiliary randomness *rand*: a 32-byte array, serialized scalar (optional argument) +- If the optional argument *rand* is present: + - Let *secshare' = xor_bytes(secshare, hashFROST/aux(rand))* +- Else: + - Let *secshare' = secshare* +- Let *(_, _, u, id1..u, pubshare1..u, thresh_pk) = signers_ctx* +- Let *tweak_ctx0 = TweakCtxInit(thresh_pk)*; fail if that fails +- For *i = 1 .. v*: + - Let *tweak_ctxi = ApplyTweak(tweak_ctxi-1, tweaki, is_xonly_ti)*; fail if that fails +- Let *tweaked_tpk = GetXonlyPubkey(tweak_ctxv)* +- Let *ki = scalar_from_bytes_wrapping(hashFROST/deterministic/nonce(secshare' || aggothernonce || tweaked_tpk || bytes(8, len(m)) || m || bytes(1, i - 1)))* for *i = 1,2* +- Fail if *k1 = Scalar(0)* or *k2 = Scalar(0)* +- Let *R\*,1 = k1 · G, R\*,2 = k2 · G* +- Let *pubnonce = cbytes(R\*,1) || cbytes(R\*,2)* +- Let *d = scalar_from_bytes_nonzero_checked(secshare')*; fail if that fails +- Let *my_pubshare = cbytes(d · G)* +- Fail if *my_pubshare* is not present in *pubshare1..u* +- Let *secnonce = scalar_to_bytes(k1) || scalar_to_bytes(k2)* +- Let *aggnonce = NonceAgg((pubnonce, aggothernonce), (my_id, COORDINATOR_ID))*[^coordinator-id-sentinel]; fail if that fails and blame coordinator for invalid *aggothernonce*. +- Let *session_ctx = (signers_ctx, aggnonce, v, tweak1..v, is_xonly_t1..v, m)* +- Return (pubnonce, Sign(secnonce, secshare, my_id, session_ctx)) + +[^coordinator-id-sentinel]: *COORDINATOR_ID* is a sentinel value (not an actual participant identifier) used to track the source of *aggothernonce* for error attribution. If *NonceAgg* fails, the coordinator is blamed for providing an invalid *aggothernonce*. In the reference implementation, *COORDINATOR_ID* is represented as `None`. + +### Tweaking Definition + +Two modes of tweaking the threshold public key are supported. They correspond to the following algorithms: + +Algorithm *ApplyPlainTweak(P, t)*: + +- Inputs: + - *P*: a point + - The tweak *t*: a scalar +- Return *P + t · G* + +Algorithm *ApplyXonlyTweak(P, t)*: + +- Inputs: + - *P*: a point + - The tweak *t*: a scalar +- Return *with_even_y(P) + t · G* + + +### Negation of the Secret Share when Signing + +> [!NOTE] +> In the following equations, all scalar arithmetic is understood to be modulo the group order, as specified in the [Notation](#notation) section. + +During the signing process, the *[Sign](#signing)* algorithm might have to negate the secret share in order to produce a partial signature for an X-only threshold public key, which may be tweaked *v* times (X-only or plain). + +The following elliptic curve points arise as intermediate steps when creating a signature: + +- The values *Pi* (pubshare), *di'* (secret share), and *Q0* (threshold public key) are produced by a FROST key generation protocol. We have +
+    Pi = di'·G
+    Q0 = λid1·P1 + λid2·P2 + ... + λidu·Pu
+  
+ Here, *λidi* denotes the interpolating value for the *i*-th signing participant in the [Signers Context](#signers-context). + +- *Qi* is the tweaked threshold public key after the *i*-th execution of *ApplyTweak* for *1 ≤ i ≤ v*. It holds that +
+    Qi = f(i-1) + ti·G for i = 1, ..., v where
+      f(i-1) := with_even_y(Qi-1) if is_xonly_ti and
+      f(i-1) := Qi-1 otherwise.
+  
+- *with_even_y(Q*v*)* is the final result of the threshold public key tweaking operations. It corresponds to the output of *GetXonlyPubkey* applied on the final Tweak Context. + +The signer's goal is to produce a partial signature corresponding to the final result of threshold pubkey derivation and tweaking, i.e., the X-only public key *with_even_y(Qv)*. + +For *1 ≤ i ≤ v*, we denote the value *g* computed in the *i*-th execution of *ApplyTweak* by *gi-1*. Therefore, *gi-1* equals *Scalar(-1)* if and only if *is_xonly_ti* is true and *Qi-1* has an odd Y coordinate. In other words, *gi-1* indicates whether *Qi-1* needed to be negated to apply an X-only tweak: +
+  f(i-1) = gi-1·Qi-1 for 1 ≤ i ≤ v
+
+Furthermore, the *Sign* and *PartialSigVerify* algorithms set value *g* depending on whether Qv needed to be negated to produce the (X-only) final output. For consistency, this value *g* is referred to as *gv* in this section. +
+  with_even_y(Qv) = gv·Qv
+
+ +So, the (X-only) final public key is +
+  with_even_y(Qv)
+    = gv·Qv
+    = gv·(f(v-1) + tv·G)
+    = gv·(gv-1·(f(v-2) + tv-1·G) + tv·G)
+    = gv·gv-1·f(v-2) + gv·(tv + gv-1·tv-1)·G
+    = gv·gv-1·f(v-2) + (sumi=v-1..v ti · prodj=i..v gj)·G
+    = gv·gv-1· ... ·g1·f(0) + (sumi=1..v ti · prodj=i..v gj)·G
+    = gv· ... ·g0·Q0 + gv·taccv·G
+
+where tacci is computed by TweakCtxInit and ApplyTweak as follows: +
+  tacc0 = 0
+  tacci = ti + gi-1·tacci-1 for i=1..v
+
+ for which it holds that +
+  gv·taccv = sumi=1..v ti · prodj=i..v gj
+
+ +*TweakCtxInit* and *ApplyTweak* compute +
+  gacc0 = 1
+  gacci = gi-1 · gacci-1 for i=1..v
+
+So we can rewrite above equation for the final public key as +
+  with_even_y(Qv) = gv · gaccv · Q0 + gv · taccv · G
+
+ +Then we have +
+  with_even_y(Qv) - gv·taccv·G
+    = gv·gaccv·Q0
+    = gv·gaccv·(λid1·P1 + ... + λidu·Pu)
+    = gv·gaccv·(λid1·d1'·G + ... + λidu·du'·G)
+    = sumj=1..u(gv·gaccv·λidj·dj')·G
+
+ +Intuitively, *gacci* tracks accumulated sign flipping and *tacci* tracks the accumulated tweak value after applying the first *i* individual tweaks. Additionally, *gv* indicates whether *Qv* needed to be negated to produce the final X-only result. Thus, participant *i* multiplies their secret share *di'* with *gv·gaccv* in the [*Sign*](#signing) algorithm. + +#### Negation of the Pubshare when Partially Verifying + +As explained in [Negation Of The Secret Share When Signing](#negation-of-the-secret-share-when-signing) the signer uses a possibly negated secret share +
+  d = gv·gaccv·d'
+
+when producing a partial signature to ensure that the aggregate signature will correspond to a threshold public key with even Y coordinate. + +The [*PartialSigVerifyInternal*](#partial-signature-verification) algorithm is supposed to check +
+  s·G = Re* + e·λ·d·G
+
+ +The verifier doesn't have access to *d · G* but can construct it using the participant *pubshare* as follows: +
+d·G
+  = gv · gaccv · d' · G
+  = gv · gaccv · cpoint(pubshare)
+
+Note that the threshold public key and list of tweaks are inputs to partial signature verification, so the verifier can also construct *gv* and *gaccv*. + +### Dealing with Infinity in Nonce Aggregation + +If the coordinator provides *aggnonce = bytes(33,0) || bytes(33,0)*, either the coordinator is dishonest or there is at least one dishonest signer (except with negligible probability). +If signing aborted in this case, it would be impossible to determine who is dishonest. +Therefore, signing continues so that the culprit is revealed when collecting and verifying partial signatures. + +However, the final nonce *R* of a BIP340 Schnorr signature cannot be the point at infinity. +If we would nonetheless allow the final nonce to be the point at infinity, then the scheme would lose the following property: +if *PartialSigVerify* succeeds for all partial signatures, then *PartialSigAgg* will return a valid Schnorr signature. +Since this is a valuable feature, we modify [FROST3 signing][roast] to avoid producing an invalid Schnorr signature while still allowing detection of the dishonest signer: In *GetSessionValues*, if the final nonce *R* would be the point at infinity, set it to the generator instead (an arbitrary choice). + +This modification to *GetSessionValues* does not affect the unforgeability of the scheme. +Given a successful adversary against the unforgeability game (EUF-CMA) for the modified scheme, a reduction can win the unforgeability game for the original scheme by simulating the modification towards the adversary: +When the adversary provides *aggnonce' = bytes(33, 0) || bytes(33, 0)*, the reduction sets *aggnonce = cbytes_ext(G) || bytes(33, 0)*. +For any other *aggnonce'*, the reduction sets *aggnonce = aggnonce'*. +(The case that the adversary provides an *aggnonce' ≠ bytes(33, 0) || bytes(33, 0)* but nevertheless *R'* in *GetSessionValues* is the point at infinity happens only with negligible probability.) + +## Backwards Compatibility + +This document proposes a standard for the FROST threshold signature scheme that is compatible with [BIP340][bip340]. FROST is *not* compatible with ECDSA signatures traditionally used in Bitcoin. + +## Changelog + +- *0.3.4* (2026-01-01): Add an example file to the reference code. +- *0.3.3* (2025-12-29): Replace the lengthy Introduction section with a concise Motivation section. +- *0.3.2* (2025-12-20): Use 2-of-3 keys in test vectors. +- *0.3.1* (2025-12-17): Update the Algorithms section to use secp256k1lab methods and types. +- *0.3.0* (2025-12-15): Introduces the following changes: + - Introduce *SignersContext* and define key material compatibility with *ValidateSignersCtx*. + - Rewrite the signing protocol assuming a coordinator, add sequence diagram, and warn key generation protocols to output Taproot-safe *threshold public key*. + - Remove *GetSessionInterpolatingValue*, *SessionHasSignerPubshare*, *ValidatePubshares*, and *ValidateThreshPubkey* algorithms + - Revert back to initializing *TweakCtxInit* with threshold public key instead of *pubshares* +- *0.2.3* (2025-11-25): Sync terminologies with the ChillDKG BIP. +- *0.2.2* (2025-11-11): Remove key generation test vectors as key generation is out of scope for this specification. +- *0.2.1* (2025-11-10): Vendor secp256k1lab library to provide `Scalar` and `GE` primitives. Restructure reference implementation into a Python package layout. +- *0.2.0* (2025-04-11): Includes minor fixes and the following major changes: + - Initialize *TweakCtxInit* using individual *pubshares* instead of the threshold public key. + - Add Python script to automate generation of test vectors. + - Represent participant identifiers as 4-byte integers in the range `0..n - 1` (inclusive). +- *0.1.0* (2024-07-31): Publication of draft BIP on the bitcoin-dev mailing list + +## Acknowledgments + +We thank Jonas Nick, Tim Ruffing, Jesse Posner, and Sebastian Falbesoner for their contributions to this document. + + +[bip32]: https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki +[bip340]: https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki +[bip341]: https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki + +[bip327]: https://github.com/bitcoin/bips/blob/master/bip-0327.mediawiki +[frost1]: https://eprint.iacr.org/2020/852 +[frost2]: https://eprint.iacr.org/2021/1375 +[stronger-security-frost]: https://eprint.iacr.org/2022/833 +[olaf]: https://eprint.iacr.org/2023/899 +[roast]: https://eprint.iacr.org/2022/550 + +[rfc9591]: https://www.rfc-editor.org/rfc/rfc9591.html diff --git a/bip-frost-signing/.markdownlint.json b/bip-frost-signing/.markdownlint.json new file mode 100644 index 0000000000..b0578f5e45 --- /dev/null +++ b/bip-frost-signing/.markdownlint.json @@ -0,0 +1,9 @@ +{ + "first-line-heading": false, + "line-length": false, + "no-inline-html": { "allowed_elements": ["sub", "sup", "pre"] }, + "no-duplicate-heading": { "siblings_only": true }, + "emphasis-style": { "style": "asterisk" }, + "strong-style": { "style": "asterisk" }, + "ul-style": { "style": "dash" } +} \ No newline at end of file diff --git a/bip-frost-signing/all.sh b/bip-frost-signing/all.sh new file mode 100755 index 0000000000..f94bf3c558 --- /dev/null +++ b/bip-frost-signing/all.sh @@ -0,0 +1,18 @@ +#!/bin/sh + +set -euo pipefail + +check_availability() { + command -v "$1" > /dev/null 2>&1 || { + echo >&2 "$1 is required but it's not installed. Aborting."; + exit 1; + } +} + +check_availability markdownlint-cli2 + +markdownlint-cli2 ../bip-frost-signing.md --config ./.markdownlint.json || true + +cd python || exit 1 +./tests.sh +./example.py \ No newline at end of file diff --git a/bip-frost-signing/docs/frost-signing-flow.png b/bip-frost-signing/docs/frost-signing-flow.png new file mode 100644 index 0000000000000000000000000000000000000000..f0fb210930223684da299ee6c62cdb5fe7683c9f GIT binary patch literal 453113 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

o^^Wc6d-=Qc*i(4js`$uTg~`%bX_e$I?js+DEN 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 literal 0 HcmV?d00001 diff --git a/bip-frost-signing/docs/partialsig_forgery.md b/bip-frost-signing/docs/partialsig_forgery.md new file mode 100644 index 0000000000..144ef39a98 --- /dev/null +++ b/bip-frost-signing/docs/partialsig_forgery.md @@ -0,0 +1,63 @@ +This is an adaptation of the MuSig2's partial signature forgery for the FROST protocol, described by Adam Gibson. You can find the [original write-up here](https://gist.github.com/AdamISZ/ca974ed67889cedc738c4a1f65ff620b). + +In FROST signing, a malicious participant could forge the partial signature (i.e., _PartialSigVerify_ on it will succeed) of another participant without knowing their secret share, but only under the following conditions: +- The victim does not participate in the signing. +- The malicious participant impersonates the victim while also participating with their original share, making it appear as if two different participants are involved in the signing. + +As a consequence, the malicious signing participant will be unable to create a valid partial signature for their original secret share. + +1. +Key Setup: Let's consider a 3-of-5 FROST policy among a group of participants $\{P_1, P_2, P_3, P_4, P_5\}$ with the following details: + +- The participant identifiers are $I = \{1, 2, 3, 4, 5\}$. +- Each participant's public share is denoted by $X_i \;\forall i \in I$. +- Each participant's secret share is denoted by $x_i \;\forall i \in I$. +- The threshold public key is denoted by $\tilde{X}$. + +2. +Signing Setup: Assume we start a signing session with $S = \{P_1, P_2, P_4^*, P_5\}$. The adversarial participant will take the role of both $P_4^*$ and $P_5$, and will forge a partial signature on the public share $X_4$ without knowing the corresponding secret share $x_4$, on a given message $m$. +> [!NOTE] +> In this scenario, the malicious participant $P_5$ is pretending to be the participant $P_4^*$ while also participating with their legitimate share. The real $P_4$ is unaware of this signing session. + +3. +The adversary receives the nonces from other signing participants: $(R_{1,1}, R_{1,2}), (R_{2,1}, R_{2,2})$. + +4. +The adversary sets aggregate partial nonces $R_1, R_2$ at random, without yet choose the individual pairs of partial nonces for $P_4^*$ and $P_5$. This is the 'cheat', which allows him to precalculate: + +Calculate $\tilde{X} = \sum\limits_{i \in S} \lambda_{i, S} \cdot X_{i}$ + +Calculate $b = \mathbb{H}(\tilde{X}, R_1, R_2, m)$ + +Calculate $\tilde{R} = R_1 + b \cdot R_2$ + +Calculate $R_{1}^{*} = R_1 - \Sigma_{k=1}^2 R_{k,1}$ + +Calculate $R_{2}^{*} = R_2 - \Sigma_{k=1}^2 R_{k,2}$ + +The last two values $R_{i}^{*}$ are 'what is left over' to be filled in by the adversary's nonce values. + +5. +To create nonces such that the forgery $s_4$ on $X_4$ verifies, the adversary does this: + +Choose $s_4$ at random. + +Calculate $Q = s_{4} \cdot G - \mathbb{H}\left(\tilde{X}, \tilde{R}, m\right)\lambda_{4, S}X_4$ + +Choose $R_{4,1}$ at random. + +Calculate $R_{4,2} = b^{-1} \cdot \left (Q - R_{4,1}\right)$ + +Now, $s_4$ *will* successfully pass the *PartialSigVerify* for the public share $X_4$, message $m$, and the signer set $S$, but only if: + +$R_{5,1} = R_{1}^{*} - R_{4,1}$ +and +$R_{5,2} = R_{2}^{*} - R_{4,2}$ + +So, concluding the first communication round of the signing protocol, the adversary shares the rogue nonce values $R_{4,1}, R_{4,2}, R_{5,1}, R_{5,2}$ as calculated above. + +Moving to the second communication round, the adversary can present the forged partial signature $s_4$. However, the adversary is unable to produce a valid $s_5$, as evident from examining the partial signature verification equation for $P_5$: + +$$s_{5} \cdot G = R_{5,1} + b \cdot R_{5,2} + \mathbb{H}\left(\tilde{X}, \tilde{R}, m\right)\lambda_{5, S}X_5$$ + +The RHS is entirely fixed by the previous steps, and thus the LHS is a point whose discrete log cannot be extracted. In other words, the values $R_{5,1}, R_{5,2}$ were determined by the choices of nonces at other indices, and their secret values cannot be deduced (unlike in honest signing, where they are pre-determined by the signer). Therefore, **even though the adversary knows the secret share** $x_5$, **they are still unable to successfully complete the FROST protocol execution**. diff --git a/bip-frost-signing/python/.ruff.toml b/bip-frost-signing/python/.ruff.toml new file mode 100644 index 0000000000..d8cd9cc98f --- /dev/null +++ b/bip-frost-signing/python/.ruff.toml @@ -0,0 +1,3 @@ +[format] +# Exclude vendored package. +exclude = ["secp256k1lab/*"] \ No newline at end of file diff --git a/bip-frost-signing/python/example.py b/bip-frost-signing/python/example.py new file mode 100755 index 0000000000..da0cd6c2db --- /dev/null +++ b/bip-frost-signing/python/example.py @@ -0,0 +1,320 @@ +#!/usr/bin/env python3 + +"""Example of a full FROST signing session.""" + +from typing import List, Tuple +import asyncio +import argparse +import secrets + +# Import frost_ref first to set up secp256k1lab path +from frost_ref import ( + nonce_gen, + nonce_agg, + sign, + partial_sig_agg, + partial_sig_verify, + SignersContext, + SessionContext, + PlainPk, +) +from frost_ref.signing import ( + thresh_pubkey_and_tweak, + get_xonly_pk, + partial_sig_verify_internal, +) + +from secp256k1lab.bip340 import schnorr_verify +from trusted_dealer import trusted_dealer_keygen + + +# +# Network mocks to simulate full FROST signing sessions +# + + +class CoordinatorChannels: + def __init__(self, n): + self.n = n + self.queues = [asyncio.Queue() for _ in range(n)] + self.participant_queues = None + + 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: int) -> bytes: + return await self.queues[i].get() + + +class ParticipantChannel: + def __init__(self, coord_queue): + self.queue = asyncio.Queue() + self.coord_queue = coord_queue + + def send(self, m): + self.coord_queue.put_nowait(m) + + async def receive(self): + return await self.queue.get() + + +# +# Helper functions +# + + +def generate_frost_keys( + n: int, t: int +) -> Tuple[PlainPk, List[int], List[bytes], List[PlainPk]]: + """Generate t-of-n FROST keys using trusted dealer. + + Returns: + thresh_pk: Threshold public key (33-byte compressed) + ids: List of signer IDs (0-indexed: 0, 1, ..., n-1) + secshares: List of secret shares (32-byte scalars) + pubshares: List of public shares (33-byte compressed) + """ + thresh_pk, secshares, pubshares = trusted_dealer_keygen( + secrets.token_bytes(32), n, t + ) + + assert len(secshares) == n + ids = list(range(len(secshares))) # ids are 0..n-1 + + return thresh_pk, ids, secshares, pubshares + + +# +# Protocol parties +# + + +async def participant( + chan: ParticipantChannel, + secshare: bytes, + pubshare: PlainPk, + my_id: int, + signers_ctx: SignersContext, + tweaks: List[bytes], + is_xonly: List[bool], + msg: bytes, +) -> Tuple[bytes, bytes]: + """ + Participant in FROST signing protocol. + + Returns: + (psig, final_sig): Partial signature and final BIP340 signature + """ + # Get tweaked threshold pubkey + tweak_ctx = thresh_pubkey_and_tweak(signers_ctx.thresh_pk, tweaks, is_xonly) + tweaked_thresh_pk = get_xonly_pk(tweak_ctx) + + # Round 1: Nonce generation + secnonce, pubnonce = nonce_gen(secshare, pubshare, tweaked_thresh_pk, msg, None) + chan.send(pubnonce) + aggnonce = await chan.receive() + + # Round 2: Signing + session_ctx = SessionContext(aggnonce, signers_ctx, tweaks, is_xonly, msg) + psig = sign(secnonce, secshare, my_id, session_ctx) + assert partial_sig_verify_internal(psig, my_id, pubnonce, pubshare, session_ctx), ( + "Partial signature verification failed" + ) + chan.send(psig) + + # Receive final signature + final_sig = await chan.receive() + return (psig, final_sig) + + +async def coordinator( + chans: CoordinatorChannels, + signers_ctx: SignersContext, + tweaks: List[bytes], + is_xonly: List[bool], + msg: bytes, +) -> bytes: + """ + Coordinator in FROST signing protocol. + + Returns: + final_sig: Final BIP340 signature (64 bytes) + """ + # Determine the signers + signer_ids = signers_ctx.ids + num_signers = len(signer_ids) + + # Round 1: Collect pubnonces + pubnonces = [] + for i in range(num_signers): + pubnonce = await chans.receive_from(i) + pubnonces.append(pubnonce) + + # Aggregate nonces + aggnonce = nonce_agg(pubnonces, signer_ids) + chans.send_all(aggnonce) + + # Round 2: Collect partial signatures + session_ctx = SessionContext(aggnonce, signers_ctx, tweaks, is_xonly, msg) + psigs = [] + for i in range(num_signers): + psig = await chans.receive_from(i) + assert partial_sig_verify( + psig, pubnonces, signers_ctx, tweaks, is_xonly, msg, i + ), f"Partial signature verification failed for singer {i}" + psigs.append(psig) + + # Aggregate partial signatures + final_sig = partial_sig_agg(psigs, signer_ids, session_ctx) + chans.send_all(final_sig) + + return final_sig + + +# +# Signing Session +# + + +def simulate_frost_signing( + secshares: List[bytes], + signers_ctx: SignersContext, + msg: bytes, + tweaks: List[bytes], + is_xonly: List[bool], +) -> Tuple[bytes, List[bytes]]: + """Run a full FROST signing session. + + Returns: + (final_sig, psigs): Final signature and list of partial signatures + """ + # Extract signer set from signers_ctx + signer_ids = signers_ctx.ids + pubshares = signers_ctx.pubshares + num_signers = len(signer_ids) + + async def session(): + # Set up channels + coord_chans = CoordinatorChannels(num_signers) + participant_chans = [ + ParticipantChannel(coord_chans.queues[i]) for i in range(num_signers) + ] + coord_chans.set_participant_queues( + [participant_chans[i].queue for i in range(num_signers)] + ) + + # Create coroutines + coroutines = [coordinator(coord_chans, signers_ctx, tweaks, is_xonly, msg)] + [ + participant( + participant_chans[i], + secshares[i], + pubshares[i], + signer_ids[i], + signers_ctx, + tweaks, + is_xonly, + msg, + ) + for i in range(num_signers) + ] + + return await asyncio.gather(*coroutines) + + results = asyncio.run(session()) + final_sig = results[0] + psigs = [r[0] for r in results[1:]] # Extract psigs from participant results + return final_sig, psigs + + +def main(): + parser = argparse.ArgumentParser(description="FROST Signing example") + parser.add_argument( + "t", nargs="?", type=int, default=2, help="Threshold [default=2]" + ) + parser.add_argument( + "n", nargs="?", type=int, default=3, help="Participants [default=3]" + ) + args = parser.parse_args() + + t, n = args.t, args.n + assert 2 <= t <= n, "Threshold t must satisfy 2 <= t <= n" + + print("====== FROST Signing example session ======") + print(f"Using n = {n} participants and a threshold of t = {t}.") + print() + + # 1. Generate FROST keys + thresh_pk, all_ids, all_secshares, all_pubshares = generate_frost_keys(n, t) + + print("=== Key Configuration ===") + print(f"Threshold public key: {thresh_pk.hex()}") + print() + print("=== Public shares ===") + for i, pubshare in enumerate(all_pubshares): + print(f" Participant {all_ids[i]}: {pubshare.hex()}") + print() + + # 2. Select first t signers + signer_indices = list(range(t)) + signer_ids = [all_ids[i] for i in signer_indices] + signer_secshares = [all_secshares[i] for i in signer_indices] + signer_pubshares = [all_pubshares[i] for i in signer_indices] + + # 3. Initialize the signers context + print("=== Signing Set ===") + print(f"Selected signers: {signer_ids}") + print() + signers_ctx = SignersContext(n, t, signer_ids, signer_pubshares, thresh_pk) + + # 4. Create message and tweaks + msg = secrets.token_bytes(32) + + # Apply both plain (BIP32-style) and xonly (BIP341-style) tweaks + tweaks = [secrets.token_bytes(32), secrets.token_bytes(32)] + is_xonly = [False, True] # First: plain (BIP32), Second: xonly (BIP341) + + tweak_ctx = thresh_pubkey_and_tweak(thresh_pk, tweaks, is_xonly) + tweaked_thresh_pk = get_xonly_pk(tweak_ctx) + + print("=== Message and Tweaks ===") + print(f"Message: {msg.hex()}") + print(f"Tweak 1 (plain/BIP32): {tweaks[0].hex()}") + print(f"Tweak 2 (xonly/BIP341): {tweaks[1].hex()}") + print(f"Tweaked threshold public key: {tweaked_thresh_pk.hex()}") + print() + + # 5. Run signing protocol + final_sig, psigs = simulate_frost_signing( + signer_secshares, + signers_ctx, + msg, + tweaks, + is_xonly, + ) + + print("=== Participants Partial Signatures ===") + for i, psig in enumerate(psigs): + print(f" Participant {signer_ids[i]}: {psig.hex()}") + print() + + print("=== Final Signature ===") + print(f"BIP340 signature: {final_sig.hex()}") + print() + + # 6. Verify signature + assert schnorr_verify(msg, tweaked_thresh_pk, final_sig) + print("=== Verification ===") + print("Signature verified successfully!") + + +if __name__ == "__main__": + main() diff --git a/bip-frost-signing/python/frost_ref/__init__.py b/bip-frost-signing/python/frost_ref/__init__.py new file mode 100644 index 0000000000..d6a00bb380 --- /dev/null +++ b/bip-frost-signing/python/frost_ref/__init__.py @@ -0,0 +1,43 @@ +from pathlib import Path +import sys + +# Add the vendored copy of secp256k1lab to path. +sys.path.append(str(Path(__file__).parent / "../secp256k1lab/src")) + +from .signing import ( + # Functions + validate_signers_ctx, + nonce_gen, + nonce_agg, + sign, + deterministic_sign, + partial_sig_verify, + partial_sig_agg, + # Exceptions + InvalidContributionError, + # Types + PlainPk, + XonlyPk, + SignersContext, + TweakContext, + SessionContext, +) + +__all__ = [ + # Functions + "validate_signers_ctx", + "nonce_gen", + "nonce_agg", + "sign", + "deterministic_sign", + "partial_sig_verify", + "partial_sig_agg", + # Exceptions + "InvalidContributionError", + # Types + "PlainPk", + "XonlyPk", + "SignersContext", + "TweakContext", + "SessionContext", +] diff --git a/bip-frost-signing/python/frost_ref/signing.py b/bip-frost-signing/python/frost_ref/signing.py new file mode 100644 index 0000000000..9961c07700 --- /dev/null +++ b/bip-frost-signing/python/frost_ref/signing.py @@ -0,0 +1,500 @@ +# BIP FROST Signing reference implementation +# +# It's worth noting that many functions, types, and exceptions were directly +# copied or modified from the MuSig2 (BIP 327) reference code, found at: +# https://github.com/bitcoin/bips/blob/master/bip-0327/reference.py +# +# WARNING: This implementation is for demonstration purposes only and _not_ to +# be used in production environments. The code is vulnerable to timing attacks, +# for example. + +from typing import List, Optional, Tuple, NewType, NamedTuple, Sequence, Literal +import secrets + +from secp256k1lab.secp256k1 import G, GE, Scalar +from secp256k1lab.util import int_from_bytes, tagged_hash, xor_bytes + +PlainPk = NewType("PlainPk", bytes) +XonlyPk = NewType("XonlyPk", bytes) +ContribKind = Literal[ + "aggothernonce", "aggnonce", "psig", "pubkey", "pubnonce", "pubshare" +] + +# There are two types of exceptions that can be raised by this implementation: +# - ValueError for indicating that an input doesn't conform to some function +# precondition (e.g. an input array is the wrong length, a serialized +# representation doesn't have the correct format). +# - InvalidContributionError for indicating that a signer (or the +# coordinator) is misbehaving in the protocol. +# +# Assertions are used to (1) satisfy the type-checking system, and (2) check for +# inconvenient events that can't happen except with negligible probability (e.g. +# output of a hash function is 0) and can't be manually triggered by any +# signer. + + +# This exception is raised if a party (signer or nonce coordinator) sends invalid +# values. Actual implementations should not crash when receiving invalid +# contributions. Instead, they should hold the offending party accountable. +class InvalidContributionError(Exception): + def __init__(self, signer_id: Optional[int], contrib: ContribKind) -> None: + # participant identifier of the signer who sent the invalid value + self.id = signer_id + # contrib is one of "pubkey", "pubnonce", "aggnonce", or "psig". + self.contrib = contrib + + +def derive_interpolating_value(ids: List[int], my_id: int) -> Scalar: + assert my_id in ids + assert 0 <= my_id < 2**32 + num = Scalar(1) + deno = Scalar(1) + for curr_id in ids: + if curr_id == my_id: + continue + num *= Scalar(curr_id + 1) + deno *= Scalar(curr_id - my_id) + return num / deno + + +def derive_thresh_pubkey(ids: List[int], pubshares: List[PlainPk]) -> PlainPk: + Q = GE() + for my_id, pubshare in zip(ids, pubshares): + try: + X_i = GE.from_bytes_compressed(pubshare) + except ValueError: + raise InvalidContributionError(my_id, "pubshare") + lam_i = derive_interpolating_value(ids, my_id) + Q = Q + lam_i * X_i + # Q is not the point at infinity except with negligible probability. + assert not Q.infinity + return PlainPk(Q.to_bytes_compressed()) + + +# REVIEW: should we remove n and t from this struct? +class SignersContext(NamedTuple): + n: int + t: int + ids: List[int] + pubshares: List[PlainPk] + thresh_pk: PlainPk + + +def validate_signers_ctx(signers_ctx: SignersContext) -> None: + n, t, ids, pubshares, thresh_pk = signers_ctx + assert t <= n + if not t <= len(ids) <= n: + raise ValueError("The number of signers must be between t and n.") + if len(pubshares) != len(ids): + raise ValueError("The pubshares and ids arrays must have the same length.") + for i, pubshare in zip(ids, pubshares): + if not 0 <= i <= n - 1: + raise ValueError(f"The participant identifier {i} is out of range.") + try: + _ = GE.from_bytes_compressed(pubshare) + except ValueError: + raise InvalidContributionError(i, "pubshare") + if len(set(ids)) != len(ids): + raise ValueError( + "The participant identifier list must contain unique elements." + ) + if derive_thresh_pubkey(ids, pubshares) != thresh_pk: + raise ValueError("The provided key material is incorrect.") + + +class TweakContext(NamedTuple): + Q: GE + gacc: Scalar + tacc: Scalar + + +def get_xonly_pk(tweak_ctx: TweakContext) -> XonlyPk: + Q, _, _ = tweak_ctx + return XonlyPk(Q.to_bytes_xonly()) + + +def get_plain_pk(tweak_ctx: TweakContext) -> PlainPk: + Q, _, _ = tweak_ctx + return PlainPk(Q.to_bytes_compressed()) + + +def tweak_ctx_init(thresh_pk: PlainPk) -> TweakContext: + Q = GE.from_bytes_compressed(thresh_pk) + gacc = Scalar(1) + tacc = Scalar(0) + return TweakContext(Q, gacc, tacc) + + +def apply_tweak(tweak_ctx: TweakContext, tweak: bytes, is_xonly: bool) -> TweakContext: + if len(tweak) != 32: + raise ValueError("The tweak must be a 32-byte array.") + Q, gacc, tacc = tweak_ctx + if is_xonly and not Q.has_even_y(): + g = Scalar(-1) + else: + g = Scalar(1) + try: + twk = Scalar.from_bytes_checked(tweak) + except ValueError: + raise ValueError("The tweak must be less than n.") + Q_ = g * Q + twk * G + if Q_.infinity: + raise ValueError("The result of tweaking cannot be infinity.") + gacc_ = g * gacc + tacc_ = twk + g * tacc + return TweakContext(Q_, gacc_, tacc_) + + +def nonce_hash( + rand: bytes, + pubshare: PlainPk, + thresh_pk: XonlyPk, + i: int, + msg_prefixed: bytes, + extra_in: bytes, +) -> bytes: + buf = b"" + buf += rand + buf += len(pubshare).to_bytes(1, "big") + buf += pubshare + buf += len(thresh_pk).to_bytes(1, "big") + buf += thresh_pk + buf += msg_prefixed + buf += len(extra_in).to_bytes(4, "big") + buf += extra_in + buf += i.to_bytes(1, "big") + return tagged_hash("FROST/nonce", buf) + + +def nonce_gen_internal( + rand_: bytes, + secshare: Optional[bytes], + pubshare: Optional[PlainPk], + thresh_pk: Optional[XonlyPk], + msg: Optional[bytes], + extra_in: Optional[bytes], +) -> Tuple[bytearray, bytes]: + if secshare is not None: + rand = xor_bytes(secshare, tagged_hash("FROST/aux", rand_)) + else: + rand = rand_ + if pubshare is None: + pubshare = PlainPk(b"") + if thresh_pk is None: + thresh_pk = XonlyPk(b"") + if msg is None: + msg_prefixed = b"\x00" + else: + msg_prefixed = b"\x01" + msg_prefixed += len(msg).to_bytes(8, "big") + msg_prefixed += msg + if extra_in is None: + extra_in = b"" + k_1 = Scalar.from_bytes_wrapping( + nonce_hash(rand, pubshare, thresh_pk, 0, msg_prefixed, extra_in) + ) + k_2 = Scalar.from_bytes_wrapping( + nonce_hash(rand, pubshare, thresh_pk, 1, msg_prefixed, extra_in) + ) + # k_1 == 0 or k_2 == 0 cannot occur except with negligible probability. + assert k_1 != 0 + assert k_2 != 0 + R1_partial = k_1 * G + R2_partial = k_2 * G + assert not R1_partial.infinity + assert not R2_partial.infinity + pubnonce = R1_partial.to_bytes_compressed() + R2_partial.to_bytes_compressed() + # use mutable `bytearray` since secnonce need to be replaced with zeros during signing. + secnonce = bytearray(k_1.to_bytes() + k_2.to_bytes()) + return secnonce, pubnonce + + +# think: can msg & extra_in be of any length here? +# think: why doesn't musig2 ref code check for `pk` length here? +# REVIEW: Why should thresh_pk be XOnlyPk here? Shouldn't it be PlainPk? +def nonce_gen( + secshare: Optional[bytes], + pubshare: Optional[PlainPk], + thresh_pk: Optional[XonlyPk], + msg: Optional[bytes], + extra_in: Optional[bytes], +) -> Tuple[bytearray, bytes]: + if secshare is not None and len(secshare) != 32: + raise ValueError("The optional byte array secshare must have length 32.") + if pubshare is not None and len(pubshare) != 33: + raise ValueError("The optional byte array pubshare must have length 33.") + if thresh_pk is not None and len(thresh_pk) != 32: + raise ValueError("The optional byte array thresh_pk must have length 32.") + # bench: will adding individual_pk(secshare) == pubshare check, increase the execution time significantly? + rand_ = secrets.token_bytes(32) + return nonce_gen_internal(rand_, secshare, pubshare, thresh_pk, msg, extra_in) + + +# REVIEW should we raise value errors for: +# (1) duplicate ids +# (2) 0 <= id < max_participants < 2^32 +# in each function that takes `ids` as argument? + + +# `ids` is typed as Sequence[Optional[int]] so that callers can pass either +# List[int] or List[Optional[int]] without triggering mypy invariance errors. +# Sequence is read-only and covariant. +def nonce_agg(pubnonces: List[bytes], ids: Sequence[Optional[int]]) -> bytes: + if len(pubnonces) != len(ids): + raise ValueError("The pubnonces and ids arrays must have the same length.") + aggnonce = b"" + for j in (1, 2): + R_j = GE() + for my_id, pubnonce in zip(ids, pubnonces): + try: + R_ij = GE.from_bytes_compressed(pubnonce[(j - 1) * 33 : j * 33]) + except ValueError: + raise InvalidContributionError(my_id, "pubnonce") + R_j = R_j + R_ij + aggnonce += R_j.to_bytes_compressed_with_infinity() + return aggnonce + + +class SessionContext(NamedTuple): + aggnonce: bytes + signers_ctx: SignersContext + tweaks: List[bytes] + is_xonly: List[bool] + msg: bytes + + +def thresh_pubkey_and_tweak( + thresh_pk: PlainPk, tweaks: List[bytes], is_xonly: List[bool] +) -> TweakContext: + if len(tweaks) != len(is_xonly): + raise ValueError("The tweaks and is_xonly arrays must have the same length.") + tweak_ctx = tweak_ctx_init(thresh_pk) + v = len(tweaks) + for i in range(v): + tweak_ctx = apply_tweak(tweak_ctx, tweaks[i], is_xonly[i]) + return tweak_ctx + + +def get_session_values( + session_ctx: SessionContext, +) -> Tuple[GE, Scalar, Scalar, List[int], List[PlainPk], Scalar, GE, Scalar]: + (aggnonce, signers_ctx, tweaks, is_xonly, msg) = session_ctx + validate_signers_ctx(signers_ctx) + _, _, ids, pubshares, thresh_pk = signers_ctx + Q, gacc, tacc = thresh_pubkey_and_tweak(thresh_pk, tweaks, is_xonly) + # sort the ids before serializing because ROAST paper considers them as a set + ser_ids = serialize_ids(ids) + b = Scalar.from_bytes_wrapping( + tagged_hash("FROST/noncecoef", ser_ids + aggnonce + Q.to_bytes_xonly() + msg) + ) + assert b != 0 + try: + R1 = GE.from_bytes_compressed_with_infinity(aggnonce[0:33]) + R2 = GE.from_bytes_compressed_with_infinity(aggnonce[33:66]) + except ValueError: + # coordinator sent invalid aggnonce + raise InvalidContributionError(None, "aggnonce") + R_ = R1 + b * R2 + R = R_ if not R_.infinity else G + assert not R.infinity + e = Scalar.from_bytes_wrapping( + tagged_hash("BIP0340/challenge", R.to_bytes_xonly() + Q.to_bytes_xonly() + msg) + ) + assert e != 0 + return (Q, gacc, tacc, ids, pubshares, b, R, e) + + +def serialize_ids(ids: List[int]) -> bytes: + # REVIEW assert for ids not being unsigned values? + sorted_ids = sorted(ids) + ser_ids = b"".join(i.to_bytes(4, byteorder="big", signed=False) for i in sorted_ids) + return ser_ids + + +def sign( + secnonce: bytearray, secshare: bytes, my_id: int, session_ctx: SessionContext +) -> bytes: + (Q, gacc, _, ids, pubshares, b, R, e) = get_session_values(session_ctx) + try: + k_1_ = Scalar.from_bytes_nonzero_checked(bytes(secnonce[0:32])) + except ValueError: + raise ValueError("first secnonce value is out of range.") + try: + k_2_ = Scalar.from_bytes_nonzero_checked(bytes(secnonce[32:64])) + except ValueError: + raise ValueError("second secnonce value is out of range.") + # Overwrite the secnonce argument with zeros such that subsequent calls of + # sign with the same secnonce raise a ValueError. + secnonce[:] = bytearray(b"\x00" * 64) + k_1 = k_1_ if R.has_even_y() else -k_1_ + k_2 = k_2_ if R.has_even_y() else -k_2_ + d_ = int_from_bytes(secshare) + if not 0 < d_ < GE.ORDER: + raise ValueError("The signer's secret share value is out of range.") + P = d_ * G + assert not P.infinity + my_pubshare = P.to_bytes_compressed() + # REVIEW: do we actually need this check? Musig2 embeds pk in secnonce to prevent + # the wagner's attack related to tweaked pubkeys, but here we don't have that issue. + # If we don't need to worry about that attack, we remove pubshare from get_session_values + # return values + if my_pubshare not in pubshares: + raise ValueError( + "The signer's pubshare must be included in the list of pubshares." + ) + # REVIEW: do we actually need this check? + if my_id not in ids: + raise ValueError( + "The signer's id must be present in the participant identifier list." + ) + a = derive_interpolating_value(ids, my_id) + g = Scalar(1) if Q.has_even_y() else Scalar(-1) + d = g * gacc * d_ + s = k_1 + b * k_2 + e * a * d + psig = s.to_bytes() + R1_partial = k_1_ * G + R2_partial = k_2_ * G + assert not R1_partial.infinity + assert not R2_partial.infinity + pubnonce = R1_partial.to_bytes_compressed() + R2_partial.to_bytes_compressed() + # Optional correctness check. The result of signing should pass signature verification. + assert partial_sig_verify_internal(psig, my_id, pubnonce, my_pubshare, session_ctx) + return psig + + +# REVIEW should we hash the signer set (or pubshares) too? Otherwise same nonce will be generate even if the signer set changes +def det_nonce_hash( + secshare_: bytes, aggothernonce: bytes, tweaked_tpk: bytes, msg: bytes, i: int +) -> bytes: + buf = b"" + buf += secshare_ + buf += aggothernonce + buf += tweaked_tpk + buf += len(msg).to_bytes(8, "big") + buf += msg + buf += i.to_bytes(1, "big") + return tagged_hash("FROST/deterministic/nonce", buf) + + +COORDINATOR_ID = None + + +def deterministic_sign( + secshare: bytes, + my_id: int, + aggothernonce: bytes, + signers_ctx: SignersContext, + tweaks: List[bytes], + is_xonly: List[bool], + msg: bytes, + rand: Optional[bytes], +) -> Tuple[bytes, bytes]: + if rand is not None: + secshare_ = xor_bytes(secshare, tagged_hash("FROST/aux", rand)) + else: + secshare_ = secshare + # REVIEW: do we need to add any check for ids & pubshares (in signers_ctx context) here? + validate_signers_ctx(signers_ctx) + _, _, _, _, thresh_pk = signers_ctx + tweaked_tpk = get_xonly_pk(thresh_pubkey_and_tweak(thresh_pk, tweaks, is_xonly)) + + k_1 = Scalar.from_bytes_wrapping( + det_nonce_hash(secshare_, aggothernonce, tweaked_tpk, msg, 0) + ) + k_2 = Scalar.from_bytes_wrapping( + det_nonce_hash(secshare_, aggothernonce, tweaked_tpk, msg, 1) + ) + # k_1 == 0 or k_2 == 0 cannot occur except with negligible probability. + assert k_1 != 0 + assert k_2 != 0 + + R1_partial = k_1 * G + R2_partial = k_2 * G + assert not R1_partial.infinity + assert not R2_partial.infinity + pubnonce = R1_partial.to_bytes_compressed() + R2_partial.to_bytes_compressed() + secnonce = bytearray(k_1.to_bytes() + k_2.to_bytes()) + try: + aggnonce = nonce_agg([pubnonce, aggothernonce], [my_id, COORDINATOR_ID]) + except Exception: + # Since `pubnonce` can never be invalid, blame coordinator's pubnonce. + # REVIEW: should we introduce an unknown participant or coordinator error? + raise InvalidContributionError(COORDINATOR_ID, "aggothernonce") + session_ctx = SessionContext(aggnonce, signers_ctx, tweaks, is_xonly, msg) + psig = sign(secnonce, secshare, my_id, session_ctx) + return (pubnonce, psig) + + +def partial_sig_verify( + psig: bytes, + pubnonces: List[bytes], + signers_ctx: SignersContext, + tweaks: List[bytes], + is_xonly: List[bool], + msg: bytes, + i: int, +) -> bool: + validate_signers_ctx(signers_ctx) + _, _, ids, pubshares, _ = signers_ctx + if len(pubnonces) != len(ids): + raise ValueError("The pubnonces and ids arrays must have the same length.") + if len(tweaks) != len(is_xonly): + raise ValueError("The tweaks and is_xonly arrays must have the same length.") + aggnonce = nonce_agg(pubnonces, ids) + session_ctx = SessionContext(aggnonce, signers_ctx, tweaks, is_xonly, msg) + return partial_sig_verify_internal( + psig, ids[i], pubnonces[i], pubshares[i], session_ctx + ) + + +# REVIEW: catch `cpoint` ValueError and return false +def partial_sig_verify_internal( + psig: bytes, + my_id: int, + pubnonce: bytes, + pubshare: bytes, + session_ctx: SessionContext, +) -> bool: + (Q, gacc, _, ids, pubshares, b, R, e) = get_session_values(session_ctx) + try: + s = Scalar.from_bytes_nonzero_checked(psig) + except ValueError: + return False + if pubshare not in pubshares: + return False + if my_id not in ids: + return False + try: + R1_partial = GE.from_bytes_compressed(pubnonce[0:33]) + R2_partial = GE.from_bytes_compressed(pubnonce[33:66]) + except ValueError: + return False + Re_s_ = R1_partial + b * R2_partial + Re_s = Re_s_ if R.has_even_y() else -Re_s_ + try: + P = GE.from_bytes_compressed(pubshare) + except ValueError: + return False + a = derive_interpolating_value(ids, my_id) + g = Scalar(1) if Q.has_even_y() else Scalar(-1) + g_ = g * gacc + return s * G == Re_s + (e * a * g_) * P + + +def partial_sig_agg( + psigs: List[bytes], ids: List[int], session_ctx: SessionContext +) -> bytes: + assert COORDINATOR_ID not in ids + if len(psigs) != len(ids): + raise ValueError("The psigs and ids arrays must have the same length.") + (Q, _, tacc, _, _, _, R, e) = get_session_values(session_ctx) + s = Scalar(0) + for my_id, psig in zip(ids, psigs): + try: + s_i = Scalar.from_bytes_checked(psig) + except ValueError: + raise InvalidContributionError(my_id, "psig") + s = s + s_i + g = Scalar(1) if Q.has_even_y() else Scalar(-1) + s = s + e * g * tacc + return R.to_bytes_xonly() + s.to_bytes() diff --git a/bip-frost-signing/python/gen_vectors.py b/bip-frost-signing/python/gen_vectors.py new file mode 100755 index 0000000000..912e11e9c1 --- /dev/null +++ b/bip-frost-signing/python/gen_vectors.py @@ -0,0 +1,1466 @@ +#!/usr/bin/env python3 + +import json +import os +import shutil +import sys +from typing import Dict, List, Sequence, Union +import secrets +import pprint + +from frost_ref import ( + InvalidContributionError, + SessionContext, + SignersContext, + deterministic_sign, + nonce_agg, + partial_sig_agg, + partial_sig_verify, + sign, +) +from frost_ref.signing import nonce_gen_internal +from secp256k1lab.secp256k1 import GE, Scalar +from secp256k1lab.keys import pubkey_gen_plain +from trusted_dealer import trusted_dealer_keygen + + +def bytes_to_hex(data: bytes) -> str: + return data.hex().upper() + + +def bytes_list_to_hex(lst: Sequence[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 int_list_to_bytes(lst: List[int]) -> List[bytes]: + return [Scalar(x).to_bytes() for x in lst] + + +ErrorInfo = Dict[str, Union[int, str, None, "ErrorInfo"]] + + +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, type(None))): + error_info[key] = value + elif isinstance(value, bytes): + error_info[key] = bytes_to_hex(value) + else: + raise NotImplementedError( + f"Conversion for type {type(value).__name__} is not implemented" + ) + + # If the last argument is not found in the instance’s attributes and + # is a string, treat it as an extra message. + if e.args and isinstance(e.args[-1], str) and e.args[-1] not in e.__dict__.values(): + error_info.setdefault("message", e.args[-1]) + 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") + + +COMMON_RAND = bytes.fromhex( + "0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F" +) + +COMMON_MSGS = [ + bytes.fromhex( + "F95466D086770E689964664219266FE5ED215C92AE20BAB5C9D79ADDDDF3C0CF" + ), # 32-byte message + bytes.fromhex(""), # Empty message + bytes.fromhex( + "2626262626262626262626262626262626262626262626262626262626262626262626262626" + ), # 38-byte message +] + +COMMON_TWEAKS = hex_list_to_bytes( + [ + "E8F791FF9225A2AF0102AFFF4A9A723D9612A682A25EBE79802B263CDFCD83BB", + "AE2EA797CC0FE72AC5B97B97F3C6957D7E4199A167A58EB08BCAFFDA70AC0455", + "F52ECBC565B3D8BEA2DFD5B75A4F457E54369809322E4120831626F290FA87E0", + "1969AD73CC177FA0B4FCED6DF1F7BF9907E665FDE9BA196A74FED0A3CF5AEF9D", + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", # Invalid (exceeds group size) + ] +) + +SIG_AGG_TWEAKS = hex_list_to_bytes( + [ + "B511DA492182A91B0FFB9A98020D55F260AE86D7ECBD0399C7383D59A5F2AF7C", + "A815FE049EE3C5AAB66310477FBC8BCCCAC2F3395F59F921C364ACD78A2F48DC", + "75448A87274B056468B977BE06EB1E9F657577B7320B0A3376EA51FD420D18A8", + ] +) + +INVALID_PUBSHARE = bytes.fromhex( + "020000000000000000000000000000000000000000000000000000000000000007" +) + + +def write_test_vectors(filename, vectors): + output_file = os.path.join("vectors", filename) + with open(output_file, "w") as f: + json.dump(vectors, f, indent=4) + + +def get_common_setup(): + t, n, thresh_pk_ge, secshares, pubshares = frost_keygen_fixed() + return ( + n, + t, + thresh_pk_ge.to_bytes_compressed(), + thresh_pk_ge.to_bytes_xonly(), + list(range(n)), + secshares, + pubshares, + ) + + +def generate_all_nonces(rand, secshares, pubshares, xonly_thresh_pk, msg=None): + secnonces = [] + pubnonces = [] + for i in range(len(secshares)): + sec, pub = nonce_gen_internal( + rand, secshares[i], pubshares[i], xonly_thresh_pk, msg, None + ) + secnonces.append(sec) + pubnonces.append(pub) + return secnonces, pubnonces + + +def frost_keygen_fixed(): + n = 3 + t = 2 + thresh_pubkey_bytes = bytes.fromhex( + "03B02645D79ABFC494338139410F9D7F0A72BE86C952D6BDE1A66447B8A8D69237" + ) + thresh_pubkey_ge = GE.from_bytes_compressed(thresh_pubkey_bytes) + secshares = hex_list_to_bytes( + [ + "CCD2EF4559DB05635091D80189AB3544D6668EFC0500A8D5FF51A1F4D32CC1F1", + "62A04F63F105A40FCF25634AA645D77AAC692641916E4DFC8C1EEC83CAB5BEBA", + "F86DAF82883042BC4DB8EE93C2E079AF3D1A9A6DCD24935ED8BE959F9274FCC4", + ] + ) + pubshares = hex_list_to_bytes( + [ + "022B02109FBCFB4DA3F53C7393B22E72A2A51C4AFBF0C01AAF44F73843CFB4B74B", + "02EC6444271D791A1DA95300329DB2268611B9C60E193DABFDEE0AA816AE512583", + "03113F810F612567D9552F46AF9BDA21A67D52060F95BD4A723F4B60B1820D3676", + ] + ) + return (t, n, thresh_pubkey_ge, secshares, pubshares) + + +# NOTE: This function is used only once to generate a long-term key for frost_keygen_fixed(). It is intentionally not called anywhere else. It will be used in case we decide to change the long-term key, in future. +def frost_keygen_random(): + random_scalar = Scalar.from_bytes_nonzero_checked(secrets.token_bytes(32)) + threshold_seckey = random_scalar.to_bytes() + threshold_pubkey = pubkey_gen_plain(threshold_seckey) + output_tpk, secshares, pubshares = trusted_dealer_keygen(random_scalar, 3, 2) + assert threshold_pubkey == output_tpk + + print(f"threshold secret key: {threshold_seckey.hex().upper()}") + print(f"threshold public key: {threshold_pubkey.hex().upper()}") + print("secret shares:") + pprint.pprint(bytes_list_to_hex(secshares)) + print("public shares:") + pprint.pprint(bytes_list_to_hex(pubshares)) + + +def generate_nonce_gen_vectors(): + vectors = {"test_cases": []} + + _, _, thresh_pk_ge, secshares, pubshares = frost_keygen_fixed() + extra_in = bytes.fromhex( + "0808080808080808080808080808080808080808080808080808080808080808" + ) + xonly_thresh_pk = thresh_pk_ge.to_bytes_xonly() + + # --- Valid Test Case 1 --- + msg = bytes.fromhex( + "0101010101010101010101010101010101010101010101010101010101010101" + ) + secnonce, pubnonce = nonce_gen_internal( + COMMON_RAND, secshares[0], pubshares[0], xonly_thresh_pk, msg, extra_in + ) + vectors["test_cases"].append( + { + "rand_": bytes_to_hex(COMMON_RAND), + "secshare": bytes_to_hex(secshares[0]), + "pubshare": bytes_to_hex(pubshares[0]), + "threshold_pubkey": bytes_to_hex(xonly_thresh_pk), + "msg": bytes_to_hex(msg), + "extra_in": bytes_to_hex(extra_in), + "expected_secnonce": bytes_to_hex(secnonce), + "expected_pubnonce": bytes_to_hex(pubnonce), + "comment": "", + } + ) + # --- Valid Test Case 2 --- + secnonce, pubnonce = nonce_gen_internal( + COMMON_RAND, + secshares[0], + pubshares[0], + xonly_thresh_pk, + COMMON_MSGS[1], + extra_in, + ) + vectors["test_cases"].append( + { + "rand_": bytes_to_hex(COMMON_RAND), + "secshare": bytes_to_hex(secshares[0]), + "pubshare": bytes_to_hex(pubshares[0]), + "threshold_pubkey": bytes_to_hex(xonly_thresh_pk), + "msg": bytes_to_hex(COMMON_MSGS[1]), + "extra_in": bytes_to_hex(extra_in), + "expected_secnonce": bytes_to_hex(secnonce), + "expected_pubnonce": bytes_to_hex(pubnonce), + "comment": "Empty Message", + } + ) + # --- Valid Test Case 3 --- + secnonce, pubnonce = nonce_gen_internal( + COMMON_RAND, + secshares[0], + pubshares[0], + xonly_thresh_pk, + COMMON_MSGS[2], + extra_in, + ) + vectors["test_cases"].append( + { + "rand_": bytes_to_hex(COMMON_RAND), + "secshare": bytes_to_hex(secshares[0]), + "pubshare": bytes_to_hex(pubshares[0]), + "threshold_pubkey": bytes_to_hex(xonly_thresh_pk), + "msg": bytes_to_hex(COMMON_MSGS[2]), + "extra_in": bytes_to_hex(extra_in), + "expected_secnonce": bytes_to_hex(secnonce), + "expected_pubnonce": bytes_to_hex(pubnonce), + "comment": "38-byte message", + } + ) + # --- Valid Test Case 4 --- + secnonce, pubnonce = nonce_gen_internal(COMMON_RAND, None, None, None, None, None) + vectors["test_cases"].append( + { + "rand_": bytes_to_hex(COMMON_RAND), + "secshare": None, + "pubshare": None, + "threshold_pubkey": None, + "msg": None, + "extra_in": None, + "expected_secnonce": bytes_to_hex(secnonce), + "expected_pubnonce": bytes_to_hex(pubnonce), + "comment": "Every optional parameter is absent", + } + ) + + write_test_vectors("nonce_gen_vectors.json", vectors) + + +# REVIEW: we can simply use the pubnonces directly in the valid & error +# test cases, instead of referencing their indices +def generate_nonce_agg_vectors(): + vectors = dict() + + # Special pubnonce indices for test cases + INVALID_TAG_IDX = 4 # Pubnonce with wrong tag 0x04 + INVALID_XCOORD_IDX = 5 # Pubnonce with invalid X coordinate + INVALID_EXCEEDS_FIELD_IDX = 6 # Pubnonce X exceeds field size + + pubnonces = hex_list_to_bytes( + [ + "020151C80F435648DF67A22B749CD798CE54E0321D034B92B709B567D60A42E66603BA47FBC1834437B3212E89A84D8425E7BF12E0245D98262268EBDCB385D50641", + "03FF406FFD8ADB9CD29877E4985014F66A59F6CD01C0E88CAA8E5F3166B1F676A60248C264CDD57D3C24D79990B0F865674EB62A0F9018277A95011B41BFC193B833", + "020151C80F435648DF67A22B749CD798CE54E0321D034B92B709B567D60A42E6660279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798", + "03FF406FFD8ADB9CD29877E4985014F66A59F6CD01C0E88CAA8E5F3166B1F676A60379BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798", + "04FF406FFD8ADB9CD29877E4985014F66A59F6CD01C0E88CAA8E5F3166B1F676A60248C264CDD57D3C24D79990B0F865674EB62A0F9018277A95011B41BFC193B833", + "03FF406FFD8ADB9CD29877E4985014F66A59F6CD01C0E88CAA8E5F3166B1F676A60248C264CDD57D3C24D79990B0F865674EB62A0F9018277A95011B41BFC193B831", + "03FF406FFD8ADB9CD29877E4985014F66A59F6CD01C0E88CAA8E5F3166B1F676A602FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC30", + ] + ) + vectors["pubnonces"] = bytes_list_to_hex(pubnonces) + + vectors["valid_test_cases"] = [] + # --- Valid Test Case 1 --- + pubnonce_indices = [0, 1] + curr_pubnonces = [pubnonces[i] for i in pubnonce_indices] + pids = [0, 1] + aggnonce = nonce_agg(curr_pubnonces, pids) + vectors["valid_test_cases"].append( + { + "pubnonce_indices": pubnonce_indices, + "participant_identifiers": pids, + "expected_aggnonce": bytes_to_hex(aggnonce), + } + ) + # --- Valid Test Case 2 --- + pubnonce_indices = [2, 3] + curr_pubnonces = [pubnonces[i] for i in pubnonce_indices] + pids = [0, 1] + aggnonce = nonce_agg(curr_pubnonces, pids) + vectors["valid_test_cases"].append( + { + "pubnonce_indices": pubnonce_indices, + "participant_identifiers": pids, + "expected_aggnonce": bytes_to_hex(aggnonce), + "comment": "Sum of second points encoded in the nonces is point at infinity which is serialized as 33 zero bytes", + } + ) + + vectors["error_test_cases"] = [] + # --- Error Test Case 1 --- + pubnonce_indices = [0, INVALID_TAG_IDX] + curr_pubnonces = [pubnonces[i] for i in pubnonce_indices] + pids = [0, 1] + error = expect_exception( + lambda: nonce_agg(curr_pubnonces, pids), InvalidContributionError + ) + vectors["error_test_cases"].append( + { + "pubnonce_indices": pubnonce_indices, + "participant_identifiers": pids, + "error": error, + "comment": "Public nonce from signer 1 is invalid due wrong tag, 0x04, in the first half", + } + ) + # --- Error Test Case 2 --- + pubnonce_indices = [INVALID_XCOORD_IDX, 1] + curr_pubnonces = [pubnonces[i] for i in pubnonce_indices] + pids = [0, 1] + error = expect_exception( + lambda: nonce_agg(curr_pubnonces, pids), InvalidContributionError + ) + vectors["error_test_cases"].append( + { + "pubnonce_indices": pubnonce_indices, + "participant_identifiers": pids, + "error": error, + "comment": "Public nonce from signer 0 is invalid because the second half does not correspond to an X coordinate", + } + ) + # --- Error Test Case 3 --- + pubnonce_indices = [INVALID_EXCEEDS_FIELD_IDX, 1] + curr_pubnonces = [pubnonces[i] for i in pubnonce_indices] + pids = [0, 1] + error = expect_exception( + lambda: nonce_agg(curr_pubnonces, pids), InvalidContributionError + ) + vectors["error_test_cases"].append( + { + "pubnonce_indices": pubnonce_indices, + "participant_identifiers": pids, + "error": error, + "comment": "Public nonce from signer 0 is invalid because second half exceeds field size", + } + ) + + write_test_vectors("nonce_agg_vectors.json", vectors) + + +# TODO: Remove `pubnonces` param from these vectors. It's not used. +def generate_sign_verify_vectors(): + vectors = dict() + + n, t, thresh_pk, xonly_thresh_pk, ids, secshares, pubshares = get_common_setup() + secshare_p0 = secshares[0] + + # Special indices for test cases + INVALID_PUBSHARE_IDX = 3 # Invalid pubshare (appended to list) + INV_PUBNONCE_IDX = 4 # Inverse pubnonce (for infinity test) + SECNONCE_ZERO_IDX = 1 # All-zero secnonce (nonce reuse) + AGGNONCE_INF_IDX = 3 # Aggnonce with both halves as infinity + AGGNONCE_INVALID_TAG_IDX = 4 # Invalid tag 0x04 + AGGNONCE_INVALID_XCOORD_IDX = 5 # Invalid X coordinate + AGGNONCE_INVALID_EXCEEDS_FIELD_IDX = 6 # X exceeds field size + + vectors["n"] = n + vectors["t"] = t + vectors["threshold_pubkey"] = bytes_to_hex(thresh_pk) + vectors["secshare_p0"] = bytes_to_hex(secshare_p0) + vectors["identifiers"] = ids + pubshares.append(INVALID_PUBSHARE) + vectors["pubshares"] = bytes_list_to_hex(pubshares) + + secnonces, pubnonces = generate_all_nonces( + COMMON_RAND, secshares, pubshares, xonly_thresh_pk + ) + secnonces_p0 = [ + secnonces[0], + bytes.fromhex( + "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + ), # all zero + ] + vectors["secnonces_p0"] = bytes_list_to_hex(secnonces_p0) + # compute -(pubnonce[0] + pubnonce[1]) + tmp = nonce_agg(pubnonces[:2], ids[:2]) + R1 = GE.from_bytes_compressed_with_infinity(tmp[0:33]) + R2 = GE.from_bytes_compressed_with_infinity(tmp[33:66]) + neg_R1 = -R1 + neg_R2 = -R2 + inv_pubnonce = ( + neg_R1.to_bytes_compressed_with_infinity() + + neg_R2.to_bytes_compressed_with_infinity() + ) + invalid_pubnonce = bytes.fromhex( + "0200000000000000000000000000000000000000000000000000000000000000090287BF891D2A6DEAEBADC909352AA9405D1428C15F4B75F04DAE642A95C2548480" + ) + pubnonces += [invalid_pubnonce, inv_pubnonce] + vectors["pubnonces"] = bytes_list_to_hex(pubnonces) + + # aggnonces indices represent the following + # 0 - 2 -> valid aggnonces for the three indices group below + # 3 -> valid aggnonce with both halves as inf points + # 4 -> wrong parity tag + # 5 -> invalid x coordinate in second half + # 6 -> second half exceeds field size + indices_grp = [[0, 1], [0, 2], [0, 1, 2]] + aggnonces = [ + nonce_agg([pubnonces[i] for i in indices], [ids[i] for i in indices]) + for indices in indices_grp + ] + # aggnonce with inf points + aggnonces.append( + nonce_agg( + [ + pubnonces[0], + pubnonces[1], + pubnonces[-1], + ], # pubnonces[-1] is inv_pubnonce + [ids[0], ids[1], ids[2]], + ) + ) + # invalid aggnonces + aggnonces += [ + bytes.fromhex( + "048465FCF0BBDBCF443AABCCE533D42B4B5A10966AC09A49655E8C42DAAB8FCD61037496A3CC86926D452CAFCFD55D25972CA1675D549310DE296BFF42F72EEEA8C9" + ), # wrong parity tag 04 + bytes.fromhex( + "028465FCF0BBDBCF443AABCCE533D42B4B5A10966AC09A49655E8C42DAAB8FCD61020000000000000000000000000000000000000000000000000000000000000009" + ), # invalid x coordinate in second half + bytes.fromhex( + "028465FCF0BBDBCF443AABCCE533D42B4B5A10966AC09A49655E8C42DAAB8FCD6102FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC30" + ), # second half exceeds field size + ] + vectors["aggnonces"] = bytes_list_to_hex(aggnonces) + + vectors["msgs"] = bytes_list_to_hex(COMMON_MSGS) + + vectors["valid_test_cases"] = [] + # --- Valid Test Cases --- + # Every List[int] & int below represents indices + # REVIEW: add secnonce here (easy readability), than using `secnonce_p0` list as common prefix + valid_cases = [ + { + "ids": [0, 1], + "pubshares": [0, 1], + "pubnonces": [0, 1], + "aggnonce": 0, + "msg": 0, + "signer": 0, + "comment": "Signing with minimum number of participants", + }, + { + "ids": [1, 0], + "pubshares": [1, 0], + "pubnonces": [1, 0], + "aggnonce": 0, + "msg": 0, + "signer": 1, + "comment": "Partial-signature doesn't change if the order of signers set changes (without changing secnonces)", + }, + { + "ids": [0, 2], + "pubshares": [0, 2], + "pubnonces": [0, 2], + "aggnonce": 1, + "msg": 0, + "signer": 0, + "comment": "Partial-signature changes if the members of signers set changes", + }, + { + "ids": [0, 1, 2], + "pubshares": [0, 1, 2], + "pubnonces": [0, 1, 2], + "aggnonce": 2, + "msg": 0, + "signer": 0, + "comment": "Signing with max number of participants", + }, + { + "ids": [0, 1, 2], + "pubshares": [0, 1, 2], + "pubnonces": [0, 1, INV_PUBNONCE_IDX], + "aggnonce": AGGNONCE_INF_IDX, + "msg": 0, + "signer": 0, + "comment": "Both halves of aggregate nonce correspond to point at infinity", + }, + { + "ids": [0, 1], + "pubshares": [0, 1], + "pubnonces": [0, 1], + "aggnonce": 0, + "msg": 1, + "signer": 0, + "comment": "Empty message", + }, + { + "ids": [0, 1], + "pubshares": [0, 1], + "pubnonces": [0, 1], + "aggnonce": 0, + "msg": 2, + "signer": 0, + "comment": "Message longer than 32 bytes (38-byte msg)", + }, + ] + for case in valid_cases: + curr_ids = [ids[i] for i in case["ids"]] + curr_pubshares = [pubshares[i] for i in case["pubshares"]] + curr_pubnonces = [pubnonces[i] for i in case["pubnonces"]] + curr_aggnonce = aggnonces[case["aggnonce"]] + curr_msg = COMMON_MSGS[case["msg"]] + my_id = curr_ids[case["signer"]] + curr_signers = SignersContext(n, t, curr_ids, curr_pubshares, thresh_pk) + session_ctx = SessionContext(curr_aggnonce, curr_signers, [], [], curr_msg) + expected_psig = sign( + bytearray(secnonces_p0[0]), secshare_p0, my_id, session_ctx + ) + vectors["valid_test_cases"].append( + { + "id_indices": case["ids"], + "pubshare_indices": case["pubshares"], + "pubnonce_indices": case["pubnonces"], + "aggnonce_index": case["aggnonce"], + "msg_index": case["msg"], + "signer_index": case["signer"], + "expected": bytes_to_hex(expected_psig), + "comment": case["comment"], + } + ) + # TODO: verify the signatures here + + vectors["sign_error_test_cases"] = [] + # --- Sign Error Test Cases --- + error_cases = [ + { + "ids": [2, 1], + "pubshares": [0, 1], + "aggnonce": 0, + "msg": 0, + "signer_idx": None, + "signer_id": 0, + "secnonce": 0, + "error": "value", + "comment": "The signer's id is not in the participant identifier list", + }, + { + "ids": [0, 1, 1], + "pubshares": [0, 1, 1], + "aggnonce": 0, + "msg": 0, + "signer_idx": 0, + "secnonce": 0, + "error": "value", + "comment": "The participant identifier list contains duplicate elements", + }, + { + "ids": [0, 1], + "pubshares": [2, 1], + "aggnonce": 0, + "msg": 0, + "signer_idx": 0, + "secnonce": 0, + "error": "value", + "comment": "The signer's pubshare is not in the list of pubshares. This test case is optional: it can be skipped by implementations that do not check that the signer's pubshare is included in the list of pubshares.", + }, + { + "ids": [0, 1, 2], + "pubshares": [0, 1], + "aggnonce": 0, + "msg": 0, + "signer_idx": 0, + "secnonce": 0, + "error": "value", + "comment": "The participant identifiers count exceed the participant public shares count", + }, + { + "ids": [0, 1], + "pubshares": [0, INVALID_PUBSHARE_IDX], + "aggnonce": 0, + "msg": 0, + "signer_idx": 0, + "secnonce": 0, + "error": "invalid_contrib", + "comment": "Signer 1 provided an invalid participant public share", + }, + { + "ids": [0, 1], + "pubshares": [0, 1], + "aggnonce": AGGNONCE_INVALID_TAG_IDX, + "msg": 0, + "signer_idx": 0, + "secnonce": 0, + "error": "invalid_contrib", + "comment": "Aggregate nonce is invalid due wrong tag, 0x04, in the first half", + }, + { + "ids": [0, 1], + "pubshares": [0, 1], + "aggnonce": AGGNONCE_INVALID_XCOORD_IDX, + "msg": 0, + "signer_idx": 0, + "secnonce": 0, + "error": "invalid_contrib", + "comment": "Aggregate nonce is invalid because the second half does not correspond to an X coordinate", + }, + { + "ids": [0, 1], + "pubshares": [0, 1], + "aggnonce": AGGNONCE_INVALID_EXCEEDS_FIELD_IDX, + "msg": 0, + "signer_idx": 0, + "secnonce": 0, + "error": "invalid_contrib", + "comment": "Aggregate nonce is invalid because second half exceeds field size", + }, + { + "ids": [0, 1], + "pubshares": [0, 1], + "aggnonce": 0, + "msg": 0, + "signer_idx": 0, + "secnonce": SECNONCE_ZERO_IDX, + "error": "value", + "comment": "Secnonce is invalid which may indicate nonce reuse", + }, + ] + for case in error_cases: + curr_ids = [ids[i] for i in case["ids"]] + curr_pubshares = [pubshares[i] for i in case["pubshares"]] + curr_aggnonce = aggnonces[case["aggnonce"]] + curr_msg = COMMON_MSGS[case["msg"]] + if case["signer_idx"] is None: + my_id = case["signer_id"] + else: + my_id = curr_ids[case["signer_idx"]] + curr_signers = SignersContext(n, t, curr_ids, curr_pubshares, thresh_pk) + session_ctx = SessionContext(curr_aggnonce, curr_signers, [], [], curr_msg) + curr_secnonce = bytearray(secnonces_p0[case["secnonce"]]) + expected_error = ( + ValueError if case["error"] == "value" else InvalidContributionError + ) + error = expect_exception( + lambda: sign(curr_secnonce, secshare_p0, my_id, session_ctx), expected_error + ) + vectors["sign_error_test_cases"].append( + { + "id_indices": case["ids"], + "pubshare_indices": case["pubshares"], + "aggnonce_index": case["aggnonce"], + "msg_index": case["msg"], + "signer_index": case["signer_idx"], + **( + {"signer_id": case["signer_id"]} + if case["signer_idx"] is None + else {} + ), + "secnonce_index": case["secnonce"], + "error": error, + "comment": case["comment"], + } + ) + + # REVIEW: In the following vectors, pubshare_indices are not required, + # just aggnonce value would do. But we should include `secshare` and + # `secnonce` indices tho. + vectors["verify_fail_test_cases"] = [] + # --- Verify Fail Test Cases --- + id_indices = [0, 1] + pubshare_indices = [0, 1] + pubnonce_indices = [0, 1] + aggnonce_idx = 0 + msg_idx = 0 + signer_idx = 0 + + curr_ids = [ids[i] for i in id_indices] + curr_pubshares = [pubshares[i] for i in pubshare_indices] + curr_aggnonce = aggnonces[aggnonce_idx] + curr_msg = COMMON_MSGS[msg_idx] + my_id = curr_ids[signer_idx] + curr_signers = SignersContext(n, t, curr_ids, curr_pubshares, thresh_pk) + session_ctx = SessionContext(curr_aggnonce, curr_signers, [], [], curr_msg) + curr_secnonce = bytearray(secnonces_p0[0]) + psig = sign(curr_secnonce, secshare_p0, my_id, session_ctx) + # --- Verify Fail Test Cases 1 --- + psig_scalar = Scalar.from_bytes_checked(psig) + neg_psig = (-psig_scalar).to_bytes() + vectors["verify_fail_test_cases"].append( + { + "psig": bytes_to_hex(neg_psig), + "id_indices": id_indices, + "pubshare_indices": pubshare_indices, + "pubnonce_indices": pubnonce_indices, + "msg_index": msg_idx, + "signer_index": signer_idx, + "comment": "Wrong signature (which is equal to the negation of valid signature)", + } + ) + # --- Verify Fail Test Cases 2 --- + vectors["verify_fail_test_cases"].append( + { + "psig": bytes_to_hex(psig), + "id_indices": id_indices, + "pubshare_indices": pubshare_indices, + "pubnonce_indices": pubnonce_indices, + "msg_index": msg_idx, + "signer_index": signer_idx + 1, + "comment": "Wrong signer index", + } + ) + # --- Verify Fail Test Cases 3 --- + vectors["verify_fail_test_cases"].append( + { + "psig": bytes_to_hex(psig), + "id_indices": id_indices, + "pubshare_indices": [2] + pubshare_indices[1:], + "pubnonce_indices": pubnonce_indices, + "msg_index": msg_idx, + "signer_index": signer_idx, + "comment": "The signer's pubshare is not in the list of pubshares", + } + ) + # --- Verify Fail Test Cases 4 --- + vectors["verify_fail_test_cases"].append( + { + "psig": "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", + "id_indices": id_indices, + "pubshare_indices": pubshare_indices, + "pubnonce_indices": pubnonce_indices, + "msg_index": msg_idx, + "signer_index": signer_idx, + "comment": "Signature value is out of range", + } + ) + + vectors["verify_error_test_cases"] = [] + # --- Verify Error Test Cases --- + verify_error_cases = [ + { + "ids": [0, 1], + "pubshares": [0, 1], + "pubnonces": [3, 1], + "msg": 0, + "signer": 0, + "error": "invalid_contrib", + "comment": "Invalid pubnonce", + }, + { + "ids": [0, 1], + "pubshares": [INVALID_PUBSHARE_IDX, 1], + "pubnonces": [0, 1], + "msg": 0, + "signer": 0, + "error": "invalid_contrib", + "comment": "Invalid pubshare", + }, + { + "ids": [0, 1], + "pubshares": [0, 1], + "pubnonces": [0, 1, 2], + "msg": 0, + "signer": 0, + "error": "value", + "comment": "public nonces count is greater than ids and pubshares", + }, + ] + for case in verify_error_cases: + curr_ids = [ids[i] for i in case["ids"]] + curr_pubshares = [pubshares[i] for i in case["pubshares"]] + curr_pubnonces = [pubnonces[i] for i in case["pubnonces"]] + msg = case["msg"] + signer_idx = case["signer"] + curr_signers = SignersContext(n, t, curr_ids, curr_pubshares, thresh_pk) + expected_error = ( + ValueError if case["error"] == "value" else InvalidContributionError + ) + error = expect_exception( + # reuse the valid `psig` generated at the start of "verify fail test cases" + lambda: partial_sig_verify( + psig, curr_pubnonces, curr_signers, [], [], msg, signer_idx + ), + expected_error, + ) + vectors["verify_error_test_cases"].append( + { + "psig": bytes_to_hex(psig), + "id_indices": case["ids"], + "pubshare_indices": case["pubshares"], + "pubnonce_indices": case["pubnonces"], + "msg_index": case["msg"], + "signer_index": case["signer"], + "error": error, + "comment": case["comment"], + } + ) + + write_test_vectors("sign_verify_vectors.json", vectors) + + +def generate_tweak_vectors(): + vectors = dict() + + n, t, thresh_pk, xonly_thresh_pk, ids, secshares, pubshares = get_common_setup() + secshare_p0 = secshares[0] + + # Special indices for test cases + INVALID_TWEAK_IDX = 4 # Tweak exceeds secp256k1 group order + + vectors["n"] = n + vectors["t"] = t + vectors["threshold_pubkey"] = bytes_to_hex(thresh_pk) + vectors["secshare_p0"] = bytes_to_hex(secshare_p0) + vectors["identifiers"] = ids + pubshares_with_invalid = pubshares + [INVALID_PUBSHARE] + vectors["pubshares"] = bytes_list_to_hex(pubshares_with_invalid) + + secnonces, pubnonces = generate_all_nonces( + COMMON_RAND, secshares, pubshares, xonly_thresh_pk + ) + vectors["secnonce_p0"] = bytes_to_hex(secnonces[0]) + vectors["pubnonces"] = bytes_list_to_hex(pubnonces) + + # create valid aggnonces + indices_grp = [[0, 1], [0, 1, 2]] + aggnonces = [ + nonce_agg([pubnonces[i] for i in indices], [ids[i] for i in indices]) + for indices in indices_grp + ] + # aggnonce with inf points + aggnonces.append( + nonce_agg( + [pubnonces[0], pubnonces[1], pubnonces[-1]], + [ids[0], ids[1], ids[2]], + ) + ) + vectors["aggnonces"] = bytes_list_to_hex(aggnonces) + + vectors["tweaks"] = bytes_list_to_hex(COMMON_TWEAKS) + vectors["msg"] = bytes_to_hex(COMMON_MSGS[0]) + + vectors["valid_test_cases"] = [] + # --- Valid Test Cases --- + valid_cases = [ + {"tweaks_indices": [], "is_xonly": [], "comment": "No tweak"}, + {"tweaks_indices": [0], "is_xonly": [True], "comment": "A single x-only tweak"}, + {"tweaks_indices": [0], "is_xonly": [False], "comment": "A single plain tweak"}, + { + "tweaks_indices": [0, 1], + "is_xonly": [False, True], + "comment": "A plain tweak followed by an x-only tweak", + }, + { + "tweaks_indices": [0, 1, 2, 3], + "is_xonly": [True, False, True, False], + "comment": "Four tweaks: x-only, plain, x-only, plain. If an implementation prohibits applying plain tweaks after x-only tweaks, it can skip this test vector or return an error", + }, + { + "tweaks_indices": [0, 1, 2, 3], + "is_xonly": [False, False, True, True], + "comment": "Four tweaks: plain, plain, x-only, x-only", + }, + { + "tweaks_indices": [0, 1, 2, 3], + "is_xonly": [False, False, True, True], + "indices": [0, 1, 2], + "aggnonce_idx": 1, + "comment": "Tweaking with max number of participants. The expected value (partial sig) must match the previous test vector", + }, + ] + for case in valid_cases: + indices = case.get("indices", [0, 1]) + curr_ids = [ids[i] for i in indices] + curr_pubshares = [pubshares_with_invalid[i] for i in indices] + aggnonce_idx = case.get("aggnonce_idx", 0) + curr_aggnonce = aggnonces[aggnonce_idx] + curr_tweaks = [COMMON_TWEAKS[i] for i in case["tweaks_indices"]] + curr_tweak_modes = case["is_xonly"] + signer_idx = 0 + my_id = curr_ids[signer_idx] + + curr_signers = SignersContext(n, t, curr_ids, curr_pubshares, thresh_pk) + session_ctx = SessionContext( + curr_aggnonce, curr_signers, curr_tweaks, curr_tweak_modes, COMMON_MSGS[0] + ) + psig = sign(bytearray(secnonces[0]), secshare_p0, my_id, session_ctx) + + vectors["valid_test_cases"].append( + { + "id_indices": indices, + "pubshare_indices": indices, + "pubnonce_indices": indices, + "tweak_indices": case["tweaks_indices"], + "aggnonce_index": aggnonce_idx, + "is_xonly": curr_tweak_modes, + "signer_index": signer_idx, + "expected": bytes_to_hex(psig), + "comment": case["comment"], + } + ) + + vectors["error_test_cases"] = [] + # --- Error Test Cases --- + error_cases = [ + { + "tweaks_indices": [INVALID_TWEAK_IDX], + "is_xonly": [False], + "comment": "Tweak is invalid because it exceeds group size", + }, + { + "tweaks_indices": [0, 1, 2, 3], + "is_xonly": [True, False], + "comment": "Tweaks count doesn't match the tweak modes count", + }, + ] + for case in error_cases: + indices = [0, 1] + curr_ids = [ids[i] for i in indices] + curr_pubshares = [pubshares_with_invalid[i] for i in indices] + aggnonce_idx = 0 + curr_aggnonce = aggnonces[aggnonce_idx] + curr_tweaks = [COMMON_TWEAKS[i] for i in case["tweaks_indices"]] + curr_tweak_modes = case["is_xonly"] + signer_idx = 0 + my_id = curr_ids[signer_idx] + + curr_signers = SignersContext(n, t, curr_ids, curr_pubshares, thresh_pk) + session_ctx = SessionContext( + curr_aggnonce, curr_signers, curr_tweaks, curr_tweak_modes, COMMON_MSGS[0] + ) + error = expect_exception( + lambda: sign(bytearray(secnonces[0]), secshare_p0, my_id, session_ctx), + ValueError, + ) + vectors["error_test_cases"].append( + { + "id_indices": indices, + "pubshare_indices": indices, + "tweak_indices": case["tweaks_indices"], + "aggnonce_index": 0, + "is_xonly": curr_tweak_modes, + "signer_index": signer_idx, + "error": error, + "comment": case["comment"], + } + ) + + write_test_vectors("tweak_vectors.json", vectors) + + +def generate_det_sign_vectors(): + vectors = dict() + + n, t, thresh_pk, xonly_thresh_pk, ids, secshares, pubshares = get_common_setup() + secshare_p0 = secshares[0] + + # Special indices for test cases + INVALID_PUBSHARE_IDX = 3 # Invalid pubshare (appended to list) + INVALID_TWEAK_IDX = 1 # Invalid tweak (COMMON_TWEAKS[4]) + RAND_NONE_IDX = 1 # No auxiliary randomness (None) + RAND_MAX_IDX = 2 # Max auxiliary randomness (0xFF...FF) + + vectors["n"] = n + vectors["t"] = t + vectors["threshold_pubkey"] = bytes_to_hex(thresh_pk) + vectors["secshare_p0"] = bytes_to_hex(secshare_p0) + vectors["identifiers"] = ids + pubshares.append(INVALID_PUBSHARE) + vectors["pubshares"] = bytes_list_to_hex(pubshares) + + vectors["msgs"] = bytes_list_to_hex(COMMON_MSGS) + assert len(COMMON_MSGS[2]) == 38 + + rands = [ + bytes.fromhex( + "0000000000000000000000000000000000000000000000000000000000000000" + ), + None, + bytes.fromhex( + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + ), + ] + + tweaks = [ + [COMMON_TWEAKS[0]], + [COMMON_TWEAKS[4]], + ] + + vectors["valid_test_cases"] = [] + # --- Valid Test Cases --- + valid_cases = [ + { + "indices": [0, 1], + "signer": 0, + "msg": 0, + "rand": 0, + "comment": "Signing with minimum number of participants", + }, + { + "indices": [1, 0], + "signer": 1, + "msg": 0, + "rand": 0, + "comment": "Partial-signature shouldn't change if the order of signers set changes. Note: The deterministic sign will generate the same secnonces due to unchanged parameters", + }, + { + "indices": [0, 2], + "signer": 0, + "msg": 0, + "rand": 0, + "comment": "Partial-signature changes if the members of signers set changes", + }, + { + "indices": [0, 1], + "signer": 0, + "msg": 0, + "rand": RAND_NONE_IDX, + "comment": "Signing without auxiliary randomness", + }, + { + "indices": [0, 1], + "signer": 0, + "msg": 0, + "rand": RAND_MAX_IDX, + "comment": "Signing with max auxiliary randomness", + }, + { + "indices": [0, 1, 2], + "signer": 0, + "msg": 0, + "rand": 0, + "comment": "Signing with maximum number of participants", + }, + { + "indices": [0, 1], + "signer": 0, + "msg": 1, + "rand": 0, + "comment": "Empty message", + }, + { + "indices": [0, 1], + "signer": 0, + "msg": 2, + "rand": 0, + "comment": "Message longer than 32 bytes (38-byte msg)", + }, + { + "indices": [0, 1], + "signer": 0, + "msg": 0, + "rand": 0, + "tweaks": 0, + "is_xonly": [True], + "comment": "Signing with tweaks", + }, + ] + for case in valid_cases: + curr_ids = [ids[i] for i in case["indices"]] + curr_pubshares = [pubshares[i] for i in case["indices"]] + curr_msg = COMMON_MSGS[case["msg"]] + curr_rand = rands[case["rand"]] + signer_index = case["signer"] + my_id = curr_ids[signer_index] + tweaks_idx = case.get("tweaks", None) + curr_tweaks = [] if tweaks_idx is None else tweaks[tweaks_idx] + curr_tweak_modes = case.get("is_xonly", []) + + # generate `aggothernonce` + other_ids = curr_ids[:signer_index] + curr_ids[signer_index + 1 :] + other_pubnonces = [] + for i in case["indices"]: + if i == signer_index: + continue + tmp = b"" if curr_rand is None else curr_rand + _, pub = nonce_gen_internal( + tmp, secshares[i], pubshares[i], xonly_thresh_pk, curr_msg, None + ) + other_pubnonces.append(pub) + curr_aggothernonce = nonce_agg(other_pubnonces, other_ids) + + curr_signers = SignersContext(n, t, curr_ids, curr_pubshares, thresh_pk) + expected = deterministic_sign( + secshare_p0, + my_id, + curr_aggothernonce, + curr_signers, + curr_tweaks, + curr_tweak_modes, + curr_msg, + curr_rand, + ) + + vectors["valid_test_cases"].append( + { + "rand": bytes_to_hex(curr_rand) if curr_rand is not None else curr_rand, + "aggothernonce": bytes_to_hex(curr_aggothernonce), + "id_indices": case["indices"], + "pubshare_indices": case["indices"], + "tweaks": bytes_list_to_hex(curr_tweaks), + "is_xonly": curr_tweak_modes, + "msg_index": case["msg"], + "signer_index": signer_index, + "expected": bytes_list_to_hex(list(expected)), + "comment": case["comment"], + } + ) + + vectors["error_test_cases"] = [] + # --- Error Test Cases --- + error_cases = [ + { + "ids": [2, 1], + "pubshares": [0, 1], + "signer_idx": None, + "signer_id": 0, + "msg": 0, + "rand": 0, + "aggothernonce": "02FCDBEE416E4426FB4004BAB2B416164845DEC27337AD2B96184236D715965AB2039F71F389F6808DC6176F062F80531E13EA5BC2612B690FC284AE66C2CD859CE9", + "error": "value", + "comment": "The signer's id is not in the participant identifier list", + }, + { + "ids": [0, 1, 1], + "pubshares": [0, 1, 1], + "signer_idx": 0, + "msg": 0, + "rand": 0, + "error": "value", + "comment": "The participant identifier list contains duplicate elements", + }, + { + "ids": [0, 1], + "pubshares": [2, 1], + "signer_idx": 0, + "msg": 0, + "rand": 0, + "error": "value", + "comment": "The signer's pubshare is not in the list of pubshares. This test case is optional: it can be skipped by implementations that do not check that the signer's pubshare is included in the list of pubshares.", + }, + { + "ids": [0, 1, 2], + "pubshares": [0, 1], + "signer_idx": 0, + "msg": 0, + "rand": 0, + "aggothernonce": "02FCDBEE416E4426FB4004BAB2B416164845DEC27337AD2B96184236D715965AB2039F71F389F6808DC6176F062F80531E13EA5BC2612B690FC284AE66C2CD859CE9", + "error": "value", + "comment": "The participant identifiers count exceed the participant public shares count", + }, + { + "ids": [0, 1], + "pubshares": [0, INVALID_PUBSHARE_IDX], + "signer_idx": 0, + "msg": 0, + "rand": 0, + "error": "invalid_contrib", + "comment": "Signer 1 provided an invalid participant public share", + }, + { + "ids": [0, 1], + "pubshares": [0, 1], + "signer_idx": 0, + "msg": 0, + "rand": 0, + "aggothernonce": "048465FCF0BBDBCF443AABCCE533D42B4B5A10966AC09A49655E8C42DAAB8FCD61037496A3CC86926D452CAFCFD55D25972CA1675D549310DE296BFF42F72EEEA8C9", + "error": "invalid_contrib", + "comment": "aggothernonce is invalid due wrong tag, 0x04, in the first half", + }, + { + "ids": [0, 1], + "pubshares": [0, 1], + "signer_idx": 0, + "msg": 0, + "rand": 0, + "aggothernonce": "0000000000000000000000000000000000000000000000000000000000000000000287BF891D2A6DEAEBADC909352AA9405D1428C15F4B75F04DAE642A95C2548480", + "error": "invalid_contrib", + "comment": "aggothernonce is invalid because first half corresponds to point at infinity", + }, + { + "ids": [0, 1], + "pubshares": [0, 1], + "signer_idx": 0, + "msg": 0, + "rand": 0, + "tweaks": INVALID_TWEAK_IDX, + "is_xonly": [False], + "error": "value", + "comment": "Tweak is invalid because it exceeds group size", + }, + ] + for case in error_cases: + curr_ids = [ids[i] for i in case["ids"]] + curr_pubshares = [pubshares[i] for i in case["pubshares"]] + curr_msg = COMMON_MSGS[case["msg"]] + curr_rand = rands[case["rand"]] + signer_index = case["signer_idx"] + if case["signer_idx"] is None: + my_id = case["signer_id"] + else: + my_id = curr_ids[case["signer_idx"]] + tweaks_idx = case.get("tweaks", None) + curr_tweaks = [] if tweaks_idx is None else tweaks[tweaks_idx] + curr_tweak_modes = case.get("is_xonly", []) + + # generate `aggothernonce` + is_aggothernonce = case.get("aggothernonce", None) + if is_aggothernonce is None: + if signer_index is None: + other_ids = curr_ids[1:] + else: + other_ids = curr_ids[:signer_index] + curr_ids[signer_index + 1 :] + other_pubnonces = [] + for i in case["ids"]: + if i == signer_index: + continue + tmp = b"" if curr_rand is None else curr_rand + _, pub = nonce_gen_internal( + tmp, secshares[i], pubshares[i], xonly_thresh_pk, curr_msg, None + ) + other_pubnonces.append(pub) + curr_aggothernonce = nonce_agg(other_pubnonces, other_ids) + else: + curr_aggothernonce = bytes.fromhex(is_aggothernonce) + + expected_exception = ( + ValueError if case["error"] == "value" else InvalidContributionError + ) + curr_signers = SignersContext(n, t, curr_ids, curr_pubshares, thresh_pk) + error = expect_exception( + lambda: deterministic_sign( + secshare_p0, + my_id, + curr_aggothernonce, + curr_signers, + curr_tweaks, + curr_tweak_modes, + curr_msg, + curr_rand, + ), + expected_exception, + ) + + vectors["error_test_cases"].append( + { + "rand": bytes_to_hex(curr_rand) if curr_rand is not None else curr_rand, + "aggothernonce": bytes_to_hex(curr_aggothernonce), + "id_indices": case["ids"], + "pubshare_indices": case["pubshares"], + "tweaks": bytes_list_to_hex(curr_tweaks), + "is_xonly": curr_tweak_modes, + "msg_index": case["msg"], + "signer_index": signer_index, + **( + {"signer_id": case["signer_id"]} + if case["signer_idx"] is None + else {} + ), + "error": error, + "comment": case["comment"], + } + ) + + write_test_vectors("det_sign_vectors.json", vectors) + + +def generate_sig_agg_vectors(): + vectors = dict() + + n, t, thresh_pk, xonly_thresh_pk, ids, secshares, pubshares = get_common_setup() + + vectors["n"] = n + vectors["t"] = t + vectors["threshold_pubkey"] = bytes_to_hex(thresh_pk) + vectors["identifiers"] = ids + vectors["pubshares"] = bytes_list_to_hex(pubshares) + + secnonces, pubnonces = generate_all_nonces( + COMMON_RAND, secshares, pubshares, xonly_thresh_pk + ) + vectors["pubnonces"] = bytes_list_to_hex(pubnonces) + + vectors["tweaks"] = bytes_list_to_hex(SIG_AGG_TWEAKS) + + msg = bytes.fromhex( + "599C67EA410D005B9DA90817CF03ED3B1C868E4DA4EDF00A5880B0082C237869" + ) + vectors["msg"] = bytes_to_hex(msg) + + vectors["valid_test_cases"] = [] + # --- Valid Test Cases --- + valid_cases = [ + { + "indices": [0, 1], + "comment": "Signing with minimum number of participants", + }, + { + "indices": [1, 0], + "comment": "Order of the singer set shouldn't affect the aggregate signature. The expected value must match the previous test vector.", + }, + { + "indices": [0, 1], + "tweaks": [0, 1, 2], + "is_xonly": [True, False, False], + "comment": "Signing with tweaked threshold public key", + }, + { + "indices": [0, 1, 2], + "comment": "Signing with max number of participants and tweaked threshold public key", + }, + ] + for case in valid_cases: + curr_ids = [ids[i] for i in case["indices"]] + curr_pubshares = [pubshares[i] for i in case["indices"]] + curr_pubnonces = [pubnonces[i] for i in case["indices"]] + curr_aggnonce = nonce_agg(curr_pubnonces, curr_ids) + curr_msg = msg + tweak_indices = case.get("tweaks", []) + curr_tweaks = [SIG_AGG_TWEAKS[i] for i in tweak_indices] + curr_tweak_modes = case.get("is_xonly", []) + psigs = [] + curr_signers = SignersContext(n, t, curr_ids, curr_pubshares, thresh_pk) + session_ctx = SessionContext( + curr_aggnonce, + curr_signers, + curr_tweaks, + curr_tweak_modes, + curr_msg, + ) + for i in case["indices"]: + my_id = ids[i] + sig = sign(bytearray(secnonces[i]), secshares[i], my_id, session_ctx) + psigs.append(sig) + # TODO: verify the signatures here + bip340_sig = partial_sig_agg(psigs, curr_ids, session_ctx) + vectors["valid_test_cases"].append( + { + "id_indices": case["indices"], + "pubshare_indices": case["indices"], + "pubnonce_indices": case["indices"], + "aggnonce": bytes_to_hex(curr_aggnonce), + "tweak_indices": tweak_indices, + "is_xonly": curr_tweak_modes, + "psigs": bytes_list_to_hex(psigs), + "expected": bytes_to_hex(bip340_sig), + "comment": case["comment"], + } + ) + + vectors["error_test_cases"] = [] + # --- Error Test Cases --- + error_cases = [ + { + "indices": [0, 1], + "error": "invalid_contrib", + "comment": "Partial signature is invalid because it exceeds group size", + }, + { + "indices": [0, 1], + "error": "value", + "comment": "Partial signature count doesn't match the signer set count", + }, + ] + for j, case in enumerate(error_cases): + curr_ids = [ids[i] for i in case["indices"]] + curr_pubshares = [pubshares[i] for i in case["indices"]] + curr_pubnonces = [pubnonces[i] for i in case["indices"]] + curr_aggnonce = nonce_agg(curr_pubnonces, curr_ids) + curr_msg = msg + psigs = [] + curr_signers = SignersContext(n, t, curr_ids, curr_pubshares, thresh_pk) + session_ctx = SessionContext(curr_aggnonce, curr_signers, [], [], curr_msg) + for i in case["indices"]: + my_id = ids[i] + sig = sign(bytearray(secnonces[i]), secshares[i], my_id, session_ctx) + psigs.append(sig) + # TODO: verify the signatures here + + if j == 0: + invalid_psig = bytes.fromhex( + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141" + ) + psigs[1] = invalid_psig + if j == 1: + psigs.pop() + + expected_exception = ( + ValueError if case["error"] == "value" else InvalidContributionError + ) + error = expect_exception( + lambda: partial_sig_agg(psigs, curr_ids, session_ctx), expected_exception + ) + vectors["error_test_cases"].append( + { + "id_indices": case["indices"], + "pubshare_indices": case["indices"], + "pubnonce_indices": case["indices"], + "aggnonce": bytes_to_hex(curr_aggnonce), + "tweak_indices": [], + "is_xonly": [], + "psigs": bytes_list_to_hex(psigs), + "error": error, + "comment": case["comment"], + } + ) + + write_test_vectors("sig_agg_vectors.json", vectors) + + +def create_vectors_directory(): + if os.path.exists("vectors"): + shutil.rmtree("vectors") + os.makedirs("vectors") + + +def run_gen_vectors(test_name, test_func): + max_len = 30 + test_name = test_name.ljust(max_len, ".") + print(f"Running {test_name}...", end="", flush=True) + try: + test_func() + print("Done!") + except Exception as e: + print(f"Failed :'(\nError: {e}") + + +def main(): + create_vectors_directory() + + run_gen_vectors("generate_nonce_gen_vectors", generate_nonce_gen_vectors) + run_gen_vectors("generate_nonce_agg_vectors", generate_nonce_agg_vectors) + run_gen_vectors("generate_sign_verify_vectors", generate_sign_verify_vectors) + run_gen_vectors("generate_tweak_vectors", generate_tweak_vectors) + run_gen_vectors("generate_sig_agg_vectors", generate_sig_agg_vectors) + run_gen_vectors("generate_det_sign_vectors", generate_det_sign_vectors) + print("Test vectors generated successfully") + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/bip-frost-signing/python/mypy.ini b/bip-frost-signing/python/mypy.ini new file mode 100644 index 0000000000..08f1e086d1 --- /dev/null +++ b/bip-frost-signing/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 \ No newline at end of file diff --git a/bip-frost-signing/python/secp256k1lab/.github/workflows/main.yml b/bip-frost-signing/python/secp256k1lab/.github/workflows/main.yml new file mode 100644 index 0000000000..10e4bac61e --- /dev/null +++ b/bip-frost-signing/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.9", "3.10", "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.9", "3.10", "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-frost-signing/python/secp256k1lab/.gitignore b/bip-frost-signing/python/secp256k1lab/.gitignore new file mode 100644 index 0000000000..505a3b1ca2 --- /dev/null +++ b/bip-frost-signing/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-frost-signing/python/secp256k1lab/.python-version b/bip-frost-signing/python/secp256k1lab/.python-version new file mode 100644 index 0000000000..bd28b9c5c2 --- /dev/null +++ b/bip-frost-signing/python/secp256k1lab/.python-version @@ -0,0 +1 @@ +3.9 diff --git a/bip-frost-signing/python/secp256k1lab/CHANGELOG.md b/bip-frost-signing/python/secp256k1lab/CHANGELOG.md new file mode 100644 index 0000000000..4c756d3695 --- /dev/null +++ b/bip-frost-signing/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-frost-signing/python/secp256k1lab/COPYING b/bip-frost-signing/python/secp256k1lab/COPYING new file mode 100644 index 0000000000..e8f2163641 --- /dev/null +++ b/bip-frost-signing/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-frost-signing/python/secp256k1lab/README.md b/bip-frost-signing/python/secp256k1lab/README.md new file mode 100644 index 0000000000..dbc9dbd04c --- /dev/null +++ b/bip-frost-signing/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-frost-signing/python/secp256k1lab/pyproject.toml b/bip-frost-signing/python/secp256k1lab/pyproject.toml new file mode 100644 index 0000000000..a0bdd19f42 --- /dev/null +++ b/bip-frost-signing/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.9" +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-frost-signing/python/secp256k1lab/src/secp256k1lab/__init__.py b/bip-frost-signing/python/secp256k1lab/src/secp256k1lab/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/bip-frost-signing/python/secp256k1lab/src/secp256k1lab/bip340.py b/bip-frost-signing/python/secp256k1lab/src/secp256k1lab/bip340.py new file mode 100644 index 0000000000..ba839d16e1 --- /dev/null +++ b/bip-frost-signing/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-frost-signing/python/secp256k1lab/src/secp256k1lab/ecdh.py b/bip-frost-signing/python/secp256k1lab/src/secp256k1lab/ecdh.py new file mode 100644 index 0000000000..73f47fa1a7 --- /dev/null +++ b/bip-frost-signing/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-frost-signing/python/secp256k1lab/src/secp256k1lab/keys.py b/bip-frost-signing/python/secp256k1lab/src/secp256k1lab/keys.py new file mode 100644 index 0000000000..3e28897e99 --- /dev/null +++ b/bip-frost-signing/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-frost-signing/python/secp256k1lab/src/secp256k1lab/py.typed b/bip-frost-signing/python/secp256k1lab/src/secp256k1lab/py.typed new file mode 100644 index 0000000000..e69de29bb2 diff --git a/bip-frost-signing/python/secp256k1lab/src/secp256k1lab/secp256k1.py b/bip-frost-signing/python/secp256k1lab/src/secp256k1lab/secp256k1.py new file mode 100644 index 0000000000..1a99dc4899 --- /dev/null +++ b/bip-frost-signing/python/secp256k1lab/src/secp256k1lab/secp256k1.py @@ -0,0 +1,475 @@ +# 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 +""" + +# 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=0, b=1): + """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: + num = a % self.SIZE + den = 1 + if isinstance(b, type(self)): + den = (den * b._num) % self.SIZE + num = (num * b._den) % self.SIZE + else: + den = (den * b) % self.SIZE + assert den != 0 + if num == 0: + den = 1 + self._num = num + self._den = den + + def __add__(self, a): + """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): + """Compute the sum of an integer and a field element.""" + return type(self)(a) + self + + @classmethod + # REVIEW This should be + # def sum(cls, *es: Iterable[Self]) -> Self: + # but Self needs the typing_extension package on Python <= 3.12. + def sum(cls, *es): + """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): + """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): + """Compute the difference of an integer and a field element.""" + return type(self)(a) - self + + def __mul__(self, a): + """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): + """Compute the product of an integer with a field element.""" + return type(self)(a) * self + + def __truediv__(self, a): + """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): + """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): + """Negate a field element.""" + return type(self)(-self._num, self._den) + + def __int__(self): + """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): + """Compute the square root of a field element if it exists (None otherwise).""" + raise NotImplementedError + + def is_square(self): + """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): + """Determine whether this field element, represented as integer in 0..SIZE-1, is even.""" + return int(self) & 1 == 0 + + def __eq__(self, a): + """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 + return (self._num - self._den * a) % self.SIZE == 0 + + def to_bytes(self): + """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): + """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): + """Convert an integer to a field element (reduced modulo SIZE).""" + return cls(v % cls.SIZE) + + @classmethod + def from_bytes_checked(cls, b): + """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): + """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): + """Convert this field element to a 64 character hex string.""" + return f"{int(self):064x}" + + def __repr__(self): + """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): + # 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): + """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): + """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): + """Whether the group element is the point at infinity.""" + return self._infinity + + @property + def x(self): + """The x coordinate (a field element) of a non-infinite group element.""" + assert not self.infinity + return self._x + + @property + def y(self): + """The y coordinate (a field element) of a non-infinite group element.""" + assert not self.infinity + return self._y + + def __init__(self, x=None, y=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). + 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): + """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): + """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): + """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): + """Multiply an integer with a group element.""" + if self == G: + return FAST_G.mul(Scalar(a)) + return GE.batch_mul((Scalar(a), self)) + + def __neg__(self): + """Compute the negation of a group element.""" + if self.infinity: + return self + return GE(self.x, -self.y) + + def __sub__(self, a): + """Subtract a group element from another.""" + return self + (-a) + + def __eq__(self, a): + """Check if two group elements are equal.""" + return (self - a).infinity + + def has_even_y(self): + """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): + """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): + """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): + """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): + """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): + """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): + """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): + """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): + """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): + """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): + """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): + """Determine whether the provided field element is a valid X coordinate.""" + return (FE(x)**3 + 7).is_square() + + def __str__(self): + """Convert this group element to a string.""" + if self.infinity: + return "(inf)" + return f"({self.x},{self.y})" + + def __repr__(self): + """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): + """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): + self.table = [p] # table[i] = (2^i) * p + for _ in range(255): + p = p + p + self.table.append(p) + + def mul(self, a): + 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-frost-signing/python/secp256k1lab/src/secp256k1lab/secp256k1.pyi b/bip-frost-signing/python/secp256k1lab/src/secp256k1lab/secp256k1.pyi new file mode 100644 index 0000000000..abf77ae918 --- /dev/null +++ b/bip-frost-signing/python/secp256k1lab/src/secp256k1lab/secp256k1.pyi @@ -0,0 +1,133 @@ +from __future__ import annotations + +# mypy treated secp256k1lab.secp256k1 as Any, so callers (see frost_ref/signing.py) +# hit "Returning Any from function declared to return ..." errors. This stub describes adds type defintions for the secp256k1lab APIs used + +from typing_extensions import Self + + +class APrimeFE: + SIZE: int + + def __init__(self, a: int | Self = 0, b: int | Self = 1) -> None: ... + def __add__(self, a: int | Self) -> Self: ... + def __radd__(self, a: int) -> Self: ... + + @classmethod + def sum(cls, *es: Self) -> Self: ... + + def __sub__(self, a: int | Self) -> Self: ... + def __rsub__(self, a: int) -> Self: ... + def __mul__(self, a: int | Self) -> Self: ... + def __rmul__(self, a: int) -> Self: ... + def __truediv__(self, a: int | Self) -> Self: ... + def __pow__(self, a: int) -> Self: ... + def __neg__(self) -> Self: ... + def __int__(self) -> int: ... + def sqrt(self) -> Self | None: ... + def is_square(self) -> bool: ... + def is_even(self) -> bool: ... + def __eq__(self, a: object) -> bool: ... + def to_bytes(self) -> bytes: ... + + @classmethod + def from_int_checked(cls, v: int) -> Self: ... + + @classmethod + def from_int_wrapping(cls, v: int) -> Self: ... + + @classmethod + def from_bytes_checked(cls, b: bytes) -> Self: ... + + @classmethod + def from_bytes_wrapping(cls, b: bytes) -> Self: ... + + def __str__(self) -> str: ... + def __repr__(self) -> str: ... + + +class FE(APrimeFE): + SIZE: int + + def sqrt(self) -> Self | None: ... + + +class Scalar(APrimeFE): + SIZE: int + + @classmethod + def from_int_nonzero_checked(cls, v: int) -> Self: ... + + @classmethod + def from_bytes_nonzero_checked(cls, b: bytes) -> Self: ... + + +class GE: + ORDER: int + ORDER_HALF: int + + @property + def infinity(self) -> bool: ... + + @property + def x(self) -> FE: ... + + @property + def y(self) -> FE: ... + + def __init__(self, x: int | FE | None = None, y: int | FE | None = None) -> None: ... + def __add__(self, a: GE) -> GE: ... + + @staticmethod + def sum(*ps: GE) -> GE: ... + + @staticmethod + def batch_mul(*aps: tuple[int | Scalar, GE]) -> GE: ... + + def __rmul__(self, a: int | Scalar) -> GE: ... + def __neg__(self) -> GE: ... + def __sub__(self, a: GE) -> GE: ... + def __eq__(self, a: object) -> bool: ... + def has_even_y(self) -> bool: ... + def to_bytes_compressed(self) -> bytes: ... + def to_bytes_compressed_with_infinity(self) -> bytes: ... + def to_bytes_uncompressed(self) -> bytes: ... + def to_bytes_xonly(self) -> bytes: ... + + @staticmethod + def lift_x(x: int | FE) -> GE: ... + + @staticmethod + def from_bytes_compressed(b: bytes) -> GE: ... + + @staticmethod + def from_bytes_compressed_with_infinity(b: bytes) -> GE: ... + + @staticmethod + def from_bytes_uncompressed(b: bytes) -> GE: ... + + @staticmethod + def from_bytes(b: bytes) -> GE: ... + + @staticmethod + def from_bytes_xonly(b: bytes) -> GE: ... + + @staticmethod + def is_valid_x(x: int | FE) -> bool: ... + + def __str__(self) -> str: ... + def __repr__(self) -> str: ... + def __hash__(self) -> int: ... + + +G: GE + + +class FastGEMul: + table: list[GE] + + def __init__(self, p: GE) -> None: ... + def mul(self, a: int | Scalar) -> GE: ... + + +FAST_G: FastGEMul diff --git a/bip-frost-signing/python/secp256k1lab/src/secp256k1lab/util.py b/bip-frost-signing/python/secp256k1lab/src/secp256k1lab/util.py new file mode 100644 index 0000000000..d8c744b795 --- /dev/null +++ b/bip-frost-signing/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-frost-signing/python/secp256k1lab/test/__init__.py b/bip-frost-signing/python/secp256k1lab/test/__init__.py new file mode 100644 index 0000000000..862ed6e21c --- /dev/null +++ b/bip-frost-signing/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-frost-signing/python/secp256k1lab/test/test_bip340.py b/bip-frost-signing/python/secp256k1lab/test/test_bip340.py new file mode 100644 index 0000000000..7fafad54bd --- /dev/null +++ b/bip-frost-signing/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-frost-signing/python/secp256k1lab/test/test_ecdh.py b/bip-frost-signing/python/secp256k1lab/test/test_ecdh.py new file mode 100644 index 0000000000..63c9da7a1b --- /dev/null +++ b/bip-frost-signing/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-frost-signing/python/secp256k1lab/test/test_secp256k1.py b/bip-frost-signing/python/secp256k1lab/test/test_secp256k1.py new file mode 100644 index 0000000000..c6aee19a0a --- /dev/null +++ b/bip-frost-signing/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-frost-signing/python/secp256k1lab/test/vectors/bip340.csv b/bip-frost-signing/python/secp256k1lab/test/vectors/bip340.csv new file mode 100644 index 0000000000..aa317a3b3d --- /dev/null +++ b/bip-frost-signing/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-frost-signing/python/tests.py b/bip-frost-signing/python/tests.py new file mode 100755 index 0000000000..442bf492a1 --- /dev/null +++ b/bip-frost-signing/python/tests.py @@ -0,0 +1,669 @@ +#!/usr/bin/env python3 + +import json +import os +import secrets +import sys +import time +from typing import List, Optional, Tuple + +from frost_ref.signing import ( + COORDINATOR_ID, + InvalidContributionError, + PlainPk, + SessionContext, + SignersContext, + XonlyPk, + deterministic_sign, + get_xonly_pk, + thresh_pubkey_and_tweak, + nonce_agg, + nonce_gen, + nonce_gen_internal, + partial_sig_agg, + partial_sig_verify, + partial_sig_verify_internal, + sign, +) +from secp256k1lab.keys import pubkey_gen_plain +from secp256k1lab.secp256k1 import G, Scalar +from secp256k1lab.bip340 import schnorr_verify +from secp256k1lab.util import int_from_bytes +from trusted_dealer import trusted_dealer_keygen + + +def fromhex_all(hex_values): + return [bytes.fromhex(value) for value in hex_values] + + +# Check that calling `try_fn` raises a `exception`. If `exception` is raised, +# examine it with `except_fn`. +def assert_raises(exception, try_fn, except_fn): + raised = False + try: + try_fn() + except exception as e: + raised = True + assert except_fn(e) + except BaseException: + raise AssertionError("Wrong exception raised in a test.") + if not raised: + raise AssertionError( + "Exception was _not_ raised in a test where it was required." + ) + + +def get_error_details(test_case): + error = test_case["error"] + if error["type"] == "InvalidContributionError": + exception = InvalidContributionError + if "contrib" in error: + + def except_fn(e): + return e.id == error["id"] and e.contrib == error["contrib"] + else: + + def except_fn(e): + return e.id == error["id"] + elif error["type"] == "ValueError": + exception = ValueError + + def except_fn(e): + return str(e) == error["message"] + else: + raise RuntimeError(f"Invalid error type: {error['type']}") + return exception, except_fn + + +def generate_frost_keys( + n: int, t: int +) -> Tuple[PlainPk, List[int], List[bytes], List[PlainPk]]: + if not (2 <= t <= n): + raise ValueError("values must satisfy: 2 <= t <= n") + + thresh_pk, secshares, pubshares = trusted_dealer_keygen( + secrets.token_bytes(32), n, t + ) + + # IDs are 0-indexed: the index in the list IS the participant ID + assert len(secshares) == n + identifiers = list(range(len(secshares))) + + return (thresh_pk, identifiers, secshares, pubshares) + + +def test_nonce_gen_vectors(): + with open(os.path.join(sys.path[0], "vectors", "nonce_gen_vectors.json")) as f: + test_data = json.load(f) + + for test_case in test_data["test_cases"]: + + def get_value(key) -> bytes: + return bytes.fromhex(test_case[key]) + + def get_value_maybe(key) -> Optional[bytes]: + if test_case[key] is not None: + return get_value(key) + else: + return None + + rand_ = get_value("rand_") + secshare = get_value_maybe("secshare") + pubshare = get_value_maybe("pubshare") + if pubshare is not None: + pubshare = PlainPk(pubshare) + thresh_pk = get_value_maybe("threshold_pubkey") + if thresh_pk is not None: + thresh_pk = XonlyPk(thresh_pk) + msg = get_value_maybe("msg") + extra_in = get_value_maybe("extra_in") + expected_secnonce = get_value("expected_secnonce") + expected_pubnonce = get_value("expected_pubnonce") + + assert nonce_gen_internal( + rand_, secshare, pubshare, thresh_pk, msg, extra_in + ) == (expected_secnonce, expected_pubnonce) + + +def test_nonce_agg_vectors(): + with open(os.path.join(sys.path[0], "vectors", "nonce_agg_vectors.json")) as f: + test_data = json.load(f) + + pubnonces_list = fromhex_all(test_data["pubnonces"]) + valid_test_cases = test_data["valid_test_cases"] + error_test_cases = test_data["error_test_cases"] + + for test_case in valid_test_cases: + # todo: assert the t <= len(pubnonces, ids) <= n + # todo: assert the values of ids too? 1 <= id <= n? + pubnonces = [pubnonces_list[i] for i in test_case["pubnonce_indices"]] + ids = test_case["participant_identifiers"] + expected_aggnonce = bytes.fromhex(test_case["expected_aggnonce"]) + assert nonce_agg(pubnonces, ids) == expected_aggnonce + + for test_case in error_test_cases: + exception, except_fn = get_error_details(test_case) + pubnonces = [pubnonces_list[i] for i in test_case["pubnonce_indices"]] + ids = test_case["participant_identifiers"] + assert_raises(exception, lambda: nonce_agg(pubnonces, ids), except_fn) + + +# todo: include vectors from the frost draft too +# todo: add a test where thresh_pk is even (might need to modify json file) +def test_sign_verify_vectors(): + with open(os.path.join(sys.path[0], "vectors", "sign_verify_vectors.json")) as f: + test_data = json.load(f) + + n = test_data["n"] + t = test_data["t"] + secshare_p0 = bytes.fromhex(test_data["secshare_p0"]) + ids = test_data["identifiers"] + pubshares = fromhex_all(test_data["pubshares"]) + thresh_pk = bytes.fromhex(test_data["threshold_pubkey"]) + # The public key corresponding to the first participant (secshare_p0) is at index 0 + assert pubshares[0] == PlainPk(pubkey_gen_plain(secshare_p0)) + + secnonces_p0 = fromhex_all(test_data["secnonces_p0"]) + pubnonces = fromhex_all(test_data["pubnonces"]) + # The public nonce corresponding to first participant (secnonce_p0[0]) is at index 0 + k_1 = int_from_bytes(secnonces_p0[0][0:32]) + k_2 = int_from_bytes(secnonces_p0[0][32:64]) + R_s1 = k_1 * G + R_s2 = k_2 * G + assert not R_s1.infinity and not R_s2.infinity + assert pubnonces[0] == R_s1.to_bytes_compressed() + R_s2.to_bytes_compressed() + + aggnonces = fromhex_all(test_data["aggnonces"]) + msgs = fromhex_all(test_data["msgs"]) + + valid_test_cases = test_data["valid_test_cases"] + sign_error_test_cases = test_data["sign_error_test_cases"] + verify_fail_test_cases = test_data["verify_fail_test_cases"] + verify_error_test_cases = test_data["verify_error_test_cases"] + + for test_case in valid_test_cases: + ids_tmp = [ids[i] for i in test_case["id_indices"]] + pubshares_tmp = [PlainPk(pubshares[i]) for i in test_case["pubshare_indices"]] + pubnonces_tmp = [pubnonces[i] for i in test_case["pubnonce_indices"]] + aggnonce_tmp = aggnonces[test_case["aggnonce_index"]] + # Make sure that pubnonces and aggnonce in the test vector are consistent + assert nonce_agg(pubnonces_tmp, ids_tmp) == aggnonce_tmp + msg = msgs[test_case["msg_index"]] + signer_index = test_case["signer_index"] + my_id = ids_tmp[signer_index] + expected = bytes.fromhex(test_case["expected"]) + + signers_tmp = SignersContext(n, t, ids_tmp, pubshares_tmp, thresh_pk) + session_ctx = SessionContext(aggnonce_tmp, signers_tmp, [], [], msg) + # WARNING: An actual implementation should _not_ copy the secnonce. + # Reusing the secnonce, as we do here for testing purposes, can leak the + # secret key. + secnonce_tmp = bytearray(secnonces_p0[0]) + assert sign(secnonce_tmp, secshare_p0, my_id, session_ctx) == expected + assert partial_sig_verify( + expected, pubnonces_tmp, signers_tmp, [], [], msg, signer_index + ) + + for test_case in sign_error_test_cases: + exception, except_fn = get_error_details(test_case) + ids_tmp = [ids[i] for i in test_case["id_indices"]] + pubshares_tmp = [PlainPk(pubshares[i]) for i in test_case["pubshare_indices"]] + aggnonce_tmp = aggnonces[test_case["aggnonce_index"]] + msg = msgs[test_case["msg_index"]] + signer_index = test_case["signer_index"] + my_id = ( + test_case["signer_id"] if signer_index is None else ids_tmp[signer_index] + ) + secnonce_tmp = bytearray(secnonces_p0[test_case["secnonce_index"]]) + + signers_tmp = SignersContext(n, t, ids_tmp, pubshares_tmp, thresh_pk) + session_ctx = SessionContext(aggnonce_tmp, signers_tmp, [], [], msg) + assert_raises( + exception, + lambda: sign(secnonce_tmp, secshare_p0, my_id, session_ctx), + except_fn, + ) + + for test_case in verify_fail_test_cases: + psig = bytes.fromhex(test_case["psig"]) + ids_tmp = [ids[i] for i in test_case["id_indices"]] + pubshares_tmp = [PlainPk(pubshares[i]) for i in test_case["pubshare_indices"]] + pubnonces_tmp = [pubnonces[i] for i in test_case["pubnonce_indices"]] + msg = msgs[test_case["msg_index"]] + signer_index = test_case["signer_index"] + + signers_tmp = SignersContext(n, t, ids_tmp, pubshares_tmp, thresh_pk) + assert not partial_sig_verify_internal( + psig, + ids_tmp[signer_index], + pubnonces_tmp[signer_index], + pubshares_tmp[signer_index], + session_ctx, + ) + + for test_case in verify_error_test_cases: + exception, except_fn = get_error_details(test_case) + + psig = bytes.fromhex(test_case["psig"]) + ids_tmp = [ids[i] for i in test_case["id_indices"]] + pubshares_tmp = [PlainPk(pubshares[i]) for i in test_case["pubshare_indices"]] + pubnonces_tmp = [pubnonces[i] for i in test_case["pubnonce_indices"]] + msg = msgs[test_case["msg_index"]] + signer_index = test_case["signer_index"] + signers_tmp = SignersContext(n, t, ids_tmp, pubshares_tmp, thresh_pk) + assert_raises( + exception, + lambda: partial_sig_verify( + psig, pubnonces_tmp, signers_tmp, [], [], msg, signer_index + ), + except_fn, + ) + + +def test_tweak_vectors(): + with open(os.path.join(sys.path[0], "vectors", "tweak_vectors.json")) as f: + test_data = json.load(f) + + n = test_data["n"] + t = test_data["t"] + secshare_p0 = bytes.fromhex(test_data["secshare_p0"]) + ids = test_data["identifiers"] + pubshares = fromhex_all(test_data["pubshares"]) + # The public key corresponding to the first participant (secshare_p0) is at index 0 + assert pubshares[0] == PlainPk(pubkey_gen_plain(secshare_p0)) + thresh_pk = bytes.fromhex(test_data["threshold_pubkey"]) + + secnonce_p0 = bytearray(bytes.fromhex(test_data["secnonce_p0"])) + pubnonces = fromhex_all(test_data["pubnonces"]) + # The public nonce corresponding to first participant (secnonce_p0[0]) is at index 0 + k_1 = Scalar.from_bytes_checked(secnonce_p0[0:32]) + k_2 = Scalar.from_bytes_checked(secnonce_p0[32:64]) + R_s1 = k_1 * G + R_s2 = k_2 * G + assert not R_s1.infinity and not R_s2.infinity + assert pubnonces[0] == R_s1.to_bytes_compressed() + R_s2.to_bytes_compressed() + + aggnonces = fromhex_all(test_data["aggnonces"]) + tweaks = fromhex_all(test_data["tweaks"]) + + msg = bytes.fromhex(test_data["msg"]) + + valid_test_cases = test_data["valid_test_cases"] + error_test_cases = test_data["error_test_cases"] + + for i, test_case in enumerate(valid_test_cases): + ids_tmp = [ids[i] for i in test_case["id_indices"]] + pubshares_tmp = [PlainPk(pubshares[i]) for i in test_case["pubshare_indices"]] + pubnonces_tmp = [pubnonces[i] for i in test_case["pubnonce_indices"]] + aggnonce_tmp = aggnonces[test_case["aggnonce_index"]] + # Make sure that pubnonces and aggnonce in the test vector are consistent + assert nonce_agg(pubnonces_tmp, ids_tmp) == aggnonce_tmp + tweaks_tmp = [tweaks[i] for i in test_case["tweak_indices"]] + tweak_modes_tmp = test_case["is_xonly"] + signer_index = test_case["signer_index"] + my_id = ids_tmp[signer_index] + expected = bytes.fromhex(test_case["expected"]) + + signers_tmp = SignersContext(n, t, ids_tmp, pubshares_tmp, thresh_pk) + session_ctx = SessionContext( + aggnonce_tmp, signers_tmp, tweaks_tmp, tweak_modes_tmp, msg + ) + # WARNING: An actual implementation should _not_ copy the secnonce. + # Reusing the secnonce, as we do here for testing purposes, can leak the + # secret key. + secnonce_tmp = bytearray(secnonce_p0) + assert sign(secnonce_tmp, secshare_p0, my_id, session_ctx) == expected + assert partial_sig_verify( + expected, + pubnonces_tmp, + signers_tmp, + tweaks_tmp, + tweak_modes_tmp, + msg, + signer_index, + ) + + for test_case in error_test_cases: + exception, except_fn = get_error_details(test_case) + ids_tmp = [ids[i] for i in test_case["id_indices"]] + pubshares_tmp = [PlainPk(pubshares[i]) for i in test_case["pubshare_indices"]] + aggnonce_tmp = aggnonces[test_case["aggnonce_index"]] + tweaks_tmp = [tweaks[i] for i in test_case["tweak_indices"]] + tweak_modes_tmp = test_case["is_xonly"] + signer_index = test_case["signer_index"] + my_id = ids_tmp[signer_index] + + signers_tmp = SignersContext(n, t, ids_tmp, pubshares_tmp, thresh_pk) + session_ctx = SessionContext( + aggnonce_tmp, signers_tmp, tweaks_tmp, tweak_modes_tmp, msg + ) + assert_raises( + exception, + lambda: sign(secnonce_p0, secshare_p0, my_id, session_ctx), + except_fn, + ) + + +def test_det_sign_vectors(): + with open(os.path.join(sys.path[0], "vectors", "det_sign_vectors.json")) as f: + test_data = json.load(f) + + n = test_data["n"] + t = test_data["t"] + secshare_p0 = bytes.fromhex(test_data["secshare_p0"]) + ids = test_data["identifiers"] + pubshares = fromhex_all(test_data["pubshares"]) + # The public key corresponding to the first participant (secshare_p0) is at index 0 + assert pubshares[0] == PlainPk(pubkey_gen_plain(secshare_p0)) + + thresh_pk = bytes.fromhex(test_data["threshold_pubkey"]) + msgs = fromhex_all(test_data["msgs"]) + + valid_test_cases = test_data["valid_test_cases"] + error_test_cases = test_data["error_test_cases"] + + for test_case in valid_test_cases: + ids_tmp = [ids[i] for i in test_case["id_indices"]] + pubshares_tmp = [PlainPk(pubshares[i]) for i in test_case["pubshare_indices"]] + aggothernonce = bytes.fromhex(test_case["aggothernonce"]) + tweaks = fromhex_all(test_case["tweaks"]) + is_xonly = test_case["is_xonly"] + msg = msgs[test_case["msg_index"]] + signer_index = test_case["signer_index"] + my_id = ids_tmp[signer_index] + rand = ( + bytes.fromhex(test_case["rand"]) if test_case["rand"] is not None else None + ) + expected = fromhex_all(test_case["expected"]) + + signers_tmp = SignersContext(n, t, ids_tmp, pubshares_tmp, thresh_pk) + pubnonce, psig = deterministic_sign( + secshare_p0, + my_id, + aggothernonce, + signers_tmp, + tweaks, + is_xonly, + msg, + rand, + ) + assert pubnonce == expected[0] + assert psig == expected[1] + + pubnonces = [aggothernonce, pubnonce] + aggnonce_tmp = nonce_agg(pubnonces, [COORDINATOR_ID, my_id]) + session_ctx = SessionContext(aggnonce_tmp, signers_tmp, tweaks, is_xonly, msg) + assert partial_sig_verify_internal( + psig, my_id, pubnonce, pubshares_tmp[signer_index], session_ctx + ) + + for test_case in error_test_cases: + exception, except_fn = get_error_details(test_case) + ids_tmp = [ids[i] for i in test_case["id_indices"]] + pubshares_tmp = [PlainPk(pubshares[i]) for i in test_case["pubshare_indices"]] + aggothernonce = bytes.fromhex(test_case["aggothernonce"]) + tweaks = fromhex_all(test_case["tweaks"]) + is_xonly = test_case["is_xonly"] + msg = msgs[test_case["msg_index"]] + signer_index = test_case["signer_index"] + my_id = ( + test_case["signer_id"] if signer_index is None else ids_tmp[signer_index] + ) + rand = ( + bytes.fromhex(test_case["rand"]) if test_case["rand"] is not None else None + ) + + signers_tmp = SignersContext(n, t, ids_tmp, pubshares_tmp, thresh_pk) + + def try_fn(): + return deterministic_sign( + secshare_p0, + my_id, + aggothernonce, + signers_tmp, + tweaks, + is_xonly, + msg, + rand, + ) + + assert_raises(exception, try_fn, except_fn) + + +def test_sig_agg_vectors(): + with open(os.path.join(sys.path[0], "vectors", "sig_agg_vectors.json")) as f: + test_data = json.load(f) + + n = test_data["n"] + t = test_data["t"] + ids = test_data["identifiers"] + pubshares = fromhex_all(test_data["pubshares"]) + thresh_pk = bytes.fromhex(test_data["threshold_pubkey"]) + # These nonces are only required if the tested API takes the individual + # nonces and not the aggregate nonce. + pubnonces = fromhex_all(test_data["pubnonces"]) + + tweaks = fromhex_all(test_data["tweaks"]) + msg = bytes.fromhex(test_data["msg"]) + + valid_test_cases = test_data["valid_test_cases"] + error_test_cases = test_data["error_test_cases"] + + for test_case in valid_test_cases: + ids_tmp = [ids[i] for i in test_case["id_indices"]] + pubshares_tmp = [PlainPk(pubshares[i]) for i in test_case["pubshare_indices"]] + pubnonces_tmp = [pubnonces[i] for i in test_case["pubnonce_indices"]] + aggnonce_tmp = bytes.fromhex(test_case["aggnonce"]) + # Make sure that pubnonces and aggnonce in the test vector are consistent + assert aggnonce_tmp == nonce_agg(pubnonces_tmp, ids_tmp) + + tweaks_tmp = [tweaks[i] for i in test_case["tweak_indices"]] + tweak_modes_tmp = test_case["is_xonly"] + psigs_tmp = fromhex_all(test_case["psigs"]) + expected = bytes.fromhex(test_case["expected"]) + + signers_tmp = SignersContext(n, t, ids_tmp, pubshares_tmp, thresh_pk) + session_ctx = SessionContext( + aggnonce_tmp, signers_tmp, tweaks_tmp, tweak_modes_tmp, msg + ) + # Make sure that the partial signatures in the test vector are consistent. The tested API takes only aggnonce (not pubnonces list), this check can be ignored + for i in range(len(ids_tmp)): + partial_sig_verify( + psigs_tmp[i], + pubnonces_tmp, + signers_tmp, + tweaks_tmp, + tweak_modes_tmp, + msg, + i, + ) + + bip340sig = partial_sig_agg(psigs_tmp, ids_tmp, session_ctx) + assert bip340sig == expected + tweaked_thresh_pk = get_xonly_pk( + thresh_pubkey_and_tweak(thresh_pk, tweaks_tmp, tweak_modes_tmp) + ) + assert schnorr_verify(msg, tweaked_thresh_pk, bip340sig) + + for test_case in error_test_cases: + exception, except_fn = get_error_details(test_case) + + ids_tmp = [ids[i] for i in test_case["id_indices"]] + pubshares_tmp = [PlainPk(pubshares[i]) for i in test_case["pubshare_indices"]] + pubnonces_tmp = [pubnonces[i] for i in test_case["pubnonce_indices"]] + aggnonce_tmp = bytes.fromhex(test_case["aggnonce"]) + + tweaks_tmp = [tweaks[i] for i in test_case["tweak_indices"]] + tweak_modes_tmp = test_case["is_xonly"] + psigs_tmp = fromhex_all(test_case["psigs"]) + + signers_tmp = SignersContext(n, t, ids_tmp, pubshares_tmp, thresh_pk) + session_ctx = SessionContext( + aggnonce_tmp, signers_tmp, tweaks_tmp, tweak_modes_tmp, msg + ) + assert_raises( + exception, + lambda: partial_sig_agg(psigs_tmp, ids_tmp, session_ctx), + except_fn, + ) + + +def test_sign_and_verify_random(iterations: int) -> None: + for itr in range(iterations): + secure_rng = secrets.SystemRandom() + # randomly choose a number: 2 <= number <= 10 + n = secure_rng.randrange(2, 11) + # randomly choose a number: 2 <= number <= n + t = secure_rng.randrange(2, n + 1) + + thresh_pk, ids, secshares, pubshares = generate_frost_keys(n, t) + assert len(ids) == len(secshares) == len(pubshares) == n + + # randomly choose the signer set, with len: t <= len <= n + signer_count = secure_rng.randrange(t, n + 1) + signer_indices = secure_rng.sample(range(n), signer_count) + assert ( + len(set(signer_indices)) == signer_count + ) # signer set must not contain duplicate ids + + signer_ids = [ids[i] for i in signer_indices] + signer_pubshares = [pubshares[i] for i in signer_indices] + # NOTE: secret values MUST NEVER BE COPIED!!! + # we do it here to improve the code readability + signer_secshares = [secshares[i] for i in signer_indices] + + signers_ctx = SignersContext(n, t, signer_ids, signer_pubshares, thresh_pk) + + # In this example, the message and threshold pubkey are known + # before nonce generation, so they can be passed into the nonce + # generation function as a defense-in-depth measure to protect + # against nonce reuse. + # + # If these values are not known when nonce_gen is called, empty + # byte arrays can be passed in for the corresponding arguments + # instead. + msg = secrets.token_bytes(32) + v = secrets.randbelow(4) + tweaks = [secrets.token_bytes(32) for _ in range(v)] + tweak_modes = [secrets.choice([False, True]) for _ in range(v)] + tweaked_thresh_pk = get_xonly_pk( + thresh_pubkey_and_tweak(thresh_pk, tweaks, tweak_modes) + ) + + signer_secnonces = [] + signer_pubnonces = [] + for i in range(signer_count - 1): + # Use a clock for extra_in + timestamp = time.clock_gettime_ns(time.CLOCK_MONOTONIC) + secnonce_i, pubnonce_i = nonce_gen( + signer_secshares[i], + signer_pubshares[i], + tweaked_thresh_pk, + msg, + timestamp.to_bytes(8, "big"), + ) + signer_secnonces.append(secnonce_i) + signer_pubnonces.append(pubnonce_i) + + # On even iterations use regular signing algorithm for the final signer, + # otherwise use deterministic signing algorithm + if itr % 2 == 0: + timestamp = time.clock_gettime_ns(time.CLOCK_MONOTONIC) + secnonce_final, pubnonce_final = nonce_gen( + signer_secshares[-1], + signer_pubshares[-1], + tweaked_thresh_pk, + msg, + timestamp.to_bytes(8, "big"), + ) + signer_secnonces.append(secnonce_final) + else: + aggothernonce = nonce_agg(signer_pubnonces, signer_ids[:-1]) + rand = secrets.token_bytes(32) + pubnonce_final, psig_final = deterministic_sign( + signer_secshares[-1], + signer_ids[-1], + aggothernonce, + signers_ctx, + tweaks, + tweak_modes, + msg, + rand, + ) + + signer_pubnonces.append(pubnonce_final) + aggnonce = nonce_agg(signer_pubnonces, signer_ids) + session_ctx = SessionContext(aggnonce, signers_ctx, tweaks, tweak_modes, msg) + + signer_psigs = [] + for i in range(signer_count): + if itr % 2 != 0 and i == signer_count - 1: + psig_i = psig_final # last signer would have already deterministically signed + else: + psig_i = sign( + signer_secnonces[i], signer_secshares[i], signer_ids[i], session_ctx + ) + assert partial_sig_verify( + psig_i, + signer_pubnonces, + signers_ctx, + tweaks, + tweak_modes, + msg, + i, + ) + signer_psigs.append(psig_i) + + # An exception is thrown if secnonce is accidentally reused + assert_raises( + ValueError, + lambda: sign( + signer_secnonces[0], signer_secshares[0], signer_ids[0], session_ctx + ), + lambda e: True, + ) + + # Wrong signer index + assert not partial_sig_verify( + signer_psigs[0], + signer_pubnonces, + signers_ctx, + tweaks, + tweak_modes, + msg, + 1, + ) + # Wrong message + assert not partial_sig_verify( + signer_psigs[0], + signer_pubnonces, + signers_ctx, + tweaks, + tweak_modes, + secrets.token_bytes(32), + 0, + ) + + bip340sig = partial_sig_agg(signer_psigs, signer_ids, session_ctx) + assert schnorr_verify(msg, tweaked_thresh_pk, bip340sig) + + +def run_test(test_name, test_func): + max_len = 30 + test_name = test_name.ljust(max_len, ".") + print(f"Running {test_name}...", end="", flush=True) + try: + test_func() + print("Passed!") + except Exception as e: + print(f"Failed :'(\nError: {e}") + + +if __name__ == "__main__": + run_test("test_nonce_gen_vectors", test_nonce_gen_vectors) + run_test("test_nonce_agg_vectors", test_nonce_agg_vectors) + run_test("test_sign_verify_vectors", test_sign_verify_vectors) + run_test("test_tweak_vectors", test_tweak_vectors) + run_test("test_det_sign_vectors", test_det_sign_vectors) + run_test("test_sig_agg_vectors", test_sig_agg_vectors) + run_test("test_sign_and_verify_random", lambda: test_sign_and_verify_random(6)) diff --git a/bip-frost-signing/python/tests.sh b/bip-frost-signing/python/tests.sh new file mode 100755 index 0000000000..db15516100 --- /dev/null +++ b/bip-frost-signing/python/tests.sh @@ -0,0 +1,24 @@ +#!/bin/sh +set -e + +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")" + +# Keep going if a linter fails +ruff check --quiet || true +ruff format --diff --quiet || true +mypy --no-error-summary . || true +# Be more strict in the reference code +mypy --no-error-summary --strict --untyped-calls-exclude=secp256k1lab -p frost_ref --follow-imports=silent || true + +./gen_vectors.py +./tests.py diff --git a/bip-frost-signing/python/trusted_dealer.py b/bip-frost-signing/python/trusted_dealer.py new file mode 100644 index 0000000000..08eb1d63e4 --- /dev/null +++ b/bip-frost-signing/python/trusted_dealer.py @@ -0,0 +1,140 @@ +# TODO: remove this file, and use trusted dealer BIP's reference code instead, after it gets published. + +# Implementation of the Trusted Dealer Key Generation approach for FROST mentioned +# in https://datatracker.ietf.org/doc/draft-irtf-cfrg-frost/15/ (Appendix D). +# +# It's worth noting that this isn't the only compatible method (with BIP FROST Signing), +# there are alternative key generation methods available, such as BIP-FROST-DKG: +# https://github.com/BlockstreamResearch/bip-frost-dkg + +from typing import Tuple, List +import unittest +import secrets + +from secp256k1lab.secp256k1 import G, GE, Scalar +from frost_ref.signing import derive_interpolating_value +from frost_ref import PlainPk + + +# evaluates poly using Horner's method, assuming coeff[0] corresponds +# to the coefficient of highest degree term +def polynomial_evaluate(coeffs: List[Scalar], x: Scalar) -> Scalar: + res = Scalar(0) + for coeff in coeffs: + res = res * x + coeff + return res + + +def secret_share_combine(shares: List[Scalar], ids: List[int]) -> Scalar: + assert len(shares) == len(ids) + secret = Scalar(0) + for share, my_id in zip(shares, ids): + lam = derive_interpolating_value(ids, my_id) + secret += share * lam + return secret + + +def secret_share_shard(secret: Scalar, coeffs: List[Scalar], n: int) -> List[Scalar]: + coeffs = coeffs + [secret] + + secshares = [] + # ids are 0-indexed (0..n-1), but polynomial is evaluated at x = id + 1 + # because p(0) = secret + for i in range(n): + x_i = Scalar(i + 1) + y_i = polynomial_evaluate(coeffs, x_i) + assert y_i != 0 + secshares.append(y_i) + return secshares + + +def trusted_dealer_keygen( + thresh_sk_: bytes, n: int, t: int +) -> Tuple[PlainPk, List[bytes], List[PlainPk]]: + assert 2 <= t <= n + + thresh_sk = Scalar.from_bytes_nonzero_checked(thresh_sk_) + # Key generation protocols are allowed to generate plain public keys (i.e., non-xonly) + thresh_pk_ = thresh_sk * G + assert not thresh_pk_.infinity + thresh_pk = PlainPk(thresh_pk_.to_bytes_compressed()) + + coeffs = [] + for _ in range(t - 1): + coeffs.append(Scalar.from_bytes_nonzero_checked(secrets.token_bytes(32))) + + secshares_ = secret_share_shard(thresh_sk, coeffs, n) + secshares = [x.to_bytes() for x in secshares_] + + pubshares_ = [x * G for x in secshares_] + pubshares = [PlainPk(X.to_bytes_compressed()) for X in pubshares_] + + return (thresh_pk, secshares, pubshares) + + +# Test vector from RFC draft. +# section F.5 of https://datatracker.ietf.org/doc/draft-irtf-cfrg-frost/15/ +class Tests(unittest.TestCase): + def setUp(self) -> None: + self.n = 3 + self.t = 2 + self.poly = [ + Scalar(0xFBF85EADAE3058EA14F19148BB72B45E4399C0B16028ACAF0395C9B03C823579), + Scalar(0x0D004150D27C3BF2A42F312683D35FAC7394B1E9E318249C1BFE7F0795A83114), + ] + # id[i] = i + 1, where i is the index in this list + self.secshares = [ + Scalar(0x08F89FFE80AC94DCB920C26F3F46140BFC7F95B493F8310F5FC1EA2B01F4254C), + Scalar(0x04F0FEAC2EDCEDC6CE1253B7FAB8C86B856A797F44D83D82A385554E6E401984), + Scalar(0x00E95D59DD0D46B0E303E500B62B7CCB0E555D49F5B849F5E748C071DA8C0DBC), + ] + self.secret = 0x0D004150D27C3BF2A42F312683D35FAC7394B1E9E318249C1BFE7F0795A83114 + + def test_polynomial_evaluate(self) -> None: + coeffs = self.poly.copy() + expected_secret = self.secret + + self.assertEqual(int(polynomial_evaluate(coeffs, Scalar(0))), expected_secret) + + def test_secret_share_combine(self) -> None: + secshares = self.secshares.copy() + expected_secret = self.secret + + # ids 0 and 1 + self.assertEqual( + secret_share_combine([secshares[0], secshares[1]], [0, 1]), expected_secret + ) + # ids 1 and 2 + self.assertEqual( + secret_share_combine([secshares[1], secshares[2]], [1, 2]), expected_secret + ) + # ids 0 and 2 + self.assertEqual( + secret_share_combine([secshares[0], secshares[2]], [0, 2]), expected_secret + ) + # all ids + self.assertEqual(secret_share_combine(secshares, [0, 1, 2]), expected_secret) + + def test_trusted_dealer_keygen(self) -> None: + thresh_sk_ = secrets.token_bytes(32) + n = 5 + t = 3 + thresh_pk_, secshares_, pubshares_ = trusted_dealer_keygen(thresh_sk_, n, t) + + thresh_sk = Scalar.from_bytes_nonzero_checked(thresh_sk_) + thresh_pk = GE.from_bytes_compressed(thresh_pk_) + secshares = [Scalar.from_bytes_nonzero_checked(s) for s in secshares_] + pubshares = [GE.from_bytes_compressed(p) for p in pubshares_] + + self.assertEqual(thresh_pk, thresh_sk * G) + + self.assertEqual(secret_share_combine(secshares, list(range(n))), thresh_sk) + self.assertEqual(len(secshares), n) + self.assertEqual(len(pubshares), n) + for i in range(len(pubshares)): + with self.subTest(i=i): + self.assertEqual(pubshares[i], secshares[i] * G) + + +if __name__ == "__main__": + unittest.main() diff --git a/bip-frost-signing/python/vectors/det_sign_vectors.json b/bip-frost-signing/python/vectors/det_sign_vectors.json new file mode 100644 index 0000000000..1ef249a5c2 --- /dev/null +++ b/bip-frost-signing/python/vectors/det_sign_vectors.json @@ -0,0 +1,400 @@ +{ + "n": 3, + "t": 2, + "threshold_pubkey": "03B02645D79ABFC494338139410F9D7F0A72BE86C952D6BDE1A66447B8A8D69237", + "secshare_p0": "CCD2EF4559DB05635091D80189AB3544D6668EFC0500A8D5FF51A1F4D32CC1F1", + "identifiers": [ + 0, + 1, + 2 + ], + "pubshares": [ + "022B02109FBCFB4DA3F53C7393B22E72A2A51C4AFBF0C01AAF44F73843CFB4B74B", + "02EC6444271D791A1DA95300329DB2268611B9C60E193DABFDEE0AA816AE512583", + "03113F810F612567D9552F46AF9BDA21A67D52060F95BD4A723F4B60B1820D3676", + "020000000000000000000000000000000000000000000000000000000000000007" + ], + "msgs": [ + "F95466D086770E689964664219266FE5ED215C92AE20BAB5C9D79ADDDDF3C0CF", + "", + "2626262626262626262626262626262626262626262626262626262626262626262626262626" + ], + "valid_test_cases": [ + { + "rand": "0000000000000000000000000000000000000000000000000000000000000000", + "aggothernonce": "0353BC2314D46C813AF81317AF1BDF99816B6444E416BB8D3DC04ACB2F5388D1AC02B13BC644F720223B547DB344C94E0F5E769B674D8A9C3F5E86A5231A5B9C3297", + "id_indices": [ + 0, + 1 + ], + "pubshare_indices": [ + 0, + 1 + ], + "tweaks": [], + "is_xonly": [], + "msg_index": 0, + "signer_index": 0, + "expected": [ + "02ECF1B7C1C3675E9EC605D95EAFF24CD7A4A0F8DAD89F8A9B6050F78F0C33522103B4368C46B3A9DA59EC52BB1A4B27A0446A302A046E593723E111FECEDE04CC30", + "572A5EED305B5533A4EC73D40B4645C5559BF2C137D57F1EBA974DA84B13B8D7" + ], + "comment": "Signing with minimum number of participants" + }, + { + "rand": "0000000000000000000000000000000000000000000000000000000000000000", + "aggothernonce": "02A8B5F064871F3BBB06D325F5B4A2B51487E0AE24F14E2A121C39B9F7CBDE7474038161382177105511164E63DD2C73138EDB271CF11B922DBA54CA4A9B365EDB55", + "id_indices": [ + 1, + 0 + ], + "pubshare_indices": [ + 1, + 0 + ], + "tweaks": [], + "is_xonly": [], + "msg_index": 0, + "signer_index": 1, + "expected": [ + "03D1F0BE59DFDC6E9BA09C830FB60B95CA154904F4919D080CF085A86F383EC66E024D3002A880C72187F62A041E01C0A356284C82BA2688DF2CB58B66DF28F75295", + "3BEEB13926DAAF6AA6F042FB20B2C33D5887EA8127B94197D8213474DAA1EF49" + ], + "comment": "Partial-signature shouldn't change if the order of signers set changes. Note: The deterministic sign will generate the same secnonces due to unchanged parameters" + }, + { + "rand": "0000000000000000000000000000000000000000000000000000000000000000", + "aggothernonce": "022260912C9999C9EC5A3B8E493F5DEB76DCA3E772345905E12C24D281612DA403022508B1A355D6E94A5BB239441B38971716031EF05DE9AF952BAB69799621AE52", + "id_indices": [ + 0, + 2 + ], + "pubshare_indices": [ + 0, + 2 + ], + "tweaks": [], + "is_xonly": [], + "msg_index": 0, + "signer_index": 0, + "expected": [ + "03C71C9C0B81BE5F3501553182252266CC935E5D2A900C376CCEC8EDF78BB3F8A903585BF5D830984908F0F3B090CBE8C32B3CCE9719F941B27D7E128F475F22EE14", + "62B7FC46552F3E3F3F7B07596FE9E73817A1576F4D0E850015334CE340434CC3" + ], + "comment": "Partial-signature changes if the members of signers set changes" + }, + { + "rand": null, + "aggothernonce": "0220E5B590F7058B5E88593C8635411767B416EB53378AEE7E40CD1D35329AD2C302164F00762CDFFF9138C43884661CC93FF53D61B4BFD3CD2BD7265F41775A4F0F", + "id_indices": [ + 0, + 1 + ], + "pubshare_indices": [ + 0, + 1 + ], + "tweaks": [], + "is_xonly": [], + "msg_index": 0, + "signer_index": 0, + "expected": [ + "036BF6DCC0A80061CC2CCA7A8BB20448534BEFDCCB9A2E23714F5AB1112CA3098A03D68C52E4D90D3951B19D3BC3BB7411066F4AAE6F4B91BEC1B811C1CDD38E1D9F", + "4F464D1B1F3EF7CABFBC446BBB1D226F82AC56B7DC1DAD4EF7A641E0A8F378FD" + ], + "comment": "Signing without auxiliary randomness" + }, + { + "rand": "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", + "aggothernonce": "0258091883DA3CCA616F8BEC80AAE2E397E3572DFFC3C261EDC695E5FCA0BE00180251EC60E426128FA2370F13A6235040120CCDB0CA97C36BC63693C44FC5B73E44", + "id_indices": [ + 0, + 1 + ], + "pubshare_indices": [ + 0, + 1 + ], + "tweaks": [], + "is_xonly": [], + "msg_index": 0, + "signer_index": 0, + "expected": [ + "03C2493BB6AB51760DBFBF1A08FCB6F82472552D4343637296DF6392D2F203821903B75F2339C21FB67AB0AA5351A15EB0A1CC5F7BCD3417CF06B6870AABAACAE9CF", + "77CE17195D39B6CBEF533E5EA416F173C294450AD8283153C67F6AAD87BFF0EB" + ], + "comment": "Signing with max auxiliary randomness" + }, + { + "rand": "0000000000000000000000000000000000000000000000000000000000000000", + "aggothernonce": "03B7F17DD6A2495898D19BDEA05C46F45B2C14BCA17F61C3DDC1D3C087CD748AB002CF7D031F5075C164D6A9D713F03B56422FD3472BDC8E0E6BB3ED6B6ED9C529AE", + "id_indices": [ + 0, + 1, + 2 + ], + "pubshare_indices": [ + 0, + 1, + 2 + ], + "tweaks": [], + "is_xonly": [], + "msg_index": 0, + "signer_index": 0, + "expected": [ + "0244C0BAA16B62D53047051B95111D89060FA23052C99CD4521B1222D98B267C2002B04FEDE26A30C4648657FC32B334FFA4FB2162133A52E4BB5373FC3B6B6FFA93", + "41F558B7335F18ED09F1B0F04324AC84FDD97DE9E63B5090542B9566BBBAD78E" + ], + "comment": "Signing with maximum number of participants" + }, + { + "rand": "0000000000000000000000000000000000000000000000000000000000000000", + "aggothernonce": "02E67053C8D9A6754D8243EF13B722B4909F3037FEAB23600D019E1C307D0BA2E20381207D01C5D6F4E2E654D2CBD23C9B6E8C38407FECAC41571E0EA2B307634004", + "id_indices": [ + 0, + 1 + ], + "pubshare_indices": [ + 0, + 1 + ], + "tweaks": [], + "is_xonly": [], + "msg_index": 1, + "signer_index": 0, + "expected": [ + "030B18187D6DA62CE6B6E48ACD472A4CD9285BE7671F11B80D8F838EFE55A2C9660218FC5EED7224B5EA4284ED913D44A634817608721EC1F75B615375B88B1F3293", + "DFDBC51E48278E34AFC436D9EF1944657AB49D349945841442380C54C36F9FA7" + ], + "comment": "Empty message" + }, + { + "rand": "0000000000000000000000000000000000000000000000000000000000000000", + "aggothernonce": "03C71201804A346CCAF2733EB9701302FA50F4C99B053D23C20363ECE2E05DEE97027D4DBE90EC16ACA95DBC921F4BD3FBDBCF1D9F3D01627F6077BE6BD7DAEDB627", + "id_indices": [ + 0, + 1 + ], + "pubshare_indices": [ + 0, + 1 + ], + "tweaks": [], + "is_xonly": [], + "msg_index": 2, + "signer_index": 0, + "expected": [ + "028BB5C9114C5991CF2DAE6CE6D6AA1274783BDFB30ED89AF0D64B8E4061015087021E5712184A72688ED52D54D02FCB3E28A695C2DAEC6BE9FE9AD894D958AF530A", + "853BF60CA2EB5D15B54D3B6986B763A9E84270CD68F0F8B9167C4AD801C0B86A" + ], + "comment": "Message longer than 32 bytes (38-byte msg)" + }, + { + "rand": "0000000000000000000000000000000000000000000000000000000000000000", + "aggothernonce": "0353BC2314D46C813AF81317AF1BDF99816B6444E416BB8D3DC04ACB2F5388D1AC02B13BC644F720223B547DB344C94E0F5E769B674D8A9C3F5E86A5231A5B9C3297", + "id_indices": [ + 0, + 1 + ], + "pubshare_indices": [ + 0, + 1 + ], + "tweaks": [ + "E8F791FF9225A2AF0102AFFF4A9A723D9612A682A25EBE79802B263CDFCD83BB" + ], + "is_xonly": [ + true + ], + "msg_index": 0, + "signer_index": 0, + "expected": [ + "03161BD943B39F07744FD2BE702132A051D194DD5F7B34CD7FC925FA70F35F56D8022F4597F6E0A96BB1DCA84EF68CC91AEC0EAB7A51EF0E9EE75079B36819FA7204", + "496B94BA355C529E60F728DB22F63C9AF64B8E87252AF6A22F7EC99AC788AAC9" + ], + "comment": "Signing with tweaks" + } + ], + "error_test_cases": [ + { + "rand": "0000000000000000000000000000000000000000000000000000000000000000", + "aggothernonce": "02FCDBEE416E4426FB4004BAB2B416164845DEC27337AD2B96184236D715965AB2039F71F389F6808DC6176F062F80531E13EA5BC2612B690FC284AE66C2CD859CE9", + "id_indices": [ + 2, + 1 + ], + "pubshare_indices": [ + 0, + 1 + ], + "tweaks": [], + "is_xonly": [], + "msg_index": 0, + "signer_index": null, + "signer_id": 0, + "error": { + "type": "ValueError", + "message": "The provided key material is incorrect." + }, + "comment": "The signer's id is not in the participant identifier list" + }, + { + "rand": "0000000000000000000000000000000000000000000000000000000000000000", + "aggothernonce": "02C1D8D5D95E15E4B46B49AF7A309520B0D07E5386995B99A572440C658FE443DF028A89044AE2FFC00131089E7B1EB15FE8DF52282F44D5EE2FA25F0DF437082407", + "id_indices": [ + 0, + 1, + 1 + ], + "pubshare_indices": [ + 0, + 1, + 1 + ], + "tweaks": [], + "is_xonly": [], + "msg_index": 0, + "signer_index": 0, + "error": { + "type": "ValueError", + "message": "The participant identifier list must contain unique elements." + }, + "comment": "The participant identifier list contains duplicate elements" + }, + { + "rand": "0000000000000000000000000000000000000000000000000000000000000000", + "aggothernonce": "0353BC2314D46C813AF81317AF1BDF99816B6444E416BB8D3DC04ACB2F5388D1AC02B13BC644F720223B547DB344C94E0F5E769B674D8A9C3F5E86A5231A5B9C3297", + "id_indices": [ + 0, + 1 + ], + "pubshare_indices": [ + 2, + 1 + ], + "tweaks": [], + "is_xonly": [], + "msg_index": 0, + "signer_index": 0, + "error": { + "type": "ValueError", + "message": "The provided key material is incorrect." + }, + "comment": "The signer's pubshare is not in the list of pubshares. This test case is optional: it can be skipped by implementations that do not check that the signer's pubshare is included in the list of pubshares." + }, + { + "rand": "0000000000000000000000000000000000000000000000000000000000000000", + "aggothernonce": "02FCDBEE416E4426FB4004BAB2B416164845DEC27337AD2B96184236D715965AB2039F71F389F6808DC6176F062F80531E13EA5BC2612B690FC284AE66C2CD859CE9", + "id_indices": [ + 0, + 1, + 2 + ], + "pubshare_indices": [ + 0, + 1 + ], + "tweaks": [], + "is_xonly": [], + "msg_index": 0, + "signer_index": 0, + "error": { + "type": "ValueError", + "message": "The pubshares and ids arrays must have the same length." + }, + "comment": "The participant identifiers count exceed the participant public shares count" + }, + { + "rand": "0000000000000000000000000000000000000000000000000000000000000000", + "aggothernonce": "0353BC2314D46C813AF81317AF1BDF99816B6444E416BB8D3DC04ACB2F5388D1AC02B13BC644F720223B547DB344C94E0F5E769B674D8A9C3F5E86A5231A5B9C3297", + "id_indices": [ + 0, + 1 + ], + "pubshare_indices": [ + 0, + 3 + ], + "tweaks": [], + "is_xonly": [], + "msg_index": 0, + "signer_index": 0, + "error": { + "type": "InvalidContributionError", + "id": 1, + "contrib": "pubshare" + }, + "comment": "Signer 1 provided an invalid participant public share" + }, + { + "rand": "0000000000000000000000000000000000000000000000000000000000000000", + "aggothernonce": "048465FCF0BBDBCF443AABCCE533D42B4B5A10966AC09A49655E8C42DAAB8FCD61037496A3CC86926D452CAFCFD55D25972CA1675D549310DE296BFF42F72EEEA8C9", + "id_indices": [ + 0, + 1 + ], + "pubshare_indices": [ + 0, + 1 + ], + "tweaks": [], + "is_xonly": [], + "msg_index": 0, + "signer_index": 0, + "error": { + "type": "InvalidContributionError", + "id": null, + "contrib": "aggothernonce" + }, + "comment": "aggothernonce is invalid due wrong tag, 0x04, in the first half" + }, + { + "rand": "0000000000000000000000000000000000000000000000000000000000000000", + "aggothernonce": "0000000000000000000000000000000000000000000000000000000000000000000287BF891D2A6DEAEBADC909352AA9405D1428C15F4B75F04DAE642A95C2548480", + "id_indices": [ + 0, + 1 + ], + "pubshare_indices": [ + 0, + 1 + ], + "tweaks": [], + "is_xonly": [], + "msg_index": 0, + "signer_index": 0, + "error": { + "type": "InvalidContributionError", + "id": null, + "contrib": "aggothernonce" + }, + "comment": "aggothernonce is invalid because first half corresponds to point at infinity" + }, + { + "rand": "0000000000000000000000000000000000000000000000000000000000000000", + "aggothernonce": "0353BC2314D46C813AF81317AF1BDF99816B6444E416BB8D3DC04ACB2F5388D1AC02B13BC644F720223B547DB344C94E0F5E769B674D8A9C3F5E86A5231A5B9C3297", + "id_indices": [ + 0, + 1 + ], + "pubshare_indices": [ + 0, + 1 + ], + "tweaks": [ + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141" + ], + "is_xonly": [ + false + ], + "msg_index": 0, + "signer_index": 0, + "error": { + "type": "ValueError", + "message": "The tweak must be less than n." + }, + "comment": "Tweak is invalid because it exceeds group size" + } + ] +} \ No newline at end of file diff --git a/bip-frost-signing/python/vectors/nonce_agg_vectors.json b/bip-frost-signing/python/vectors/nonce_agg_vectors.json new file mode 100644 index 0000000000..02064e8351 --- /dev/null +++ b/bip-frost-signing/python/vectors/nonce_agg_vectors.json @@ -0,0 +1,86 @@ +{ + "pubnonces": [ + "020151C80F435648DF67A22B749CD798CE54E0321D034B92B709B567D60A42E66603BA47FBC1834437B3212E89A84D8425E7BF12E0245D98262268EBDCB385D50641", + "03FF406FFD8ADB9CD29877E4985014F66A59F6CD01C0E88CAA8E5F3166B1F676A60248C264CDD57D3C24D79990B0F865674EB62A0F9018277A95011B41BFC193B833", + "020151C80F435648DF67A22B749CD798CE54E0321D034B92B709B567D60A42E6660279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798", + "03FF406FFD8ADB9CD29877E4985014F66A59F6CD01C0E88CAA8E5F3166B1F676A60379BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798", + "04FF406FFD8ADB9CD29877E4985014F66A59F6CD01C0E88CAA8E5F3166B1F676A60248C264CDD57D3C24D79990B0F865674EB62A0F9018277A95011B41BFC193B833", + "03FF406FFD8ADB9CD29877E4985014F66A59F6CD01C0E88CAA8E5F3166B1F676A60248C264CDD57D3C24D79990B0F865674EB62A0F9018277A95011B41BFC193B831", + "03FF406FFD8ADB9CD29877E4985014F66A59F6CD01C0E88CAA8E5F3166B1F676A602FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC30" + ], + "valid_test_cases": [ + { + "pubnonce_indices": [ + 0, + 1 + ], + "participant_identifiers": [ + 0, + 1 + ], + "expected_aggnonce": "035FE1873B4F2967F52FEA4A06AD5A8ECCBE9D0FD73068012C894E2E87CCB5804B024725377345BDE0E9C33AF3C43C0A29A9249F2F2956FA8CFEB55C8573D0262DC8" + }, + { + "pubnonce_indices": [ + 2, + 3 + ], + "participant_identifiers": [ + 0, + 1 + ], + "expected_aggnonce": "035FE1873B4F2967F52FEA4A06AD5A8ECCBE9D0FD73068012C894E2E87CCB5804B000000000000000000000000000000000000000000000000000000000000000000", + "comment": "Sum of second points encoded in the nonces is point at infinity which is serialized as 33 zero bytes" + } + ], + "error_test_cases": [ + { + "pubnonce_indices": [ + 0, + 4 + ], + "participant_identifiers": [ + 0, + 1 + ], + "error": { + "type": "InvalidContributionError", + "id": 1, + "contrib": "pubnonce" + }, + "comment": "Public nonce from signer 1 is invalid due wrong tag, 0x04, in the first half" + }, + { + "pubnonce_indices": [ + 5, + 1 + ], + "participant_identifiers": [ + 0, + 1 + ], + "error": { + "type": "InvalidContributionError", + "id": 0, + "contrib": "pubnonce" + }, + "comment": "Public nonce from signer 0 is invalid because the second half does not correspond to an X coordinate" + }, + { + "pubnonce_indices": [ + 6, + 1 + ], + "participant_identifiers": [ + 0, + 1 + ], + "error": { + "type": "InvalidContributionError", + "id": 0, + "contrib": "pubnonce" + }, + "comment": "Public nonce from signer 0 is invalid because second half exceeds field size" + } + ] +} \ No newline at end of file diff --git a/bip-frost-signing/python/vectors/nonce_gen_vectors.json b/bip-frost-signing/python/vectors/nonce_gen_vectors.json new file mode 100644 index 0000000000..066b34295b --- /dev/null +++ b/bip-frost-signing/python/vectors/nonce_gen_vectors.json @@ -0,0 +1,48 @@ +{ + "test_cases": [ + { + "rand_": "0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F", + "secshare": "CCD2EF4559DB05635091D80189AB3544D6668EFC0500A8D5FF51A1F4D32CC1F1", + "pubshare": "022B02109FBCFB4DA3F53C7393B22E72A2A51C4AFBF0C01AAF44F73843CFB4B74B", + "threshold_pubkey": "B02645D79ABFC494338139410F9D7F0A72BE86C952D6BDE1A66447B8A8D69237", + "msg": "0101010101010101010101010101010101010101010101010101010101010101", + "extra_in": "0808080808080808080808080808080808080808080808080808080808080808", + "expected_secnonce": "0CE17C117FFA8F4BC63E5A01250216F3AE329A212DDAB8ACC146E7EBA5580E7AC176FB8455FEA8C93C29E2A1F572E2998CC316C8685534EFDC9656193D7B6E1B", + "expected_pubnonce": "023F8BD569195D02FA2F70CD9B0BA611E32E0CE984A1BBB0E739F4B4AFBB08CCE902D692CE44EBEE93F9EC8E1503F7DE0D4A926178321A5DAF9AB41E1EA89E8F88E8", + "comment": "" + }, + { + "rand_": "0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F", + "secshare": "CCD2EF4559DB05635091D80189AB3544D6668EFC0500A8D5FF51A1F4D32CC1F1", + "pubshare": "022B02109FBCFB4DA3F53C7393B22E72A2A51C4AFBF0C01AAF44F73843CFB4B74B", + "threshold_pubkey": "B02645D79ABFC494338139410F9D7F0A72BE86C952D6BDE1A66447B8A8D69237", + "msg": "", + "extra_in": "0808080808080808080808080808080808080808080808080808080808080808", + "expected_secnonce": "2889110F994409F9F7AB5EFEDAC0F0D20C0083A3BD174F80E1C67B86127A4DE46600C968FFA50DE5B5D9EDE17081930C018E9D937A16C673889587817D4B796F", + "expected_pubnonce": "03B98FAFF2B60C56DC584AF1E7EEBDE6B300B514557EA627208608BA69E386C94E021A423749CA1C3B06E063C7F9DA9A894C9CC2D8B7C927F6605CEC7ED638B127F2", + "comment": "Empty Message" + }, + { + "rand_": "0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F", + "secshare": "CCD2EF4559DB05635091D80189AB3544D6668EFC0500A8D5FF51A1F4D32CC1F1", + "pubshare": "022B02109FBCFB4DA3F53C7393B22E72A2A51C4AFBF0C01AAF44F73843CFB4B74B", + "threshold_pubkey": "B02645D79ABFC494338139410F9D7F0A72BE86C952D6BDE1A66447B8A8D69237", + "msg": "2626262626262626262626262626262626262626262626262626262626262626262626262626", + "extra_in": "0808080808080808080808080808080808080808080808080808080808080808", + "expected_secnonce": "A9841123FC252ED4AA0239B0138B973B18300B6F527F29CF450EAE53BF3E4A96A7B2E49AB947C13B79103C29C12B2B991DBDAE5A04485AE2F1F56FCD3B6AA35E", + "expected_pubnonce": "02EE71AAF852F44EEBD7FAB088F37A8D5904D421F1DB0428E64E58FE67E1CD76FA021DDE4E68AAC676E36CBD98C0F86822A77EAE67B904A76BD41864898985F4FB1C", + "comment": "38-byte message" + }, + { + "rand_": "0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F", + "secshare": null, + "pubshare": null, + "threshold_pubkey": null, + "msg": null, + "extra_in": null, + "expected_secnonce": "E8E239B64F9A4D2B03508C029EEFC8156A3AD899FD58B15759C93C7DA745C3550FABE3F7CDD361407B97C1353056310D1610D478633C5DDE04DEC4917591D2E5", + "expected_pubnonce": "0399059E50AF7B23F89E1ED7B17A7B24F2D746C663057F6C3B696A416C99C7A1070383C53B9CF236EADF8BDFEB1C3E9A188A1A84190687CD67916DF9BC60CD2D80EC", + "comment": "Every optional parameter is absent" + } + ] +} \ No newline at end of file diff --git a/bip-frost-signing/python/vectors/sig_agg_vectors.json b/bip-frost-signing/python/vectors/sig_agg_vectors.json new file mode 100644 index 0000000000..4ce35ddf85 --- /dev/null +++ b/bip-frost-signing/python/vectors/sig_agg_vectors.json @@ -0,0 +1,186 @@ +{ + "n": 3, + "t": 2, + "threshold_pubkey": "03B02645D79ABFC494338139410F9D7F0A72BE86C952D6BDE1A66447B8A8D69237", + "identifiers": [ + 0, + 1, + 2 + ], + "pubshares": [ + "022B02109FBCFB4DA3F53C7393B22E72A2A51C4AFBF0C01AAF44F73843CFB4B74B", + "02EC6444271D791A1DA95300329DB2268611B9C60E193DABFDEE0AA816AE512583", + "03113F810F612567D9552F46AF9BDA21A67D52060F95BD4A723F4B60B1820D3676" + ], + "pubnonces": [ + "0330935948101543C50AF2FA7A7A4F7073CEB73290CA141497EF06E0269363162D0358785EB5CD7C1626CAB55C59B484E1B3147FA4EB919224ECB04BAB1271022A5C", + "0244D225137BC9390069C9D4D230B6D0942A1A3D72678B638B81F3416B6FEA719C02B1C7E637FD51FE2BC2C91CB6ACA0EA6A8BB30A33A0589D369687EAA33BFC5FA8", + "0332DAA54E451217D6F14747B72634D1E9E21B247C8E92397ABFEE296BD714772403FE1674C2B2B8076D641CEC4B2E6DF054C3D60AA77352A55233B40AC12046C312" + ], + "tweaks": [ + "B511DA492182A91B0FFB9A98020D55F260AE86D7ECBD0399C7383D59A5F2AF7C", + "A815FE049EE3C5AAB66310477FBC8BCCCAC2F3395F59F921C364ACD78A2F48DC", + "75448A87274B056468B977BE06EB1E9F657577B7320B0A3376EA51FD420D18A8" + ], + "msg": "599C67EA410D005B9DA90817CF03ED3B1C868E4DA4EDF00A5880B0082C237869", + "valid_test_cases": [ + { + "id_indices": [ + 0, + 1 + ], + "pubshare_indices": [ + 0, + 1 + ], + "pubnonce_indices": [ + 0, + 1 + ], + "aggnonce": "022AAC6A4960DE35FC36D8E2DC06255C5CB7FD28250DFD84EBF1AC943B1EA22C3502178AD06BB0490BAD857446FEF55C15FD9FF4329F4EE2F23CA8B7CA0598014910", + "tweak_indices": [], + "is_xonly": [], + "psigs": [ + "09CA21FA4AE22BBB49EE5ABC091519C8695FB74FC3D39C1437019EF6FE6C7AB4", + "D0750AE90E8475A6E984AE9159E0C4510851672D6D5C26260F643448FF0AC3EB" + ], + "expected": "90495647B268390A2ABA2518AEEEF699620C783EF21DF2800BB3C38CAE6755C7DA3F2CE35966A1623373094D62F5DE1971B11E7D312FC23A4665D33FFD773E9F", + "comment": "Signing with minimum number of participants" + }, + { + "id_indices": [ + 1, + 0 + ], + "pubshare_indices": [ + 1, + 0 + ], + "pubnonce_indices": [ + 1, + 0 + ], + "aggnonce": "022AAC6A4960DE35FC36D8E2DC06255C5CB7FD28250DFD84EBF1AC943B1EA22C3502178AD06BB0490BAD857446FEF55C15FD9FF4329F4EE2F23CA8B7CA0598014910", + "tweak_indices": [], + "is_xonly": [], + "psigs": [ + "D0750AE90E8475A6E984AE9159E0C4510851672D6D5C26260F643448FF0AC3EB", + "09CA21FA4AE22BBB49EE5ABC091519C8695FB74FC3D39C1437019EF6FE6C7AB4" + ], + "expected": "90495647B268390A2ABA2518AEEEF699620C783EF21DF2800BB3C38CAE6755C7DA3F2CE35966A1623373094D62F5DE1971B11E7D312FC23A4665D33FFD773E9F", + "comment": "Order of the singer set shouldn't affect the aggregate signature. The expected value must match the previous test vector." + }, + { + "id_indices": [ + 0, + 1 + ], + "pubshare_indices": [ + 0, + 1 + ], + "pubnonce_indices": [ + 0, + 1 + ], + "aggnonce": "022AAC6A4960DE35FC36D8E2DC06255C5CB7FD28250DFD84EBF1AC943B1EA22C3502178AD06BB0490BAD857446FEF55C15FD9FF4329F4EE2F23CA8B7CA0598014910", + "tweak_indices": [ + 0, + 1, + 2 + ], + "is_xonly": [ + true, + false, + false + ], + "psigs": [ + "19689B0B3F32C7696FF2C90CF1E6DC8311F96EC7D6307EF56085C78C3FF6C828", + "B7290A65BE76B17C8756D77EF0395D5F219CDF7EA8D63886132C9011938F241C" + ], + "expected": "7430808B048AD9EAA6240B047B790CF24DB878DC3AC31C1B528289ED3AA70C067CA7AA1E418135268A336415D4B634E1F845A981F21B69FD3ADDF7D81D41FAB8", + "comment": "Signing with tweaked threshold public key" + }, + { + "id_indices": [ + 0, + 1, + 2 + ], + "pubshare_indices": [ + 0, + 1, + 2 + ], + "pubnonce_indices": [ + 0, + 1, + 2 + ], + "aggnonce": "0282E58B733AB1B74D1C54B960D668E4298C3EF9F406D44249FB30C403568A1B14039B38604D0FD33E07C3EB81BBDAE39A38A82A1D9E325112A24F0F5480582C9CEE", + "tweak_indices": [], + "is_xonly": [], + "psigs": [ + "13036985653E36780C30AFB253D9E506EA2FD5E30439C507D337DA4062898066", + "C665E007CC6D1294105580FC46764C8F49D1A44DE9D574F5C3DF72E6636B383E", + "F5352AB8BD2C34F466ACF16046A222DAFB45FBF6327AC4E8EE6C10C8FED9EF33" + ], + "expected": "553247A24AF9A15C4D495968C0467FE6651F3584FDD671CBAE05BBC850577DD8CE9E7445EED77E008333220EE0F254727498994071415EAAC5B0FF62F4986696", + "comment": "Signing with max number of participants and tweaked threshold public key" + } + ], + "error_test_cases": [ + { + "id_indices": [ + 0, + 1 + ], + "pubshare_indices": [ + 0, + 1 + ], + "pubnonce_indices": [ + 0, + 1 + ], + "aggnonce": "022AAC6A4960DE35FC36D8E2DC06255C5CB7FD28250DFD84EBF1AC943B1EA22C3502178AD06BB0490BAD857446FEF55C15FD9FF4329F4EE2F23CA8B7CA0598014910", + "tweak_indices": [], + "is_xonly": [], + "psigs": [ + "09CA21FA4AE22BBB49EE5ABC091519C8695FB74FC3D39C1437019EF6FE6C7AB4", + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141" + ], + "error": { + "type": "InvalidContributionError", + "id": 1, + "contrib": "psig" + }, + "comment": "Partial signature is invalid because it exceeds group size" + }, + { + "id_indices": [ + 0, + 1 + ], + "pubshare_indices": [ + 0, + 1 + ], + "pubnonce_indices": [ + 0, + 1 + ], + "aggnonce": "022AAC6A4960DE35FC36D8E2DC06255C5CB7FD28250DFD84EBF1AC943B1EA22C3502178AD06BB0490BAD857446FEF55C15FD9FF4329F4EE2F23CA8B7CA0598014910", + "tweak_indices": [], + "is_xonly": [], + "psigs": [ + "09CA21FA4AE22BBB49EE5ABC091519C8695FB74FC3D39C1437019EF6FE6C7AB4" + ], + "error": { + "type": "ValueError", + "message": "The psigs and ids arrays must have the same length." + }, + "comment": "Partial signature count doesn't match the signer set count" + } + ] +} \ No newline at end of file diff --git a/bip-frost-signing/python/vectors/sign_verify_vectors.json b/bip-frost-signing/python/vectors/sign_verify_vectors.json new file mode 100644 index 0000000000..ea0a0231ab --- /dev/null +++ b/bip-frost-signing/python/vectors/sign_verify_vectors.json @@ -0,0 +1,509 @@ +{ + "n": 3, + "t": 2, + "threshold_pubkey": "03B02645D79ABFC494338139410F9D7F0A72BE86C952D6BDE1A66447B8A8D69237", + "secshare_p0": "CCD2EF4559DB05635091D80189AB3544D6668EFC0500A8D5FF51A1F4D32CC1F1", + "identifiers": [ + 0, + 1, + 2 + ], + "pubshares": [ + "022B02109FBCFB4DA3F53C7393B22E72A2A51C4AFBF0C01AAF44F73843CFB4B74B", + "02EC6444271D791A1DA95300329DB2268611B9C60E193DABFDEE0AA816AE512583", + "03113F810F612567D9552F46AF9BDA21A67D52060F95BD4A723F4B60B1820D3676", + "020000000000000000000000000000000000000000000000000000000000000007" + ], + "secnonces_p0": [ + "DB26CEB14C1CF111274574860A4667E3305B9C8D47E48861562445CF2E7D2277D17751A6F6972FD753CF2B2784CF5193ADBEA4DA066526D0A9984E9C1C07179F", + "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + ], + "pubnonces": [ + "0330935948101543C50AF2FA7A7A4F7073CEB73290CA141497EF06E0269363162D0358785EB5CD7C1626CAB55C59B484E1B3147FA4EB919224ECB04BAB1271022A5C", + "0244D225137BC9390069C9D4D230B6D0942A1A3D72678B638B81F3416B6FEA719C02B1C7E637FD51FE2BC2C91CB6ACA0EA6A8BB30A33A0589D369687EAA33BFC5FA8", + "0332DAA54E451217D6F14747B72634D1E9E21B247C8E92397ABFEE296BD714772403FE1674C2B2B8076D641CEC4B2E6DF054C3D60AA77352A55233B40AC12046C312", + "0200000000000000000000000000000000000000000000000000000000000000090287BF891D2A6DEAEBADC909352AA9405D1428C15F4B75F04DAE642A95C2548480", + "032AAC6A4960DE35FC36D8E2DC06255C5CB7FD28250DFD84EBF1AC943B1EA22C3503178AD06BB0490BAD857446FEF55C15FD9FF4329F4EE2F23CA8B7CA0598014910" + ], + "aggnonces": [ + "022AAC6A4960DE35FC36D8E2DC06255C5CB7FD28250DFD84EBF1AC943B1EA22C3502178AD06BB0490BAD857446FEF55C15FD9FF4329F4EE2F23CA8B7CA0598014910", + "020C800916841C67A0261D280120246C2DB4DB930FD6963633DC9B59F026B0EA4B03E5178080C6B5F53C3EE78297D518969CF21DDD7FDAF7C861F7BDD2C48532A6CA", + "0282E58B733AB1B74D1C54B960D668E4298C3EF9F406D44249FB30C403568A1B14039B38604D0FD33E07C3EB81BBDAE39A38A82A1D9E325112A24F0F5480582C9CEE", + "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "048465FCF0BBDBCF443AABCCE533D42B4B5A10966AC09A49655E8C42DAAB8FCD61037496A3CC86926D452CAFCFD55D25972CA1675D549310DE296BFF42F72EEEA8C9", + "028465FCF0BBDBCF443AABCCE533D42B4B5A10966AC09A49655E8C42DAAB8FCD61020000000000000000000000000000000000000000000000000000000000000009", + "028465FCF0BBDBCF443AABCCE533D42B4B5A10966AC09A49655E8C42DAAB8FCD6102FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC30" + ], + "msgs": [ + "F95466D086770E689964664219266FE5ED215C92AE20BAB5C9D79ADDDDF3C0CF", + "", + "2626262626262626262626262626262626262626262626262626262626262626262626262626" + ], + "valid_test_cases": [ + { + "id_indices": [ + 0, + 1 + ], + "pubshare_indices": [ + 0, + 1 + ], + "pubnonce_indices": [ + 0, + 1 + ], + "aggnonce_index": 0, + "msg_index": 0, + "signer_index": 0, + "expected": "79DEFC679CF419BA7C48AF8F9526B5D510AAA4115B04DECDDE10CE9E06105334", + "comment": "Signing with minimum number of participants" + }, + { + "id_indices": [ + 1, + 0 + ], + "pubshare_indices": [ + 1, + 0 + ], + "pubnonce_indices": [ + 1, + 0 + ], + "aggnonce_index": 0, + "msg_index": 0, + "signer_index": 1, + "expected": "79DEFC679CF419BA7C48AF8F9526B5D510AAA4115B04DECDDE10CE9E06105334", + "comment": "Partial-signature doesn't change if the order of signers set changes (without changing secnonces)" + }, + { + "id_indices": [ + 0, + 2 + ], + "pubshare_indices": [ + 0, + 2 + ], + "pubnonce_indices": [ + 0, + 2 + ], + "aggnonce_index": 1, + "msg_index": 0, + "signer_index": 0, + "expected": "39C84732FA3A9EBAF568AB269A18632E8CD62EEE3FE39F0799071FEFB71CAFC2", + "comment": "Partial-signature changes if the members of signers set changes" + }, + { + "id_indices": [ + 0, + 1, + 2 + ], + "pubshare_indices": [ + 0, + 1, + 2 + ], + "pubnonce_indices": [ + 0, + 1, + 2 + ], + "aggnonce_index": 2, + "msg_index": 0, + "signer_index": 0, + "expected": "304337D1F0BEF7F3FAFB8F5D165F77D6F511CEA3E0375F9B7EF5579A2314004E", + "comment": "Signing with max number of participants" + }, + { + "id_indices": [ + 0, + 1, + 2 + ], + "pubshare_indices": [ + 0, + 1, + 2 + ], + "pubnonce_indices": [ + 0, + 1, + 4 + ], + "aggnonce_index": 3, + "msg_index": 0, + "signer_index": 0, + "expected": "F6A823D47F626D7106D65F7F83AAB8B4C4E2B7DC1CC036A66C747786659FED8D", + "comment": "Both halves of aggregate nonce correspond to point at infinity" + }, + { + "id_indices": [ + 0, + 1 + ], + "pubshare_indices": [ + 0, + 1 + ], + "pubnonce_indices": [ + 0, + 1 + ], + "aggnonce_index": 0, + "msg_index": 1, + "signer_index": 0, + "expected": "679F7A5282BC979090AA7167499C27F64BDB562A791AE60DEBAF40F01E7CED40", + "comment": "Empty message" + }, + { + "id_indices": [ + 0, + 1 + ], + "pubshare_indices": [ + 0, + 1 + ], + "pubnonce_indices": [ + 0, + 1 + ], + "aggnonce_index": 0, + "msg_index": 2, + "signer_index": 0, + "expected": "FBE3883C9C8D3CA5010A649689CBB54BC429A58CBD4CD2B3347629558536EBD6", + "comment": "Message longer than 32 bytes (38-byte msg)" + } + ], + "sign_error_test_cases": [ + { + "id_indices": [ + 2, + 1 + ], + "pubshare_indices": [ + 0, + 1 + ], + "aggnonce_index": 0, + "msg_index": 0, + "signer_index": null, + "signer_id": 0, + "secnonce_index": 0, + "error": { + "type": "ValueError", + "message": "The provided key material is incorrect." + }, + "comment": "The signer's id is not in the participant identifier list" + }, + { + "id_indices": [ + 0, + 1, + 1 + ], + "pubshare_indices": [ + 0, + 1, + 1 + ], + "aggnonce_index": 0, + "msg_index": 0, + "signer_index": 0, + "secnonce_index": 0, + "error": { + "type": "ValueError", + "message": "The participant identifier list must contain unique elements." + }, + "comment": "The participant identifier list contains duplicate elements" + }, + { + "id_indices": [ + 0, + 1 + ], + "pubshare_indices": [ + 2, + 1 + ], + "aggnonce_index": 0, + "msg_index": 0, + "signer_index": 0, + "secnonce_index": 0, + "error": { + "type": "ValueError", + "message": "The provided key material is incorrect." + }, + "comment": "The signer's pubshare is not in the list of pubshares. This test case is optional: it can be skipped by implementations that do not check that the signer's pubshare is included in the list of pubshares." + }, + { + "id_indices": [ + 0, + 1, + 2 + ], + "pubshare_indices": [ + 0, + 1 + ], + "aggnonce_index": 0, + "msg_index": 0, + "signer_index": 0, + "secnonce_index": 0, + "error": { + "type": "ValueError", + "message": "The pubshares and ids arrays must have the same length." + }, + "comment": "The participant identifiers count exceed the participant public shares count" + }, + { + "id_indices": [ + 0, + 1 + ], + "pubshare_indices": [ + 0, + 3 + ], + "aggnonce_index": 0, + "msg_index": 0, + "signer_index": 0, + "secnonce_index": 0, + "error": { + "type": "InvalidContributionError", + "id": 1, + "contrib": "pubshare" + }, + "comment": "Signer 1 provided an invalid participant public share" + }, + { + "id_indices": [ + 0, + 1 + ], + "pubshare_indices": [ + 0, + 1 + ], + "aggnonce_index": 4, + "msg_index": 0, + "signer_index": 0, + "secnonce_index": 0, + "error": { + "type": "InvalidContributionError", + "id": null, + "contrib": "aggnonce" + }, + "comment": "Aggregate nonce is invalid due wrong tag, 0x04, in the first half" + }, + { + "id_indices": [ + 0, + 1 + ], + "pubshare_indices": [ + 0, + 1 + ], + "aggnonce_index": 5, + "msg_index": 0, + "signer_index": 0, + "secnonce_index": 0, + "error": { + "type": "InvalidContributionError", + "id": null, + "contrib": "aggnonce" + }, + "comment": "Aggregate nonce is invalid because the second half does not correspond to an X coordinate" + }, + { + "id_indices": [ + 0, + 1 + ], + "pubshare_indices": [ + 0, + 1 + ], + "aggnonce_index": 6, + "msg_index": 0, + "signer_index": 0, + "secnonce_index": 0, + "error": { + "type": "InvalidContributionError", + "id": null, + "contrib": "aggnonce" + }, + "comment": "Aggregate nonce is invalid because second half exceeds field size" + }, + { + "id_indices": [ + 0, + 1 + ], + "pubshare_indices": [ + 0, + 1 + ], + "aggnonce_index": 0, + "msg_index": 0, + "signer_index": 0, + "secnonce_index": 1, + "error": { + "type": "ValueError", + "message": "first secnonce value is out of range." + }, + "comment": "Secnonce is invalid which may indicate nonce reuse" + } + ], + "verify_fail_test_cases": [ + { + "psig": "86210398630BE64583B750706AD94A29AA0438D55443C16DE1C18FEECA25EE0D", + "id_indices": [ + 0, + 1 + ], + "pubshare_indices": [ + 0, + 1 + ], + "pubnonce_indices": [ + 0, + 1 + ], + "msg_index": 0, + "signer_index": 0, + "comment": "Wrong signature (which is equal to the negation of valid signature)" + }, + { + "psig": "79DEFC679CF419BA7C48AF8F9526B5D510AAA4115B04DECDDE10CE9E06105334", + "id_indices": [ + 0, + 1 + ], + "pubshare_indices": [ + 0, + 1 + ], + "pubnonce_indices": [ + 0, + 1 + ], + "msg_index": 0, + "signer_index": 1, + "comment": "Wrong signer index" + }, + { + "psig": "79DEFC679CF419BA7C48AF8F9526B5D510AAA4115B04DECDDE10CE9E06105334", + "id_indices": [ + 0, + 1 + ], + "pubshare_indices": [ + 2, + 1 + ], + "pubnonce_indices": [ + 0, + 1 + ], + "msg_index": 0, + "signer_index": 0, + "comment": "The signer's pubshare is not in the list of pubshares" + }, + { + "psig": "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", + "id_indices": [ + 0, + 1 + ], + "pubshare_indices": [ + 0, + 1 + ], + "pubnonce_indices": [ + 0, + 1 + ], + "msg_index": 0, + "signer_index": 0, + "comment": "Signature value is out of range" + } + ], + "verify_error_test_cases": [ + { + "psig": "79DEFC679CF419BA7C48AF8F9526B5D510AAA4115B04DECDDE10CE9E06105334", + "id_indices": [ + 0, + 1 + ], + "pubshare_indices": [ + 0, + 1 + ], + "pubnonce_indices": [ + 3, + 1 + ], + "msg_index": 0, + "signer_index": 0, + "error": { + "type": "InvalidContributionError", + "id": 0, + "contrib": "pubnonce" + }, + "comment": "Invalid pubnonce" + }, + { + "psig": "79DEFC679CF419BA7C48AF8F9526B5D510AAA4115B04DECDDE10CE9E06105334", + "id_indices": [ + 0, + 1 + ], + "pubshare_indices": [ + 3, + 1 + ], + "pubnonce_indices": [ + 0, + 1 + ], + "msg_index": 0, + "signer_index": 0, + "error": { + "type": "InvalidContributionError", + "id": 0, + "contrib": "pubshare" + }, + "comment": "Invalid pubshare" + }, + { + "psig": "79DEFC679CF419BA7C48AF8F9526B5D510AAA4115B04DECDDE10CE9E06105334", + "id_indices": [ + 0, + 1 + ], + "pubshare_indices": [ + 0, + 1 + ], + "pubnonce_indices": [ + 0, + 1, + 2 + ], + "msg_index": 0, + "signer_index": 0, + "error": { + "type": "ValueError", + "message": "The pubnonces and ids arrays must have the same length." + }, + "comment": "public nonces count is greater than ids and pubshares" + } + ] +} \ No newline at end of file diff --git a/bip-frost-signing/python/vectors/tweak_vectors.json b/bip-frost-signing/python/vectors/tweak_vectors.json new file mode 100644 index 0000000000..87e6d89ad9 --- /dev/null +++ b/bip-frost-signing/python/vectors/tweak_vectors.json @@ -0,0 +1,277 @@ +{ + "n": 3, + "t": 2, + "threshold_pubkey": "03B02645D79ABFC494338139410F9D7F0A72BE86C952D6BDE1A66447B8A8D69237", + "secshare_p0": "CCD2EF4559DB05635091D80189AB3544D6668EFC0500A8D5FF51A1F4D32CC1F1", + "identifiers": [ + 0, + 1, + 2 + ], + "pubshares": [ + "022B02109FBCFB4DA3F53C7393B22E72A2A51C4AFBF0C01AAF44F73843CFB4B74B", + "02EC6444271D791A1DA95300329DB2268611B9C60E193DABFDEE0AA816AE512583", + "03113F810F612567D9552F46AF9BDA21A67D52060F95BD4A723F4B60B1820D3676", + "020000000000000000000000000000000000000000000000000000000000000007" + ], + "secnonce_p0": "DB26CEB14C1CF111274574860A4667E3305B9C8D47E48861562445CF2E7D2277D17751A6F6972FD753CF2B2784CF5193ADBEA4DA066526D0A9984E9C1C07179F", + "pubnonces": [ + "0330935948101543C50AF2FA7A7A4F7073CEB73290CA141497EF06E0269363162D0358785EB5CD7C1626CAB55C59B484E1B3147FA4EB919224ECB04BAB1271022A5C", + "0244D225137BC9390069C9D4D230B6D0942A1A3D72678B638B81F3416B6FEA719C02B1C7E637FD51FE2BC2C91CB6ACA0EA6A8BB30A33A0589D369687EAA33BFC5FA8", + "0332DAA54E451217D6F14747B72634D1E9E21B247C8E92397ABFEE296BD714772403FE1674C2B2B8076D641CEC4B2E6DF054C3D60AA77352A55233B40AC12046C312" + ], + "aggnonces": [ + "022AAC6A4960DE35FC36D8E2DC06255C5CB7FD28250DFD84EBF1AC943B1EA22C3502178AD06BB0490BAD857446FEF55C15FD9FF4329F4EE2F23CA8B7CA0598014910", + "0282E58B733AB1B74D1C54B960D668E4298C3EF9F406D44249FB30C403568A1B14039B38604D0FD33E07C3EB81BBDAE39A38A82A1D9E325112A24F0F5480582C9CEE", + "0282E58B733AB1B74D1C54B960D668E4298C3EF9F406D44249FB30C403568A1B14039B38604D0FD33E07C3EB81BBDAE39A38A82A1D9E325112A24F0F5480582C9CEE" + ], + "tweaks": [ + "E8F791FF9225A2AF0102AFFF4A9A723D9612A682A25EBE79802B263CDFCD83BB", + "AE2EA797CC0FE72AC5B97B97F3C6957D7E4199A167A58EB08BCAFFDA70AC0455", + "F52ECBC565B3D8BEA2DFD5B75A4F457E54369809322E4120831626F290FA87E0", + "1969AD73CC177FA0B4FCED6DF1F7BF9907E665FDE9BA196A74FED0A3CF5AEF9D", + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141" + ], + "msg": "F95466D086770E689964664219266FE5ED215C92AE20BAB5C9D79ADDDDF3C0CF", + "valid_test_cases": [ + { + "id_indices": [ + 0, + 1 + ], + "pubshare_indices": [ + 0, + 1 + ], + "pubnonce_indices": [ + 0, + 1 + ], + "tweak_indices": [], + "aggnonce_index": 0, + "is_xonly": [], + "signer_index": 0, + "expected": "79DEFC679CF419BA7C48AF8F9526B5D510AAA4115B04DECDDE10CE9E06105334", + "comment": "No tweak" + }, + { + "id_indices": [ + 0, + 1 + ], + "pubshare_indices": [ + 0, + 1 + ], + "pubnonce_indices": [ + 0, + 1 + ], + "tweak_indices": [ + 0 + ], + "aggnonce_index": 0, + "is_xonly": [ + true + ], + "signer_index": 0, + "expected": "EED33B9C85BF813CDBE4404DE8E5ACBD8FBF34D53768B45EB21CB8B653F67EA9", + "comment": "A single x-only tweak" + }, + { + "id_indices": [ + 0, + 1 + ], + "pubshare_indices": [ + 0, + 1 + ], + "pubnonce_indices": [ + 0, + 1 + ], + "tweak_indices": [ + 0 + ], + "aggnonce_index": 0, + "is_xonly": [ + false + ], + "signer_index": 0, + "expected": "65AA345505968E08C2040BC4F726AA5A382A2E578E8E580719E44B2768975B97", + "comment": "A single plain tweak" + }, + { + "id_indices": [ + 0, + 1 + ], + "pubshare_indices": [ + 0, + 1 + ], + "pubnonce_indices": [ + 0, + 1 + ], + "tweak_indices": [ + 0, + 1 + ], + "aggnonce_index": 0, + "is_xonly": [ + false, + true + ], + "signer_index": 0, + "expected": "10EB2D7E0E1BC46B42C54E739DECCA0BA8CB819D11AF5986CB47B8AF2FA00C91", + "comment": "A plain tweak followed by an x-only tweak" + }, + { + "id_indices": [ + 0, + 1 + ], + "pubshare_indices": [ + 0, + 1 + ], + "pubnonce_indices": [ + 0, + 1 + ], + "tweak_indices": [ + 0, + 1, + 2, + 3 + ], + "aggnonce_index": 0, + "is_xonly": [ + true, + false, + true, + false + ], + "signer_index": 0, + "expected": "BF9CD5C5558915E44DDBE6399EBFE824224AB23D331173BE90664ACFA7E2C6EF", + "comment": "Four tweaks: x-only, plain, x-only, plain. If an implementation prohibits applying plain tweaks after x-only tweaks, it can skip this test vector or return an error" + }, + { + "id_indices": [ + 0, + 1 + ], + "pubshare_indices": [ + 0, + 1 + ], + "pubnonce_indices": [ + 0, + 1 + ], + "tweak_indices": [ + 0, + 1, + 2, + 3 + ], + "aggnonce_index": 0, + "is_xonly": [ + false, + false, + true, + true + ], + "signer_index": 0, + "expected": "8789A8C4198D125220F693C837CA5896101C4F46D84769E38D284BDD462FDB25", + "comment": "Four tweaks: plain, plain, x-only, x-only" + }, + { + "id_indices": [ + 0, + 1, + 2 + ], + "pubshare_indices": [ + 0, + 1, + 2 + ], + "pubnonce_indices": [ + 0, + 1, + 2 + ], + "tweak_indices": [ + 0, + 1, + 2, + 3 + ], + "aggnonce_index": 1, + "is_xonly": [ + false, + false, + true, + true + ], + "signer_index": 0, + "expected": "F114B99483FCAF269A955479F60D1AE63CF74FA15330374D2DF8523930B1558A", + "comment": "Tweaking with max number of participants. The expected value (partial sig) must match the previous test vector" + } + ], + "error_test_cases": [ + { + "id_indices": [ + 0, + 1 + ], + "pubshare_indices": [ + 0, + 1 + ], + "tweak_indices": [ + 4 + ], + "aggnonce_index": 0, + "is_xonly": [ + false + ], + "signer_index": 0, + "error": { + "type": "ValueError", + "message": "The tweak must be less than n." + }, + "comment": "Tweak is invalid because it exceeds group size" + }, + { + "id_indices": [ + 0, + 1 + ], + "pubshare_indices": [ + 0, + 1 + ], + "tweak_indices": [ + 0, + 1, + 2, + 3 + ], + "aggnonce_index": 0, + "is_xonly": [ + true, + false + ], + "signer_index": 0, + "error": { + "type": "ValueError", + "message": "The tweaks and is_xonly arrays must have the same length." + }, + "comment": "Tweaks count doesn't match the tweak modes count" + } + ] +} \ No newline at end of file From ec46a20323840b1a6aba83bc2d18b34dd0811245 Mon Sep 17 00:00:00 2001 From: siv2r Date: Thu, 8 Jan 2026 12:41:19 +0530 Subject: [PATCH 2/6] bip-frost-signing: fix typos, preamble, and title --- bip-frost-signing.md | 58 +++-------------- bip-frost-signing/.markdownlint.json | 3 +- bip-frost-signing/docs/partialsig_forgery.md | 63 ------------------- .../src/secp256k1lab/secp256k1.pyi | 2 +- 4 files changed, 12 insertions(+), 114 deletions(-) delete mode 100644 bip-frost-signing/docs/partialsig_forgery.md diff --git a/bip-frost-signing.md b/bip-frost-signing.md index af109a5cb6..1f24febdff 100644 --- a/bip-frost-signing.md +++ b/bip-frost-signing.md @@ -1,56 +1,16 @@ -```yaml -BIP: -Title: FROST Signing Protocol for BIP340 Schnorr Signatures +``` +BIP: ? +Title: FROST Signing Protocol for BIP340 Signatures Author: Sivaram Dhakshinamoorthy +Comments-URI: Status: Draft -License: CC0-1.0 -License-Code: MIT -Type: Informational -Created: +Type: Standards Track +Assigned: ? +License: CC0-1.0 or MIT Post-History: https://groups.google.com/g/bitcoindev/c/PeMp2HQl-H4/m/AcJtK0aKAwAJ -Comments-URI: +Requires: 32, 340, 341 ``` -- [Abstract](#abstract) -- [Copyright](#copyright) -- [Motivation](#motivation) -- [Overview](#overview) - - [Optionality of Features](#optionality-of-features) - - [Key Material and Setup](#key-material-and-setup) - - [Protocol Parties and Network Setup](#protocol-parties-and-network-setup) - - [Signing Inputs and Outputs](#signing-inputs-and-outputs) - - [General Signing Flow](#general-signing-flow) - - [Nonce Generation](#nonce-generation) - - [Identifying Disruptive Signers](#identifying-disruptive-signers) - - [Further Remarks](#further-remarks) - - [Tweaking the Threshold Public Key](#tweaking-the-threshold-public-key) -- [Algorithms](#algorithms) - - [Notation](#notation) - - [Cryptographic Types and Operations](#cryptographic-types-and-operations) - - [Auxiliary and Byte-string Operations](#auxiliary-and-byte-string-operations) - - [Key Material and Setup](#key-material-and-setup-1) - - [Signers Context](#signers-context) - - [Tweaking the Threshold Public Key](#tweaking-the-threshold-public-key-1) - - [Tweak Context](#tweak-context) - - [Applying Tweaks](#applying-tweaks) - - [Nonce Generation](#nonce-generation-1) - - [Nonce Aggregation](#nonce-aggregation) - - [Session Context](#session-context) - - [Signing](#signing) - - [Partial Signature Verification](#partial-signature-verification) - - [Partial Signature Aggregation](#partial-signature-aggregation) - - [Test Vectors \& Reference Code](#test-vectors--reference-code) -- [Remarks on Security and Correctness](#remarks-on-security-and-correctness) - - [Modifications to Nonce Generation](#modifications-to-nonce-generation) - - [Deterministic and Stateless Signing for a Single Signer](#deterministic-and-stateless-signing-for-a-single-signer) - - [Tweaking Definition](#tweaking-definition) - - [Negation of the Secret Share when Signing](#negation-of-the-secret-share-when-signing) - - [Negation of the Pubshare when Partially Verifying](#negation-of-the-pubshare-when-partially-verifying) - - [Dealing with Infinity in Nonce Aggregation](#dealing-with-infinity-in-nonce-aggregation) -- [Backwards Compatibility](#backwards-compatibility) -- [Changelog](#changelog) -- [Acknowledgments](#acknowledgments) - ## Abstract This document proposes a standard for the Flexible Round-Optimized Schnorr Threshold (FROST) signing protocol. The standard is compatible with [BIP340][bip340] public keys and signatures. It supports *tweaking*, which allows deriving [BIP32][bip32] child keys from the threshold public key and creating [BIP341][bip341] Taproot outputs with key and script paths. @@ -232,7 +192,7 @@ In particular, partial signatures are *not* signatures. An adversary can forge a partial signature, i.e., create a partial signature without knowing the secret share for that particular participant public share.[^partialsig-forgery] However, if *PartialSigVerify* succeeds for all partial signatures then *PartialSigAgg* will return a valid Schnorr signature. -[^partialsig-forgery]: Assume a malicious participant intends to forge a partial signature for the participant with public share *P*. It participates in the signing session pretending to be two distinct signers: one with the public share *P* and the other with its own public share. The adversary then sets the nonce for the second signer in such a way that allows it to generate a partial signature for *P*. As a side effect, it cannot generate a valid partial signature for its own public share. An explanation of the steps required to create a partial signature forgery can be found in [this document](./bip-frost-signing/docs/partialsig_forgery.md). +[^partialsig-forgery]: Assume a malicious participant intends to forge a partial signature for the participant with public share *P*. It participates in the signing session pretending to be two distinct signers: one with the public share *P* and the other with its own public share. The adversary then sets the nonce for the second signer in such a way that allows it to generate a partial signature for *P*. As a side effect, it cannot generate a valid partial signature for its own public share. An explanation of the steps required to create a partial signature forgery can be found in [this document](https://gist.github.com/siv2r/0eab97bae9b7186ef2a4919e49d3b426). ### Tweaking the Threshold Public Key diff --git a/bip-frost-signing/.markdownlint.json b/bip-frost-signing/.markdownlint.json index b0578f5e45..2c21fe5555 100644 --- a/bip-frost-signing/.markdownlint.json +++ b/bip-frost-signing/.markdownlint.json @@ -5,5 +5,6 @@ "no-duplicate-heading": { "siblings_only": true }, "emphasis-style": { "style": "asterisk" }, "strong-style": { "style": "asterisk" }, - "ul-style": { "style": "dash" } + "ul-style": { "style": "dash" }, + "fenced-code-language": false } \ No newline at end of file diff --git a/bip-frost-signing/docs/partialsig_forgery.md b/bip-frost-signing/docs/partialsig_forgery.md deleted file mode 100644 index 144ef39a98..0000000000 --- a/bip-frost-signing/docs/partialsig_forgery.md +++ /dev/null @@ -1,63 +0,0 @@ -This is an adaptation of the MuSig2's partial signature forgery for the FROST protocol, described by Adam Gibson. You can find the [original write-up here](https://gist.github.com/AdamISZ/ca974ed67889cedc738c4a1f65ff620b). - -In FROST signing, a malicious participant could forge the partial signature (i.e., _PartialSigVerify_ on it will succeed) of another participant without knowing their secret share, but only under the following conditions: -- The victim does not participate in the signing. -- The malicious participant impersonates the victim while also participating with their original share, making it appear as if two different participants are involved in the signing. - -As a consequence, the malicious signing participant will be unable to create a valid partial signature for their original secret share. - -1. -Key Setup: Let's consider a 3-of-5 FROST policy among a group of participants $\{P_1, P_2, P_3, P_4, P_5\}$ with the following details: - -- The participant identifiers are $I = \{1, 2, 3, 4, 5\}$. -- Each participant's public share is denoted by $X_i \;\forall i \in I$. -- Each participant's secret share is denoted by $x_i \;\forall i \in I$. -- The threshold public key is denoted by $\tilde{X}$. - -2. -Signing Setup: Assume we start a signing session with $S = \{P_1, P_2, P_4^*, P_5\}$. The adversarial participant will take the role of both $P_4^*$ and $P_5$, and will forge a partial signature on the public share $X_4$ without knowing the corresponding secret share $x_4$, on a given message $m$. -> [!NOTE] -> In this scenario, the malicious participant $P_5$ is pretending to be the participant $P_4^*$ while also participating with their legitimate share. The real $P_4$ is unaware of this signing session. - -3. -The adversary receives the nonces from other signing participants: $(R_{1,1}, R_{1,2}), (R_{2,1}, R_{2,2})$. - -4. -The adversary sets aggregate partial nonces $R_1, R_2$ at random, without yet choose the individual pairs of partial nonces for $P_4^*$ and $P_5$. This is the 'cheat', which allows him to precalculate: - -Calculate $\tilde{X} = \sum\limits_{i \in S} \lambda_{i, S} \cdot X_{i}$ - -Calculate $b = \mathbb{H}(\tilde{X}, R_1, R_2, m)$ - -Calculate $\tilde{R} = R_1 + b \cdot R_2$ - -Calculate $R_{1}^{*} = R_1 - \Sigma_{k=1}^2 R_{k,1}$ - -Calculate $R_{2}^{*} = R_2 - \Sigma_{k=1}^2 R_{k,2}$ - -The last two values $R_{i}^{*}$ are 'what is left over' to be filled in by the adversary's nonce values. - -5. -To create nonces such that the forgery $s_4$ on $X_4$ verifies, the adversary does this: - -Choose $s_4$ at random. - -Calculate $Q = s_{4} \cdot G - \mathbb{H}\left(\tilde{X}, \tilde{R}, m\right)\lambda_{4, S}X_4$ - -Choose $R_{4,1}$ at random. - -Calculate $R_{4,2} = b^{-1} \cdot \left (Q - R_{4,1}\right)$ - -Now, $s_4$ *will* successfully pass the *PartialSigVerify* for the public share $X_4$, message $m$, and the signer set $S$, but only if: - -$R_{5,1} = R_{1}^{*} - R_{4,1}$ -and -$R_{5,2} = R_{2}^{*} - R_{4,2}$ - -So, concluding the first communication round of the signing protocol, the adversary shares the rogue nonce values $R_{4,1}, R_{4,2}, R_{5,1}, R_{5,2}$ as calculated above. - -Moving to the second communication round, the adversary can present the forged partial signature $s_4$. However, the adversary is unable to produce a valid $s_5$, as evident from examining the partial signature verification equation for $P_5$: - -$$s_{5} \cdot G = R_{5,1} + b \cdot R_{5,2} + \mathbb{H}\left(\tilde{X}, \tilde{R}, m\right)\lambda_{5, S}X_5$$ - -The RHS is entirely fixed by the previous steps, and thus the LHS is a point whose discrete log cannot be extracted. In other words, the values $R_{5,1}, R_{5,2}$ were determined by the choices of nonces at other indices, and their secret values cannot be deduced (unlike in honest signing, where they are pre-determined by the signer). Therefore, **even though the adversary knows the secret share** $x_5$, **they are still unable to successfully complete the FROST protocol execution**. diff --git a/bip-frost-signing/python/secp256k1lab/src/secp256k1lab/secp256k1.pyi b/bip-frost-signing/python/secp256k1lab/src/secp256k1lab/secp256k1.pyi index abf77ae918..0553503377 100644 --- a/bip-frost-signing/python/secp256k1lab/src/secp256k1lab/secp256k1.pyi +++ b/bip-frost-signing/python/secp256k1lab/src/secp256k1lab/secp256k1.pyi @@ -1,7 +1,7 @@ from __future__ import annotations # mypy treated secp256k1lab.secp256k1 as Any, so callers (see frost_ref/signing.py) -# hit "Returning Any from function declared to return ..." errors. This stub describes adds type defintions for the secp256k1lab APIs used +# hit "Returning Any from function declared to return ..." errors. This stub describes adds type definitions for the secp256k1lab APIs used from typing_extensions import Self From f21244a9e5f22e7abc39fd30f2ab1b770e6ac262 Mon Sep 17 00:00:00 2001 From: siv2r Date: Thu, 22 Jan 2026 17:56:49 +0530 Subject: [PATCH 3/6] python: update secp256k1lab & remove the stub file --- .../secp256k1lab/.github/workflows/main.yml | 4 +- .../python/secp256k1lab/.python-version | 2 +- .../python/secp256k1lab/pyproject.toml | 2 +- .../src/secp256k1lab/secp256k1.py | 138 +++++++++--------- .../src/secp256k1lab/secp256k1.pyi | 133 ----------------- 5 files changed, 77 insertions(+), 202 deletions(-) delete mode 100644 bip-frost-signing/python/secp256k1lab/src/secp256k1lab/secp256k1.pyi diff --git a/bip-frost-signing/python/secp256k1lab/.github/workflows/main.yml b/bip-frost-signing/python/secp256k1lab/.github/workflows/main.yml index 10e4bac61e..fb05230b3c 100644 --- a/bip-frost-signing/python/secp256k1lab/.github/workflows/main.yml +++ b/bip-frost-signing/python/secp256k1lab/.github/workflows/main.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] + 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 }} @@ -24,7 +24,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] + python-version: ["3.11", "3.12", "3.13", "3.14"] steps: - uses: actions/checkout@v4 - name: Setup Python ${{ matrix.python-version }} diff --git a/bip-frost-signing/python/secp256k1lab/.python-version b/bip-frost-signing/python/secp256k1lab/.python-version index bd28b9c5c2..2c0733315e 100644 --- a/bip-frost-signing/python/secp256k1lab/.python-version +++ b/bip-frost-signing/python/secp256k1lab/.python-version @@ -1 +1 @@ -3.9 +3.11 diff --git a/bip-frost-signing/python/secp256k1lab/pyproject.toml b/bip-frost-signing/python/secp256k1lab/pyproject.toml index a0bdd19f42..68b927b384 100644 --- a/bip-frost-signing/python/secp256k1lab/pyproject.toml +++ b/bip-frost-signing/python/secp256k1lab/pyproject.toml @@ -14,7 +14,7 @@ maintainers = [ { name = "Jonas Nick", email = "jonasd.nick@gmail.com" }, { name = "Sebastian Falbesoner", email = "sebastian.falbesoner@gmail.com" } ] -requires-python = ">=3.9" +requires-python = ">=3.11" license = "MIT" license-files = ["COPYING"] keywords = ["secp256k1", "elliptic curves", "cryptography", "Bitcoin"] diff --git a/bip-frost-signing/python/secp256k1lab/src/secp256k1lab/secp256k1.py b/bip-frost-signing/python/secp256k1lab/src/secp256k1lab/secp256k1.py index 1a99dc4899..0526878d91 100644 --- a/bip-frost-signing/python/secp256k1lab/src/secp256k1lab/secp256k1.py +++ b/bip-frost-signing/python/secp256k1lab/src/secp256k1lab/secp256k1.py @@ -15,6 +15,9 @@ * 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. @@ -25,26 +28,28 @@ class APrimeFE: # The size of the field (also its modulus and characteristic). SIZE: int - def __init__(self, a=0, b=1): + 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 = num - self._den = den + self._num: int = num + self._den: int = den - def __add__(self, a): + 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) @@ -52,21 +57,18 @@ def __add__(self, a): return type(self)(self._num + self._den * a, self._den) return NotImplemented - def __radd__(self, a): + def __radd__(self, a: int) -> Self: """Compute the sum of an integer and a field element.""" return type(self)(a) + self @classmethod - # REVIEW This should be - # def sum(cls, *es: Iterable[Self]) -> Self: - # but Self needs the typing_extension package on Python <= 3.12. - def sum(cls, *es): + 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): + 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) @@ -74,11 +76,11 @@ def __sub__(self, a): return type(self)(self._num - self._den * a, self._den) return NotImplemented - def __rsub__(self, a): + 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): + 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) @@ -86,83 +88,85 @@ def __mul__(self, a): return type(self)(self._num * a, self._den) return NotImplemented - def __rmul__(self, a): + 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): + 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): + 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): + def __neg__(self) -> Self: """Negate a field element.""" return type(self)(-self._num, self._den) - def __int__(self): + 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): + def sqrt(self) -> Self | None: """Compute the square root of a field element if it exists (None otherwise).""" raise NotImplementedError - def is_square(self): + 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): + 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): + 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 - return (self._num - self._den * a) % 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): + 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): + 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): + 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): + 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): + 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): + def __str__(self) -> str: """Convert this field element to a 64 character hex string.""" return f"{int(self):064x}" - def __repr__(self): + def __repr__(self) -> str: """Get a string representation of this field element.""" return f"{type(self).__qualname__}(0x{int(self):x})" @@ -170,7 +174,7 @@ def __repr__(self): class FE(APrimeFE): SIZE = 2**256 - 2**32 - 977 - def sqrt(self): + 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. @@ -193,14 +197,14 @@ class Scalar(APrimeFE): SIZE = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 @classmethod - def from_int_nonzero_checked(cls, v): + 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): + 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) @@ -231,23 +235,23 @@ class GE: ORDER_HALF = ORDER // 2 @property - def infinity(self): + def infinity(self) -> bool: """Whether the group element is the point at infinity.""" return self._infinity @property - def x(self): + 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): + 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=None, y=None): + 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. @@ -255,6 +259,8 @@ def __init__(self, x=None, y=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 @@ -262,7 +268,7 @@ def __init__(self, x=None, y=None): self._x = fx self._y = fy - def __add__(self, a): + def __add__(self, a: GE) -> GE: """Add two group elements together.""" # Deal with infinity: a + infinity == infinity + a == a. if self.infinity: @@ -286,14 +292,14 @@ def __add__(self, a): return GE(x, y) @staticmethod - def sum(*ps): + 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): + 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, @@ -312,54 +318,56 @@ def batch_mul(*aps): r += p return r - def __rmul__(self, a): - """Multiply an integer with a group element.""" + 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): + 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): + def __sub__(self, a: GE) -> GE: """Subtract a group element from another.""" return self + (-a) - def __eq__(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): + 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): + 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): + 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): + 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): + 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): + 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: @@ -369,7 +377,7 @@ def lift_x(x): return GE(x, y) @staticmethod - def from_bytes_compressed(b): + 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: @@ -381,7 +389,7 @@ def from_bytes_compressed(b): return r @staticmethod - def from_bytes_compressed_with_infinity(b): + 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() @@ -389,7 +397,7 @@ def from_bytes_compressed_with_infinity(b): return GE.from_bytes_compressed(b) @staticmethod - def from_bytes_uncompressed(b): + def from_bytes_uncompressed(b: bytes) -> GE: """Convert an uncompressed to a group element.""" assert len(b) == 65 if b[0] != 4: @@ -401,7 +409,7 @@ def from_bytes_uncompressed(b): return GE(x, y) @staticmethod - def from_bytes(b): + 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: @@ -410,7 +418,7 @@ def from_bytes(b): return GE.from_bytes_uncompressed(b) @staticmethod - def from_bytes_xonly(b): + 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) @@ -418,23 +426,23 @@ def from_bytes_xonly(b): return r @staticmethod - def is_valid_x(x): + 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): + 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): + 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): + 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 @@ -457,17 +465,17 @@ class FastGEMul: i.e. on average ~128 point additions take place. """ - def __init__(self, p): - self.table = [p] # table[i] = (2^i) * p + 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): + def mul(self, a: Scalar | int) -> GE: result = GE() - a = int(a) - for bit in range(a.bit_length()): - if a & (1 << bit): + a_ = int(a) + for bit in range(a_.bit_length()): + if a_ & (1 << bit): result += self.table[bit] return result diff --git a/bip-frost-signing/python/secp256k1lab/src/secp256k1lab/secp256k1.pyi b/bip-frost-signing/python/secp256k1lab/src/secp256k1lab/secp256k1.pyi deleted file mode 100644 index 0553503377..0000000000 --- a/bip-frost-signing/python/secp256k1lab/src/secp256k1lab/secp256k1.pyi +++ /dev/null @@ -1,133 +0,0 @@ -from __future__ import annotations - -# mypy treated secp256k1lab.secp256k1 as Any, so callers (see frost_ref/signing.py) -# hit "Returning Any from function declared to return ..." errors. This stub describes adds type definitions for the secp256k1lab APIs used - -from typing_extensions import Self - - -class APrimeFE: - SIZE: int - - def __init__(self, a: int | Self = 0, b: int | Self = 1) -> None: ... - def __add__(self, a: int | Self) -> Self: ... - def __radd__(self, a: int) -> Self: ... - - @classmethod - def sum(cls, *es: Self) -> Self: ... - - def __sub__(self, a: int | Self) -> Self: ... - def __rsub__(self, a: int) -> Self: ... - def __mul__(self, a: int | Self) -> Self: ... - def __rmul__(self, a: int) -> Self: ... - def __truediv__(self, a: int | Self) -> Self: ... - def __pow__(self, a: int) -> Self: ... - def __neg__(self) -> Self: ... - def __int__(self) -> int: ... - def sqrt(self) -> Self | None: ... - def is_square(self) -> bool: ... - def is_even(self) -> bool: ... - def __eq__(self, a: object) -> bool: ... - def to_bytes(self) -> bytes: ... - - @classmethod - def from_int_checked(cls, v: int) -> Self: ... - - @classmethod - def from_int_wrapping(cls, v: int) -> Self: ... - - @classmethod - def from_bytes_checked(cls, b: bytes) -> Self: ... - - @classmethod - def from_bytes_wrapping(cls, b: bytes) -> Self: ... - - def __str__(self) -> str: ... - def __repr__(self) -> str: ... - - -class FE(APrimeFE): - SIZE: int - - def sqrt(self) -> Self | None: ... - - -class Scalar(APrimeFE): - SIZE: int - - @classmethod - def from_int_nonzero_checked(cls, v: int) -> Self: ... - - @classmethod - def from_bytes_nonzero_checked(cls, b: bytes) -> Self: ... - - -class GE: - ORDER: int - ORDER_HALF: int - - @property - def infinity(self) -> bool: ... - - @property - def x(self) -> FE: ... - - @property - def y(self) -> FE: ... - - def __init__(self, x: int | FE | None = None, y: int | FE | None = None) -> None: ... - def __add__(self, a: GE) -> GE: ... - - @staticmethod - def sum(*ps: GE) -> GE: ... - - @staticmethod - def batch_mul(*aps: tuple[int | Scalar, GE]) -> GE: ... - - def __rmul__(self, a: int | Scalar) -> GE: ... - def __neg__(self) -> GE: ... - def __sub__(self, a: GE) -> GE: ... - def __eq__(self, a: object) -> bool: ... - def has_even_y(self) -> bool: ... - def to_bytes_compressed(self) -> bytes: ... - def to_bytes_compressed_with_infinity(self) -> bytes: ... - def to_bytes_uncompressed(self) -> bytes: ... - def to_bytes_xonly(self) -> bytes: ... - - @staticmethod - def lift_x(x: int | FE) -> GE: ... - - @staticmethod - def from_bytes_compressed(b: bytes) -> GE: ... - - @staticmethod - def from_bytes_compressed_with_infinity(b: bytes) -> GE: ... - - @staticmethod - def from_bytes_uncompressed(b: bytes) -> GE: ... - - @staticmethod - def from_bytes(b: bytes) -> GE: ... - - @staticmethod - def from_bytes_xonly(b: bytes) -> GE: ... - - @staticmethod - def is_valid_x(x: int | FE) -> bool: ... - - def __str__(self) -> str: ... - def __repr__(self) -> str: ... - def __hash__(self) -> int: ... - - -G: GE - - -class FastGEMul: - table: list[GE] - - def __init__(self, p: GE) -> None: ... - def mul(self, a: int | Scalar) -> GE: ... - - -FAST_G: FastGEMul From a88f033df7a2361c700f205ced6b50d49871c696 Mon Sep 17 00:00:00 2001 From: siv2r Date: Sun, 25 Jan 2026 14:58:14 +0530 Subject: [PATCH 4/6] bip-frost-signing: fix formatting and explain more --- bip-frost-signing.md | 109 +++++++++++++++++++-------------------- bip-frost-signing/all.sh | 2 + 2 files changed, 56 insertions(+), 55 deletions(-) diff --git a/bip-frost-signing.md b/bip-frost-signing.md index 1f24febdff..146acd491e 100644 --- a/bip-frost-signing.md +++ b/bip-frost-signing.md @@ -24,13 +24,13 @@ The accompanying source code is licensed under the [MIT license](https://opensou -The FROST signature scheme enables threshold Schnorr signatures. In a `t`-of-`n` threshold configuration, any `t`[^t-edge-cases] participants can cooperatively produce a Schnorr signature that is indistinguishable from a signature produced by a single signer. FROST signatures are unforgeable as long as fewer than `t` participants are corrupted. The signing protocol remains functional provided that at least `t` honest participants retain access to their secret key shares. +The FROST signature scheme enables threshold Schnorr signatures. In a *t*-of-*n* threshold configuration, any *t*[^t-edge-cases] participants can cooperatively produce a Schnorr signature that is indistinguishable from a signature produced by a single signer. FROST signatures are unforgeable as long as fewer than *t* participants are corrupted. The signing protocol remains functional provided that at least *t* honest participants retain access to their secret key shares. -[^t-edge-cases]: While `t = n` and `t = 1` are in principle supported, simpler alternatives are available in these cases. In the case `t = n`, using a dedicated `n`-of-`n` multi-signature scheme such as MuSig2 (see [BIP327][bip327]) instead of FROST avoids the need for an interactive DKG. The case `t = 1` can be realized by letting one signer generate an ordinary [BIP340][bip340] key pair and transmitting the key pair to every other signer, who can check its consistency and then simply use the ordinary [BIP340][bip340] signing algorithm. Signers still need to ensure that they agree on a key pair. +[^t-edge-cases]: While *t = n* and *t = 1* are in principle supported, simpler alternatives are available in these cases. In the case *t = n*, using a dedicated *n*-of-*n* multi-signature scheme such as MuSig2 (see [BIP327][bip327]) instead of FROST avoids the need for an interactive DKG. The case *t = 1* can be realized by letting one signer generate an ordinary [BIP340][bip340] key pair and transmitting the key pair to every other signer, who can check its consistency and then simply use the ordinary [BIP340][bip340] signing algorithm. Signers still need to ensure that they agree on a key pair. The IRTF has published [RFC 9591][rfc9591], which specifies the FROST signing protocol for several elliptic curve and hash function combinations, including secp256k1 with SHA-256, the cryptographic primitives used in Bitcoin. However, the signatures produced by RFC 9591 are incompatible with BIP340 Schnorr signatures due to the X-only public keys introduced in BIP340. Additionally, RFC 9591 does not specify key tweaking mechanisms, which are essential for Bitcoin applications such as [BIP32][bip32] key derivation and [BIP341][bip341] Taproot. This document addresses these limitations by specifying a BIP340-compatible variant of FROST signing protocol that supports key tweaking. -Following the initial publication of the FROST protocol[[KG20][frost1]], several optimized variants have been proposed to improve computational efficiency and bandwidth optimization: FROST2[[CKM21][frost2]], FROST2-BTZ[[BTZ21][stronger-security-frost]], and FROST3[[CGRS23][olaf]]. Among these variants, FROST3 is the most efficient variant to date. +Following the initial publication of the FROST protocol[[KG20][frost1]], several optimized variants have been proposed to improve computational efficiency and bandwidth optimization: FROST2[[CKM21][frost2]], FROST2-BTZ[[BTZ21][stronger-security-frost]], and FROST3[[RRJSS][roast], [CGRS23][olaf]]. Among these variants, FROST3 is the most efficient variant to date. This document specifies the FROST3 variant[^frost3-security]. The FROST3 signing protocol shares substantial similarities with the MuSig2 signing protocol specified in [BIP327][bip327]. Accordingly, this specification adopts several design principles from BIP327, including support for key tweaking, partial signature verification, and identifiable abort mechanisms. We note that significant portions of this document have been directly adapted from BIP327 due to the similarities in the signing protocols. Key generation for FROST signing is out of scope for this document. @@ -56,38 +56,38 @@ Similarly, the test vectors that exercise the unimplemented features should be r ### Key Material and Setup -A FROST key generation protocol configures a group of `n` participants with a *threshold public key* (representing a `t`-of-`n` threshold policy). -The corresponding *threshold secret key* is Shamir secret-shared among all `n` participants, where each participant holds a distinct long-term *secret share*. -This ensures that any subset of at least `t` participants can jointly run the FROST signing protocol to produce a signature under the *threshold secret key*. +A FROST key generation protocol configures a group of *n* participants with a *threshold public key* (representing a *t*-of-*n* threshold policy). +The corresponding *threshold secret key* is Shamir secret-shared among all *n* participants, where each participant holds a distinct long-term *secret share*. +This ensures that any subset of at least *t* participants can jointly run the FROST signing protocol to produce a signature under the *threshold secret key*. -Key generation for FROST signing is out of scope for this document. Implementations can use either a trusted dealer setup, as specified in [Appendix C of RFC 9591](https://www.rfc-editor.org/rfc/rfc9591.html#name-trusted-dealer-key-generati), or a distributed key generation (DKG) protocol such as [ChillDKG](https://github.com/BlockstreamResearch/bip-frost-dkg). The appropriate choice depends on the implementations's trust model and operational requirements. +Key generation for FROST signing is out of scope for this document. Implementations can use either a trusted dealer setup, as specified in [Appendix C of RFC 9591](https://www.rfc-editor.org/rfc/rfc9591.html#name-trusted-dealer-key-generati), or a distributed key generation (DKG) protocol such as [ChillDKG](https://github.com/BlockstreamResearch/bip-frost-dkg). The appropriate choice depends on the implementation's trust model and operational requirements. This protocol distinguishes between two public key formats: *plain public keys* are 33-byte compressed public keys traditionally used in Bitcoin, while *X-only public keys* are 32-byte keys defined in [BIP340][bip340]. -Key generation protocols produce *public shares* and *threshold public keys* in the plain format. During signing, we conditionally negates *secret shares* to ensure the resulting threshold-signature verifies under the corresponding *X-only threshold public key*. +Key generation protocols produce *public shares* and *threshold public keys* in the plain format. During signing, we conditionally negate *secret shares* to ensure the resulting threshold-signature verifies under the corresponding *X-only threshold public key*. > [!WARNING] -> Key generation protocols must commit the *threshold public key* to an unspendable script path as recommended in [BIP341](https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki#cite_note-23). This prevents a malicious party from embedding a hidden script path during key generation that would allow them to bypass the `t`-of-`n` threshold policy. +> Key generation protocols must commit the *threshold public key* to an unspendable script path as recommended in [BIP341](https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki#cite_note-23). This prevents a malicious party from embedding a hidden script path during key generation that would allow them to bypass the *t*-of-*n* threshold policy. #### Protocol Parties and Network Setup -There are `u` (where `t <= u <= n < 2^32`) participants and one coordinator initiating the FROST signing protocol. +There are *u* (where *t <= u <= n < 2^32*) participants and one coordinator initiating the FROST signing protocol. Each participant has a point-to-point communication link to the coordinator (but participants do not have direct communication links to each other). If there is no dedicated coordinator, one of the participants can act as the coordinator. #### Signing Inputs and Outputs -Each signing session requires two inputs: a participant's long-term *secret share* `secshare_i` (individual to each participant, not shared with the coordinator) and a [*Signers Context*](#signers-context)[^signers-ctx-struct] data structure (common to all participants and the coordinator). +Each signing session requires two inputs: a participant's long-term *secret share* (individual to each participant, not shared with the coordinator) and a [Signers Context](#signers-context)[^signers-ctx-struct] data structure (common to all participants and the coordinator). -[^signers-ctx-struct]: The *Signers Context* represents the public data of signing participants: their identifiers (*id1..u*) and public shares (*pubshare1..u*). +[^signers-ctx-struct]: The Signers Context represents the public data of signing participants: their identifiers (*id1..u*) and public shares (*pubshare1..u*). Implementations may represent this as simply as two separate lists passed to signing APIs. The threshold public key *thresh_pk* can be stored for efficiency or recomputed when needed using *DeriveThreshPubkey*. Similarly, the values *n* and *t* are used only for validation, and can be omitted if validation is not performed. This signing protocol is compatible with any key generation protocol that produces valid FROST keys. -Valid keys satisfy: (1) each *secret share* is a Shamir share of the *threshold secret key*, and (2) each *public share* equals the scalar multiplication `secshare * G`. +Valid keys satisfy: (1) each *secret share* is a Shamir share of the *threshold secret key*, and (2) each *public share* equals the scalar multiplication *secshare \* G*. Implementations may **optionally** validate key compatibility for a signing session using the *ValidateSignersCtx* function. -For comprehensive validation of the entire key material, *ValidateSignersCtx* can be run on all possible `u` signing participants. +For comprehensive validation of the entire key material, *ValidateSignersCtx* can be run on all possible *u* signing participants. > [!IMPORTANT] > Passing *ValidateSignersCtx* ensures functional compatibility with the signing protocol but does not guarantee the security of the key generation protocol itself. @@ -96,11 +96,9 @@ The output of the FROST signing protocol is a BIP340 Schnorr signature that veri ### General Signing Flow -We assume that the coordinator and the signing participants (in the algorithms specified below, is stored in a data structure called [Signers Context](#signers-context)) are selected externally to the signing protocol before it is initiated. They could also optionally tweak the *threshold public key* now, by initializing [Tweak Context](#tweak-context) with it. - The coordinator and signing participants must be determined before initiating the signing protocol. -This information is stored in a [*Signers Context*](#signers-context) data structure. -The *threshold public key* may optionally be tweaked by initializing a [*Tweak Context*](#tweak-context) at this stage. +The signing participants information is stored in a [Signers Context](#signers-context) data structure. +The *threshold public key* may optionally be tweaked by initializing a [Tweak Context](#tweak-context) at this stage. Whenever the signing participants want to sign a message, the basic order of operations to create a threshold-signature is as follows: @@ -120,7 +118,7 @@ If all parties behaved honestly, the result passes [BIP340][bip340] verification ![Frost signing flow](./bip-frost-signing/docs/frost-signing-flow.png) -A malicious coordinator can cause the signing session to fail but cannot compromise the unforgeability of the scheme. Even when colluding with up to `t-1` signers, a malicious coordinator cannot forge a signature. +A malicious coordinator can cause the signing session to fail but cannot compromise the unforgeability of the scheme. Even when colluding with up to *t-1* signers, a malicious coordinator cannot forge a signature. > [!TIP] > The *Sign* algorithm must **not** be executed twice with the same *secnonce*. @@ -149,7 +147,7 @@ However, the protection provided by the optional arguments should only be viewed In most conceivable scenarios, the assumption that the arguments are different between two executions of *NonceGen* is relatively strong, particularly when facing an active adversary. In some applications, the coordinator may enable preprocessing of nonce generation to reduce signing latency. -Participants run *NonceGen* to generate a batch of *pubnonce* values before the message or *Signers Context*[^preprocess-round1] is known, which are stored with the coordinator (e.g., on a centralized server). +Participants run *NonceGen* to generate a batch of *pubnonce* values before the message or Signers Context[^preprocess-round1] is known, which are stored with the coordinator (e.g., on a centralized server). During this preprocessing phase, only the available arguments are provided to *NonceGen*. When a signing session begins, the coordinator selects and aggregates *pubnonces* of the signing participants, enabling them to run *Sign* immediately once the message is determined. This way, the final signature is created quicker and with fewer round trips. @@ -157,7 +155,7 @@ However, applications that use this method presumably store the nonces for a lon Moreover, this method is not compatible with the defense-in-depth mechanism described in the previous paragraph. -[^preprocess-round1]: When preprocessing *NonceGen* round, the *Signers Context* can be extended to include the *pubnonces* of the signing participants, as these are generated and stored before the signing session begins. +[^preprocess-round1]: When preprocessing *NonceGen* round, the Signers Context can be extended to include the *pubnonces* of the signing participants, as these are generated and stored before the signing session begins. FROST signers are typically stateful: they generate *secnonce*, store it, and later use it to produce a partial signature after receiving the aggregated nonce. However, stateless signing is possible when one signer receives the aggregate nonce of all OTHER signers before generating their own nonce. @@ -214,7 +212,8 @@ Plain tweaking can be used to derive child public keys from a threshold public k On the other hand, X-only tweaking is required for Taproot tweaking per [BIP341][bip341]. A Taproot-tweaked public key commits to a *script path*, allowing users to create transaction outputs that are spendable either with a FROST threshold-signature or by providing inputs that satisfy the script path. Script path spends require a control block that contains a parity bit for the tweaked X-only public key. -The bit can be obtained with `GetPlainPubkey(tweak_ctx)[0] & 1`. + +The bit can be obtained with *GetPlainPubkey(tweak_ctx)[0] & 1*. ## Algorithms @@ -232,19 +231,17 @@ We rely on the following types and conventions throughout this document: - **Naming:** Points are denoted using uppercase letters (e.g., *P*, *Q*), while scalars are denoted using lowercase letters (e.g., *r*, *s*). - **Mathematical Context:** Points are group elements under elliptic curve addition. The group includes all points on the secp256k1 curve plus the point at infinity (the identity element). - **Arithmetic:** The operators +, -, and · are overloaded depending on their operands: - - **Scalar Arithmetic:**[^implicit-mod] When applied to two *Scalar* operands, +, -, and · denote integer addition, subtraction, and multiplication modulo the group order. + - **Scalar Arithmetic:** When applied to two *Scalar* operands, +, -, and · denote integer addition, subtraction, and multiplication modulo the group order. - **Point Addition:** When applied to two *GE* operands, + denotes the elliptic curve [group addition operation](https://en.wikipedia.org/wiki/Elliptic_curve#The_group_law). - **Scalar Multiplication:** The notation r · P denotes [scalar multiplication](https://en.wikipedia.org/wiki/Elliptic_curve_point_multiplication) (the repeated addition of point P, r times). -[^implicit-mod]: The algorithms in upcoming sections, when a scalar arithmetic is performed, the *mod order* is implicit. They don't spell it out. For example, no a · b mod order, they just say a · b. - The reference code vendors the secp256k1lab library to handle underlying arithmetic, serialization, deserialization, and auxiliary functions. To improve the readability of this specification, we utilize simplified notation aliases for the library's internal methods, as mapped below: | Notation | secp256k1lab | Description | | --- | --- | --- | | *p* | *FE.SIZE* | Field element size | -| *order* | *GE.ORDER* | Group order | +| *ord* | *GE.ORDER* | Group order | | *G* | *G* | The secp256k1 generator point | | *inf_point* | *GE()* | The infinity point | | *is_infinity(P)* | *P.infinity()* | Returns whether *P* is the point at infinity | @@ -258,10 +255,10 @@ The reference code vendors the secp256k1lab library to handle underlying arithme | *lift_x(x)*[^liftx-soln] | *GE.lift_x(x)* | Decodes a 32-byte x-only serialization *x* into a non-infinity point P. The resulting point always has an even y-coordinate. | | *cpoint(b)* | *GE.from_bytes_compressed(b)* | Decodes a 33-byte compressed serialization *b* into a non-infinity point | | *cpoint_ext(b)* | *GE.from_bytes_compressed
_with_infinity(b)* | Decodes a 33-byte compressed serialization *b* into a point. If *b* is a 33-byte array of zeros, it returns the point at infinity | -| *scalar_from_bytes_checked(b)* | *Scalar.from_bytes_checked(b)* | Deserializes a 32-byte array *b* to a scalar, fails if the value is ≥ *order* | -| *scalar_from_bytes
_nonzero_checked(b)* | *Scalar.from_bytes
_nonzero_checked(b)* | Deserializes a 32-byte array *b* to a scalar, fails if the value is zero or ≥ *order* | -| *scalar_from_bytes_wrapping(b)* | *Scalar.from_bytes_wrapping(b)* | Deserializes a 32-byte array *b* to a scalar, reducing the value modulo *order* | | *scalar_to_bytes(s)* | *s.to_bytes()* | Returns the 32-byte serialization of a scalar *s* | +| *scalar_from_bytes_checked(b)* | *Scalar.from_bytes_checked(b)* | Deserializes a 32-byte array *b* to a scalar, fails if the value is ≥ *ord* | +| *scalar_from_bytes
_nonzero_checked(b)* | *Scalar.from_bytes
_nonzero_checked(b)* | Deserializes a 32-byte array *b* to a scalar, fails if the value is zero or ≥ *ord* | +| *scalar_from_bytes_wrapping(b)* | *Scalar.from_bytes_wrapping(b)* | Deserializes a 32-byte array *b* to a scalar, reducing the value modulo *ord* | | *hashtag(x)* | *tagged_hash(x)* | Computes a 32-byte domain-separated hash of the byte array *x*. The output is *SHA256(SHA256(tag) \|\| SHA256(tag) \|\| x)*, where *tag* is UTF-8 encoded string unique to the context | | *random_bytes(n)* | - | Returns *n* bytes, sampled uniformly at random using a cryptographically secure pseudorandom number generator (CSPRNG) | | *xor_bytes(a, b)* | *xor_bytes(a, b)* | Returns byte-wise xor of *a* and *b* | @@ -308,7 +305,7 @@ Algorithm *ValidateSignersCtx(signers_ctx)*: - Inputs: - The *signers_ctx*: a [Signers Context](#signers-context) data structure - *(n, t, u, id1..u, pubshare1..u, thresh_pk) = signers_ctx* -- Fail if *t > n* +- Fail if not *1 ≤ t ≤ n* - Fail if not *t ≤ u ≤ n* - For *i = 1 .. u*: - Fail if not *0 ≤ idi ≤ n - 1* @@ -336,11 +333,13 @@ Internal Algorithm *DeriveInterpolatingValue(id1..u, my_id):* - Let *deno = Scalar(1)* - For *i = 1..u*: - If *idi ≠ my_id*: - - Let *num = num · Scalar(idi + 1)* - - Let *deno = deno · Scalar(idi - my_id)* -- *λ = num · deno-1* + - Let *num = num · Scalar(idi + 1)[^lagrange-shift]  (mod ord)* + - Let *deno = deno · Scalar(idi - my_id)  (mod ord)* +- *λ = num · deno-1  (mod ord)* - Return *λ* +[^lagrange-shift]: The standard Lagrange interpolation coefficient uses the formula *idi / (idi - my_id)* for each term in the product, where ids are in the range *1..n*. However, since participant identifiers in this protocol are zero-indexed (range *0..n-1*), we shift them by adding 1. This transforms each term to *(idi+1) / (idi - my_id)*. + ### Tweaking the Threshold Public Key #### Tweak Context @@ -393,8 +392,8 @@ Algorithm *ApplyTweak(tweak_ctx, tweak, is_xonly_t)*: - Let *t = scalar_from_bytes_nonzero_checked(tweak)*; fail if that fails - Let *Q' = g · Q + t · G* - Fail if *is_infinity(Q')* -- Let *gacc' = g · gacc* -- Let *tacc' = t + g · tacc* +- Let *gacc' = g · gacc  (mod ord)* +- Let *tacc' = t + g · tacc  (mod ord)* - Return *tweak_ctx' = (Q', gacc', tacc')* ### Nonce Generation @@ -432,7 +431,7 @@ Algorithm *NonceGen(secshare, pubshare, thresh_pk, m, extra_in)*: [^sk-xor-rand]: The random data is hashed (with a unique tag) as a precaution against situations where the randomness may be correlated with the secret signing share itself. It is xored with the secret share (rather than combined with it in a hash) to reduce the number of operations exposed to the actual secret share. -[^secnonce-ser]: The algorithms as specified here assume that the *secnonce* is stored as a 64-byte array using the serialization *secnonce = bytes(32, k1) || bytes(32, k2)*. The same format is used in the reference implementation and in the test vectors. However, since the *secnonce* is (obviously) not meant to be sent over the wire, compatibility between implementations is not a concern, and this method of storing the *secnonce* is merely a suggestion. The *secnonce* is effectively a local data structure of the signer which comprises the value triple *(k1, k2)*, and implementations may choose any suitable method to carry it from *NonceGen* (first communication round) to *Sign* (second communication round). In particular, implementations may choose to hide the *secnonce* in internal state without exposing it in an API explicitly, e.g., in an effort to prevent callers from reusing a *secnonce* accidentally. +[^secnonce-ser]: The algorithms as specified here assume that the *secnonce* is stored as a 64-byte array using the serialization *secnonce = bytes(32, k1) || bytes(32, k2)*. The same format is used in the reference implementation and in the test vectors. However, since the *secnonce* is (obviously) not meant to be sent over the wire, compatibility between implementations is not a concern, and this method of storing the *secnonce* is merely a suggestion. The *secnonce* is effectively a local data structure of the signer which comprises the value pair *(k1, k2)*, and implementations may choose any suitable method to carry it from *NonceGen* (first communication round) to *Sign* (second communication round). In particular, implementations may choose to hide the *secnonce* in internal state without exposing it in an API explicitly, e.g., in an effort to prevent callers from reusing a *secnonce* accidentally. [^max-msg-len]: In theory, the allowed message size is restricted because SHA256 accepts byte strings only up to size of 2^61-1 bytes (and because of the 8-byte length encoding). @@ -446,7 +445,7 @@ Algorithm *NonceAgg(pubnonce1..u, id1..u)*: - The list of participant identifiers *id1..u*: *u* integers, each with 0 ≤ *idi* < *n* - For *j = 1 .. 2*: - For *i = 1 .. u*: - - Let *Ri,j = cpoint(pubnoncei[(j-1)*33:j*33])*; fail if that fails and blame signer *idi* for invalid *pubnonce* + - Let *Ri,j = cpoint(pubnoncei[(j-1)\*33:j\*33])*; fail if that fails and blame signer *idi* for invalid *pubnonce* - Let *Rj = R1,j + R2,j + ... + Ru,j* - Return *aggnonce = cbytes_ext(R1) || cbytes_ext(R2)* @@ -506,14 +505,13 @@ Algorithm *Sign(secnonce, secshare, my_id, session_ctx)*: - Let *k2' = scalar_from_bytes_nonzero_checked(secnonce[32:64])*; fail if that fails - Let *k1 = k1', k2 = k2'* if *has_even_y(R)*, otherwise let *k1 = -k1', k2 = -k2'* - Let *d' = scalar_from_bytes_nonzero_checked(secshare)*; fail if that fails -- Let *P = d' · G* -- Let *pubshare = cbytes(P)* +- Let *pubshare = cbytes(d' · G)* - Fail if *pubshare* not in *pubshare1..u* - Fail if *my_id* not in *id1..u* - Let *λ = DeriveInterpolatingValue(id1..u, my_id)*; fail if that fails - Let *g = Scalar(1)* if *has_even_y(Q)*, otherwise let *g = Scalar(-1)* -- Let *d = g · gacc · d'* (See [*Negation of Secret Share When Signing*](#negation-of-the-secret-share-when-signing)) -- Let *s = k1 + b · k2 + e · λ · d* +- Let *d = g · gacc · d'  (mod ord)* (See [Negation of Secret Share When Signing](#negation-of-the-secret-share-when-signing)) +- Let *s = k1 + b · k2 + e · λ · d  (mod ord)* - Let *psig = scalar_to_bytes(s)* - Let *pubnonce = cbytes(k1' · G) || cbytes(k2' · G)* - If *PartialSigVerifyInternal(psig, my_id, pubnonce, pubshare, session_ctx)* (see below) returns failure, fail[^why-verify-partialsig] @@ -534,7 +532,7 @@ Algorithm *PartialSigVerify(psig, pubnonce1..u, signers_ctx, tweak1..v* : *v* booleans - The message *m*: a byte array[^max-msg-len] - The index *i* of the signer in the list of public nonces where *0 < i ≤ u* -- Let *(_, _, u, id1..u, pubshare1..u, thresh_pk) = signers_ctx* +- Let *(_, _, u, id1..u, pubshare1..u, _) = signers_ctx* - Let *aggnonce = NonceAgg(pubnonce1..u, id1..u)*; fail if that fails - Let *session_ctx = (signers_ctx, aggnonce, v, tweak1..v, is_xonly_t1..v, m)* - Run *PartialSigVerifyInternal(psig, idi, pubnoncei, pubsharei, session_ctx)* @@ -552,7 +550,7 @@ Internal Algorithm *PartialSigVerifyInternal(psig, my_id, pubnonce, pubshare, se - Let *P = cpoint(pubshare)*; fail if that fails - Let *λ = DeriveInterpolatingValue(id1..u, my_id)*[^lambda-cant-fail] - Let *g = Scalar(1)* if *has_even_y(Q)*, otherwise let *g = Scalar(-1)* -- Let *g' = g · gacc* (See [*Negation of Pubshare When Partially Verifying*](#negation-of-the-pubshare-when-partially-verifying)) +- Let *g' = g · gacc  (mod ord)* (See [Negation of Pubshare When Partially Verifying](#negation-of-the-pubshare-when-partially-verifying)) - Fail if *s · G ≠ Re\* + e · λ · g' · P* - Return success iff no failure occurred before reaching this point. @@ -571,14 +569,14 @@ Algorithm *PartialSigAgg(psig1..u, id1..u, session_ctx)*: - For *i = 1 .. u*: - Let *si = scalar_from_bytes_nonzero_checked(psigi)*; fail if that fails and blame signer *idi* for invalid partial signature. - Let *g = Scalar(1)* if *has_even_y(Q)*, otherwise let *g = Scalar(-1)* -- Let *s = s1 + ... + su + e · g · tacc* +- Let *s = s1 + ... + su + e · g · tacc  (mod ord)* - Return *sig = xbytes(R) || scalar_to_bytes(s)* ### Test Vectors & Reference Code -We provide a naive, highly inefficient, and non-constant time [pure Python 3 reference implementation of the threshold public key tweaking, nonce generation, partial signing, and partial signature verification algorithms](./reference/reference.py). +We provide a naive, highly inefficient, and non-constant time [pure Python 3 reference implementation of the threshold public key tweaking, nonce generation, partial signing, and partial signature verification algorithms](./bip-frost-signing/python/frost_ref/). -Standalone JSON test vectors are also available in the [same directory](./reference/vectors/), to facilitate porting the test vectors into other implementations. +Standalone JSON test vectors are also available in the [same directory](./bip-frost-signing/python/vectors/), to facilitate porting the test vectors into other implementations. > [!CAUTION] > The reference implementation is for demonstration purposes only and not to be used in production environments. @@ -643,7 +641,7 @@ Algorithm *DeterministicSign(secshare, my_id, aggothernonce, signers_ctx, tweak< - Let *session_ctx = (signers_ctx, aggnonce, v, tweak1..v, is_xonly_t1..v, m)* - Return (pubnonce, Sign(secnonce, secshare, my_id, session_ctx)) -[^coordinator-id-sentinel]: *COORDINATOR_ID* is a sentinel value (not an actual participant identifier) used to track the source of *aggothernonce* for error attribution. If *NonceAgg* fails, the coordinator is blamed for providing an invalid *aggothernonce*. In the reference implementation, *COORDINATOR_ID* is represented as `None`. +[^coordinator-id-sentinel]: *COORDINATOR_ID* is a sentinel value (not an actual participant identifier) used to track the source of *aggothernonce* for error attribution. If *NonceAgg* fails, the coordinator is blamed for providing an invalid *aggothernonce*. In the reference implementation, *COORDINATOR_ID* is represented as *None*. ### Tweaking Definition @@ -712,18 +710,18 @@ So, the (X-only) final public key is where tacci is computed by TweakCtxInit and ApplyTweak as follows:

-  tacc0 = 0
-  tacci = ti + gi-1·tacci-1 for i=1..v
+  tacc0 = 0  (mod ord)
+  tacci = ti + gi-1·tacci-1  (mod ord) for i=1..v
 
for which it holds that
-  gv·taccv = sumi=1..v ti · prodj=i..v gj
+  gv·taccv = sumi=1..v ti · prodj=i..v gj  (mod ord)
 
*TweakCtxInit* and *ApplyTweak* compute
-  gacc0 = 1
-  gacci = gi-1 · gacci-1 for i=1..v
+  gacc0 = 1  (mod ord)
+  gacci = gi-1 · gacci-1  (mod ord) for i=1..v
 
So we can rewrite above equation for the final public key as
@@ -745,7 +743,7 @@ Intuitively, *gacci* tracks accumulated sign flipping and *tacci
 
 As explained in [Negation Of The Secret Share When Signing](#negation-of-the-secret-share-when-signing) the signer uses a possibly negated secret share
 
-  d = gv·gaccv·d'
+  d = gv·gaccv·d'  (mod ord)
 
when producing a partial signature to ensure that the aggregate signature will correspond to a threshold public key with even Y coordinate. @@ -785,6 +783,7 @@ This document proposes a standard for the FROST threshold signature scheme that ## Changelog +- *0.3.5* (2026-01-25): Update secp256k1lab to latest version, remove stub file, and fix formatting in the BIP text. - *0.3.4* (2026-01-01): Add an example file to the reference code. - *0.3.3* (2025-12-29): Replace the lengthy Introduction section with a concise Motivation section. - *0.3.2* (2025-12-20): Use 2-of-3 keys in test vectors. @@ -796,11 +795,11 @@ This document proposes a standard for the FROST threshold signature scheme that - Revert back to initializing *TweakCtxInit* with threshold public key instead of *pubshares* - *0.2.3* (2025-11-25): Sync terminologies with the ChillDKG BIP. - *0.2.2* (2025-11-11): Remove key generation test vectors as key generation is out of scope for this specification. -- *0.2.1* (2025-11-10): Vendor secp256k1lab library to provide `Scalar` and `GE` primitives. Restructure reference implementation into a Python package layout. +- *0.2.1* (2025-11-10): Vendor secp256k1lab library to provide *Scalar* and *GE* primitives. Restructure reference implementation into a Python package layout. - *0.2.0* (2025-04-11): Includes minor fixes and the following major changes: - Initialize *TweakCtxInit* using individual *pubshares* instead of the threshold public key. - Add Python script to automate generation of test vectors. - - Represent participant identifiers as 4-byte integers in the range `0..n - 1` (inclusive). + - Represent participant identifiers as 4-byte integers in the range *0..n - 1* (inclusive). - *0.1.0* (2024-07-31): Publication of draft BIP on the bitcoin-dev mailing list ## Acknowledgments diff --git a/bip-frost-signing/all.sh b/bip-frost-signing/all.sh index f94bf3c558..82643c3502 100755 --- a/bip-frost-signing/all.sh +++ b/bip-frost-signing/all.sh @@ -10,8 +10,10 @@ check_availability() { } check_availability markdownlint-cli2 +check_availability typos markdownlint-cli2 ../bip-frost-signing.md --config ./.markdownlint.json || true +typos ../bip-frost-signing.md . || true cd python || exit 1 ./tests.sh From 6ea53f248b6d8350262fad4aba2f69fd0b2dd93d Mon Sep 17 00:00:00 2001 From: siv2r Date: Wed, 28 Jan 2026 21:27:42 +0530 Subject: [PATCH 5/6] python: add license file and re-write to error msg --- bip-frost-signing.md | 5 +++-- bip-frost-signing/COPYING | 21 +++++++++++++++++++ bip-frost-signing/python/frost_ref/signing.py | 5 ++--- .../python/vectors/det_sign_vectors.json | 2 +- .../python/vectors/sign_verify_vectors.json | 2 +- 5 files changed, 28 insertions(+), 7 deletions(-) create mode 100644 bip-frost-signing/COPYING diff --git a/bip-frost-signing.md b/bip-frost-signing.md index 146acd491e..68a8bcc578 100644 --- a/bip-frost-signing.md +++ b/bip-frost-signing.md @@ -6,8 +6,8 @@ Comments-URI: Status: Draft Type: Standards Track Assigned: ? -License: CC0-1.0 or MIT -Post-History: https://groups.google.com/g/bitcoindev/c/PeMp2HQl-H4/m/AcJtK0aKAwAJ +License: CC0-1.0 +Discussion: 2024-07-31: https://groups.google.com/g/bitcoindev/c/PeMp2HQl-H4/m/AcJtK0aKAwAJ Requires: 32, 340, 341 ``` @@ -783,6 +783,7 @@ This document proposes a standard for the FROST threshold signature scheme that ## Changelog +- *0.3.6* (2026-01-28): Add MIT license file for reference code and other auxiliary files. - *0.3.5* (2026-01-25): Update secp256k1lab to latest version, remove stub file, and fix formatting in the BIP text. - *0.3.4* (2026-01-01): Add an example file to the reference code. - *0.3.3* (2025-12-29): Replace the lengthy Introduction section with a concise Motivation section. diff --git a/bip-frost-signing/COPYING b/bip-frost-signing/COPYING new file mode 100644 index 0000000000..e4998218e3 --- /dev/null +++ b/bip-frost-signing/COPYING @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2024-2026 Sivaram Dhakshinamoorthy + +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-frost-signing/python/frost_ref/signing.py b/bip-frost-signing/python/frost_ref/signing.py index 9961c07700..709ed10386 100644 --- a/bip-frost-signing/python/frost_ref/signing.py +++ b/bip-frost-signing/python/frost_ref/signing.py @@ -47,6 +47,7 @@ def __init__(self, signer_id: Optional[int], contrib: ContribKind) -> None: def derive_interpolating_value(ids: List[int], my_id: int) -> Scalar: assert my_id in ids assert 0 <= my_id < 2**32 + assert len(set(ids)) == len(ids) num = Scalar(1) deno = Scalar(1) for curr_id in ids: @@ -95,9 +96,7 @@ def validate_signers_ctx(signers_ctx: SignersContext) -> None: except ValueError: raise InvalidContributionError(i, "pubshare") if len(set(ids)) != len(ids): - raise ValueError( - "The participant identifier list must contain unique elements." - ) + raise ValueError("The participant identifier list contains duplicate elements.") if derive_thresh_pubkey(ids, pubshares) != thresh_pk: raise ValueError("The provided key material is incorrect.") diff --git a/bip-frost-signing/python/vectors/det_sign_vectors.json b/bip-frost-signing/python/vectors/det_sign_vectors.json index 1ef249a5c2..19df817d02 100644 --- a/bip-frost-signing/python/vectors/det_sign_vectors.json +++ b/bip-frost-signing/python/vectors/det_sign_vectors.json @@ -258,7 +258,7 @@ "signer_index": 0, "error": { "type": "ValueError", - "message": "The participant identifier list must contain unique elements." + "message": "The participant identifier list contains duplicate elements." }, "comment": "The participant identifier list contains duplicate elements" }, diff --git a/bip-frost-signing/python/vectors/sign_verify_vectors.json b/bip-frost-signing/python/vectors/sign_verify_vectors.json index ea0a0231ab..3fca0693fd 100644 --- a/bip-frost-signing/python/vectors/sign_verify_vectors.json +++ b/bip-frost-signing/python/vectors/sign_verify_vectors.json @@ -218,7 +218,7 @@ "secnonce_index": 0, "error": { "type": "ValueError", - "message": "The participant identifier list must contain unique elements." + "message": "The participant identifier list contains duplicate elements." }, "comment": "The participant identifier list contains duplicate elements" }, From 5d87f5626edf3885ab258fe7ef3b438814fdeb96 Mon Sep 17 00:00:00 2001 From: siv2r Date: Thu, 29 Jan 2026 19:09:26 +0530 Subject: [PATCH 6/6] bip-frost-signing: add info about OP_CHECKSIGADD and minor edits --- bip-frost-signing.md | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/bip-frost-signing.md b/bip-frost-signing.md index 68a8bcc578..2c0f4e9abe 100644 --- a/bip-frost-signing.md +++ b/bip-frost-signing.md @@ -22,11 +22,9 @@ The accompanying source code is licensed under the [MIT license](https://opensou ## Motivation - +The FROST signature scheme enables threshold Schnorr signatures. In a *t-of-n* threshold configuration, any *t*[^t-edge-cases] participants can cooperatively produce a Schnorr signature that is indistinguishable from a signature produced by a single signer. FROST signatures are unforgeable as long as fewer than *t* participants are corrupted. The signing protocol remains functional provided that at least *t* honest participants retain access to their secret key shares. -The FROST signature scheme enables threshold Schnorr signatures. In a *t*-of-*n* threshold configuration, any *t*[^t-edge-cases] participants can cooperatively produce a Schnorr signature that is indistinguishable from a signature produced by a single signer. FROST signatures are unforgeable as long as fewer than *t* participants are corrupted. The signing protocol remains functional provided that at least *t* honest participants retain access to their secret key shares. - -[^t-edge-cases]: While *t = n* and *t = 1* are in principle supported, simpler alternatives are available in these cases. In the case *t = n*, using a dedicated *n*-of-*n* multi-signature scheme such as MuSig2 (see [BIP327][bip327]) instead of FROST avoids the need for an interactive DKG. The case *t = 1* can be realized by letting one signer generate an ordinary [BIP340][bip340] key pair and transmitting the key pair to every other signer, who can check its consistency and then simply use the ordinary [BIP340][bip340] signing algorithm. Signers still need to ensure that they agree on a key pair. +[^t-edge-cases]: While *t = n* and *t = 1* are in principle supported, simpler alternatives are available in these cases. In the case *t = n*, using a dedicated *n-of-n* multi-signature scheme such as MuSig2 (see [BIP327][bip327]) instead of FROST avoids the need for an interactive DKG. The case *t = 1* can be realized by letting one signer generate an ordinary [BIP340][bip340] key pair and transmitting the key pair to every other signer, who can check its consistency and then simply use the ordinary [BIP340][bip340] signing algorithm. Signers still need to ensure that they agree on a key pair. The IRTF has published [RFC 9591][rfc9591], which specifies the FROST signing protocol for several elliptic curve and hash function combinations, including secp256k1 with SHA-256, the cryptographic primitives used in Bitcoin. However, the signatures produced by RFC 9591 are incompatible with BIP340 Schnorr signatures due to the X-only public keys introduced in BIP340. Additionally, RFC 9591 does not specify key tweaking mechanisms, which are essential for Bitcoin applications such as [BIP32][bip32] key derivation and [BIP341][bip341] Taproot. This document addresses these limitations by specifying a BIP340-compatible variant of FROST signing protocol that supports key tweaking. @@ -36,6 +34,11 @@ This document specifies the FROST3 variant[^frost3-security]. The FROST3 signing [^frost3-security]: The FROST3 signing scheme has been proven existentially unforgeable for both trusted dealer and distributed key generation setups. When using a trusted dealer for key generation, security reduces to the standard One-More Discrete Logarithm (OMDL) assumption. When instantiated with a distributed key generation protocol such as SimplPedPoP, security reduces to the Algebraic One-More Discrete Logarithm (AOMDL) assumption. +The on-chain footprint of a FROST Taproot output is essentially a single BIP340 public key, and a transaction spending the output only requires a single signature cooperatively produced by at least *t* signers. This is **more compact** and has **lower verification cost** than each signer providing an individual public key and signature, as would be required by an *t-of-n* policy implemented using `OP_CHECKSIGADD` as introduced in [BIP342][bip342]. +As a side effect, the number *n* of signers is not limited by any consensus rules when using FROST. + +Moreover, FROST offers a **higher level of privacy** than `OP_CHECKSIGADD`: FROST Taproot outputs are indistinguishable for a blockchain observer from regular, single-signer Taproot outputs even though they are actually controlled by multiple signers. By tweaking the threshold public key, the shared Taproot output can have script spending paths that are hidden unless used. + ## Overview Implementers must make sure to understand this section thoroughly to avoid subtle mistakes that may lead to catastrophic failure. @@ -56,7 +59,7 @@ Similarly, the test vectors that exercise the unimplemented features should be r ### Key Material and Setup -A FROST key generation protocol configures a group of *n* participants with a *threshold public key* (representing a *t*-of-*n* threshold policy). +A FROST key generation protocol configures a group of *n* participants with a *threshold public key* (representing a *t-of-n* threshold policy). The corresponding *threshold secret key* is Shamir secret-shared among all *n* participants, where each participant holds a distinct long-term *secret share*. This ensures that any subset of at least *t* participants can jointly run the FROST signing protocol to produce a signature under the *threshold secret key*. @@ -66,7 +69,7 @@ This protocol distinguishes between two public key formats: *plain public keys* Key generation protocols produce *public shares* and *threshold public keys* in the plain format. During signing, we conditionally negate *secret shares* to ensure the resulting threshold-signature verifies under the corresponding *X-only threshold public key*. > [!WARNING] -> Key generation protocols must commit the *threshold public key* to an unspendable script path as recommended in [BIP341](https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki#cite_note-23). This prevents a malicious party from embedding a hidden script path during key generation that would allow them to bypass the *t*-of-*n* threshold policy. +> Key generation protocols must commit the *threshold public key* to an unspendable script path as recommended in [BIP341](https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki#cite_note-23). This prevents a malicious party from embedding a hidden script path during key generation that would allow them to bypass the *t-of-n* threshold policy. #### Protocol Parties and Network Setup @@ -107,8 +110,8 @@ Signers begin the signing session by running *NonceGen* to compute their *secnon Each signer sends their *pubnonce* to the coordinator, who aggregates them using *NonceAgg* to produce an aggregate nonce and sends it back to all signers. [^nonce-serialization-detail]: We treat the *secnonce* and *pubnonce* as grammatically singular even though they include serializations of two scalars and two elliptic curve points, respectively. -This treatment may be confusing for readers familiar with the MuSig2 paper. -However, serialization is a technical detail that is irrelevant for users of MuSig2 interfaces. +This treatment may be confusing for readers familiar with the [FROST paper][olaf]. +However, serialization is a technical detail that is irrelevant for users of FROST interfaces. **Second broadcast round:** At this point, every signer has the required data to sign, which, in the algorithms specified below, is stored in a data structure called [Session Context](#session-context). @@ -241,10 +244,10 @@ The reference code vendors the secp256k1lab library to handle underlying arithme | Notation | secp256k1lab | Description | | --- | --- | --- | | *p* | *FE.SIZE* | Field element size | -| *ord* | *GE.ORDER* | Group order | +| *ord* | *GE.ORDER*, *Scalar.SIZE* | Group order | | *G* | *G* | The secp256k1 generator point | | *inf_point* | *GE()* | The infinity point | -| *is_infinity(P)* | *P.infinity()* | Returns whether *P* is the point at infinity | +| *is_infinity(P)* | *P.infinity* | Returns whether *P* is the point at infinity | | *x(P)* | *P.x* | Returns the x-coordinate of a non-infinity point *P*, in the range *[0, p−1]* | | *y(P)* | *P.y* | Returns the y-coordinate of a non-infinity point *P*, in the range *[0, p-1]* | | *has_even_y(P)* | *P.has_even_y()* | Returns whether *P* has an even y-coordinate | @@ -347,8 +350,8 @@ Internal Algorithm *DeriveInterpolatingValue(id1..u, my_id):* The Tweak Context is a data structure consisting of the following elements: - The point *Q* representing the potentially tweaked threshold public key: a *GE* -- The accumulated tweak *tacc*: a *Scalar* - The value *gacc*: *Scalar(1)* or *Scalar(-1)* +- The accumulated tweak *tacc*: a *Scalar* We write "Let *(Q, gacc, tacc) = tweak_ctx*" to assign names to the elements of a Tweak Context. @@ -426,12 +429,12 @@ Algorithm *NonceGen(secshare, pubshare, thresh_pk, m, extra_in)*: - Fail if *k1 = Scalar(0)* or *k2 = Scalar(0)* - Let *R\*,1 = k1 · G*, *R\*,2 = k2 · G* - Let *pubnonce = cbytes(R\*,1) || cbytes(R\*,2)* -- Let *secnonce = bytes(32, k1) || bytes(32, k2)*[^secnonce-ser] +- Let *secnonce = scalar_to_bytes(k1) || scalar_to_bytes(k2)*[^secnonce-ser] - Return *(secnonce, pubnonce)* [^sk-xor-rand]: The random data is hashed (with a unique tag) as a precaution against situations where the randomness may be correlated with the secret signing share itself. It is xored with the secret share (rather than combined with it in a hash) to reduce the number of operations exposed to the actual secret share. -[^secnonce-ser]: The algorithms as specified here assume that the *secnonce* is stored as a 64-byte array using the serialization *secnonce = bytes(32, k1) || bytes(32, k2)*. The same format is used in the reference implementation and in the test vectors. However, since the *secnonce* is (obviously) not meant to be sent over the wire, compatibility between implementations is not a concern, and this method of storing the *secnonce* is merely a suggestion. The *secnonce* is effectively a local data structure of the signer which comprises the value pair *(k1, k2)*, and implementations may choose any suitable method to carry it from *NonceGen* (first communication round) to *Sign* (second communication round). In particular, implementations may choose to hide the *secnonce* in internal state without exposing it in an API explicitly, e.g., in an effort to prevent callers from reusing a *secnonce* accidentally. +[^secnonce-ser]: The algorithms as specified here assume that the *secnonce* is stored as a 64-byte array using the serialization *secnonce = scalar_to_bytes(k1) || scalar_to_bytes(k2)*. The same format is used in the reference implementation and in the test vectors. However, since the *secnonce* is (obviously) not meant to be sent over the wire, compatibility between implementations is not a concern, and this method of storing the *secnonce* is merely a suggestion. The *secnonce* is effectively a local data structure of the signer which comprises the value pair *(k1, k2)*, and implementations may choose any suitable method to carry it from *NonceGen* (first communication round) to *Sign* (second communication round). In particular, implementations may choose to hide the *secnonce* in internal state without exposing it in an API explicitly, e.g., in an effort to prevent callers from reusing a *secnonce* accidentally. [^max-msg-len]: In theory, the allowed message size is restricted because SHA256 accepts byte strings only up to size of 2^61-1 bytes (and because of the 8-byte length encoding). @@ -442,7 +445,7 @@ Algorithm *NonceAgg(pubnonce1..u, id1..u)*: - Inputs: - The number *u* of signing participants: an integer with *t ≤ u ≤ n* - The list of participant public nonces *pubnonce1..u*: *u* 66-byte array, each an output of *NonceGen* - - The list of participant identifiers *id1..u*: *u* integers, each with 0 ≤ *idi* < *n* + - The list of participant identifiers *id1..u*: *u* integers, each with *0 ≤ idi ≤ n-1* - For *j = 1 .. 2*: - For *i = 1 .. u*: - Let *Ri,j = cpoint(pubnoncei[(j-1)\*33:j\*33])*; fail if that fails and blame signer *idi* for invalid *pubnonce* @@ -525,7 +528,7 @@ Algorithm *PartialSigVerify(psig, pubnonce1..u, signers_ctx, tweak1..u
*: *u* 66-byte arrays, each an output of *NonceGen* + - The list of public nonces *pubnonce1..u*: *u* 66-byte arrays, each an output of *NonceGen* - The *signers_ctx*: a [Signers Context](#signers-context) data structure - The number *v* of tweaks with *0 ≤ v < 2^32* - The list of tweaks *tweak1..v*: *v* 32-byte arrays, each a serialized scalar @@ -811,7 +814,7 @@ We thank Jonas Nick, Tim Ruffing, Jesse Posner, and Sebastian Falbesoner for the [bip32]: https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki [bip340]: https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki [bip341]: https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki - +[bip342]: https://github.com/bitcoin/bips/blob/master/bip-0342.mediawiki [bip327]: https://github.com/bitcoin/bips/blob/master/bip-0327.mediawiki [frost1]: https://eprint.iacr.org/2020/852 [frost2]: https://eprint.iacr.org/2021/1375