Browse Source

update bip-frost-signing to commit f5ea4a5b

add_frost_channel_encryption
zebra-lucky 4 months ago
parent
commit
42cd66a43d
  1. 39
      src/jmclient/frost_clients.py
  2. 9
      src/jmfrost/__init__.py
  3. 751
      src/jmfrost/frost_ref/README.md
  4. 298
      src/jmfrost/frost_ref/reference.py
  5. 1
      src/jmfrost/frost_ref/utils/__init__.py
  6. 93
      src/jmfrost/frost_ref/utils/bip340.py
  7. 4
      test/jmclient/test_frost_clients.py
  8. 85
      test/jmfrost/test_frost_ref.py
  9. 48
      test/jmfrost/trusted_keygen.py
  10. 384
      test/jmfrost/vectors/det_sign_vectors.json
  11. 110
      test/jmfrost/vectors/keygen_vectors.json
  12. 64
      test/jmfrost/vectors/nonce_agg_vectors.json
  13. 34
      test/jmfrost/vectors/nonce_gen_vectors.json
  14. 291
      test/jmfrost/vectors/sig_agg_vectors.json
  15. 524
      test/jmfrost/vectors/sign_verify_vectors.json
  16. 378
      test/jmfrost/vectors/tweak_vectors.json

39
src/jmclient/frost_clients.py

@ -36,14 +36,14 @@ from jmfrost.chilldkg_ref import simplpedpop
from jmfrost.chilldkg_ref import vss
from jmfrost.secp256k1lab import secp256k1
from jmfrost.frost_ref import reference as frost
from jmfrost.frost_ref.utils.bip340 import schnorr_verify
from jmfrost.secp256k1lab.bip340 import schnorr_verify
jlog = get_log()
def calc_tweak(pubshares, ids_bytes, h=b''):
pubkey = frost.derive_group_pubkey(pubshares, ids_bytes)
def calc_tweak(pubshares, ids, h=b''):
pubkey = frost.derive_group_pubkey(pubshares, ids)
return frost.tagged_hash("TapTweak", pubkey[1:] + h)
@ -734,7 +734,7 @@ class FROSTClient(DKGClient):
self.my_id = None
for i, p in enumerate(self.hostpubkeys):
if p == hostpubkey:
self.my_id = (i+1).to_bytes(32, 'big')
self.my_id = i
break
assert self.my_id is not None, (f'unknown hostpubkey '
f'{hostpubkey.hex()}')
@ -781,7 +781,7 @@ class FROSTClient(DKGClient):
self.my_id = None
for i, p in enumerate(self.hostpubkeys):
if p == hostpubkey:
self.my_id = (i+1).to_bytes(32, 'big')
self.my_id = i
break
assert self.my_id is not None
hostpubkeyhash = sha256(hostpubkey).digest()
@ -881,13 +881,10 @@ class FROSTClient(DKGClient):
if not pub_nonce:
continue
pub_nonces.append(pub_nonce)
ids.append(i+1)
ids.append(i)
coordinator.ids = ids.copy()
ids_bytes = []
for i in ids:
ids_bytes.append(i.to_bytes(32, 'big'))
assert len(ids) == self.t
coordinator.nonce_agg = frost.nonce_agg(pub_nonces, ids_bytes)
coordinator.nonce_agg = frost.nonce_agg(pub_nonces, ids)
jlog.debug('frost_agg1 run')
return (coordinator.nonce_agg, coordinator.dkg_session_id, ids,
coordinator.msg)
@ -911,18 +908,14 @@ class FROSTClient(DKGClient):
_pubshares = dkg._dkg_pubshares.get(dkg_session_id)
pubshares = []
for i, pubshare in enumerate(_pubshares):
if (i+1) not in ids:
if i not in ids:
continue
pubshares.append(pubshare)
ids_bytes = []
for i in ids:
ids_bytes.append(i.to_bytes(32, 'big'))
tweak = calc_tweak(pubshares, ids_bytes)
tweak = calc_tweak(pubshares, ids)
tweaks = [tweak]
is_xonly = [True]
session_ctx = frost.SessionContext(
nonce_agg, ids_bytes, pubshares, tweaks, is_xonly, msg)
nonce_agg, ids, pubshares, tweaks, is_xonly, msg)
session.partial_sig = partial_sig = frost.sign(
session.sec_nonce, secshare, self.my_id, session_ctx)
jlog.debug('frost_round2 run')
@ -965,18 +958,16 @@ class FROSTClient(DKGClient):
raise Exception(f'pubshares not found for '
f'{dkg_session_id.hex()}')
ids = coordinator.ids
ids_bytes = []
pubshares = []
for i, pubshare in enumerate(_pubshares):
if (i+1) not in ids:
if i not in ids:
continue
pubshares.append(pubshare)
ids_bytes.append((i+1).to_bytes(32, 'big'))
tweak = calc_tweak(pubshares, ids_bytes)
tweak = calc_tweak(pubshares, ids)
tweaks = [tweak]
is_xonly = [True]
session_ctx = frost.SessionContext(
coordinator.nonce_agg, ids_bytes, pubshares, tweaks,
coordinator.nonce_agg, ids, pubshares, tweaks,
is_xonly, coordinator.msg)
partial_sigs = []
for pubkey in self.hostpubkeys:
@ -986,9 +977,9 @@ class FROSTClient(DKGClient):
if 'partial_sig' in session:
partial_sigs.append(session['partial_sig'])
sig = frost.partial_sig_agg(
partial_sigs, ids_bytes, session_ctx)
partial_sigs, ids, session_ctx)
tweak_ctx = frost.group_pubkey_and_tweak(
pubshares, ids_bytes, tweaks, is_xonly)
pubshares, ids, tweaks, is_xonly)
Q , _, _ = tweak_ctx
tweaked_pubkey = frost.xbytes(Q)
if not schnorr_verify(coordinator.msg, tweaked_pubkey, sig):

9
src/jmfrost/__init__.py

@ -13,8 +13,9 @@
# frost_ref is from
# https://github.com/siv2r/bip-frost-signing
#
# commit 2f249969f84c1533671c521bf864fddecb371018
# Author: siv2r <siv2ram@gmail.com>
# Date: Sat Dec 7 17:13:54 2024 +0530
#commit f5ea4a5b58c13c234f10b96b620d34e926b49127
#Merge: a599e50 8b2eaa9
#Author: Sivaram <siv2ram@gmail.com>
#Date: Sat Jun 21 19:59:11 2025 +0530
#
# spec: add header, changelog, and acknowledgements
# Merge pull request #23 from theStack/integrate_secp256k1lab

751
src/jmfrost/frost_ref/README.md

@ -0,0 +1,751 @@
```
BIP:
Title: FROST Signing for BIP340-compatible Threshold Signatures
Author: Sivaram Dhakshinamoorthy <siv2ram@gmail.com>
Status: Draft
License: CC0-1.0
License-Code: MIT
Type: Informational
Created:
Post-History:
Comments-URI:
```
# FROST Signing for BIP340-compatible Threshold Signatures
### Abstract
This document proposes a standard for the Flexible Round-Optimized Schnorr Threshold (FROST) signing protocol. The standard is compatible with [BIP340](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki) public keys and signatures. It supports _tweaking_, which allows deriving [BIP32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki) child keys from the group public key and creating [BIP341](https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki) Taproot outputs with key and script paths.
### Copyright
This document is licensed under the 3-clause BSD license.
## Introduction
This document proposes the FROST signing protocol based on the FROST3 variant (see section 2.3) introduced in ROAST[[RRJSS22](https://eprint.iacr.org/2022/550)], instead of the original FROST[[KG20](https://eprint.iacr.org/2020/852)]. Key generation for FROST signing is out of scope for this document. However, we specify the requirements that a key generation method must satisfy to be compatible with this signing protocol.
Many sections of this document have been directly copied or modified from [BIP327](https://github.com/bitcoin/bips/blob/master/bip-0327.mediawiki) due to the similarities between the FROST3 and [MuSig2](https://eprint.iacr.org/2020/1261.pdf) signature schemes.
### Motivation
The FROST signature scheme [[KG20](https://eprint.iacr.org/2020/852),[CKM21](https://eprint.iacr.org/2021/1375),[BTZ21](https://eprint.iacr.org/2022/833),[CGRS23](https://eprint.iacr.org/2023/899)] enables _t-of-n_ Schnorr threshold signatures, in which a threshold _t_ of some set of _n_ signers is required to produce a signature.
FROST remains unforgeable as long as at most _t-1_ signers are compromised, and remains functional as long as _t_ honest signers do not lose their secret key material. It supports any choice of _t_ as long as _1 ≤ t ≤ n_.[^t-edge-cases]
The primary motivation is to create a standard that allows users of different software projects to jointly control Taproot outputs ([BIP341](https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki)).
Such an output contains a public key which, in this case, would be the group public key derived from the public shares of threshold signers.
It can be spent using FROST to produce a signature for the key-based spending path.
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 _threshold_ signers. This is **more compact** and has **lower verification cost** than signers providing _n_ individual public keys and _t_ signatures, as would be required by an _t-of-n_ policy implemented using <code>OP_CHECKSIGADD</code> as introduced in ([BIP342](https://github.com/bitcoin/bips/blob/master/bip-0342.mediawiki)).
As a side effect, the numbers _t_ and _n_ of signers are not limited by any consensus rules when using FROST.
Moreover, FROST offers a **higher level of privacy** than <code>OP_CHECKSIGADD</code>: 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 a group public key, the shared Taproot output can have script spending paths that are hidden unless used.
There are threshold-signature schemes other than FROST that are fully compatible with Schnorr signatures.
The FROST variant proposed below stands out by combining all the following features:
* **Two Communication Rounds**: FROST is faster in practice than other threshold-signature schemes [[GJKR03](https://link.springer.com/chapter/10.1007/3-540-36563-x_26)] which requires at least three rounds, particularly when signers are connected through high-latency anonymous links. Moreover, the need for fewer communication rounds simplifies the algorithms and reduces the probability that implementations and users make security-relevant mistakes.
* **Efficiency over Robustness**: FROST trades off the robustness property for network efficiency (fewer communication rounds), requiring the protocol to be aborted in the case of any misbehaving participant.
* **Provable security**: FROST3 with an idealized key generation (i.e., trusted setup) has been [proven existentially unforgeable](https://eprint.iacr.org/2022/550.pdf) under the one-more discrete logarithm (OMDL) assumption (instead of the discrete logarithm assumption required for single-signer Schnorr signatures) in the random oracle model (ROM).
### Design
* **Compatibility with BIP340**: The group public key and participant public shares produced by a compatible key generation algorithm MUST be _plain_ public keys in compressed format. In this proposal, the signature output at the end of the signing protocol is a BIP340 signature, which passes BIP340 verification for the BIP340 X-only version of the group public key and a message.
* **Tweaking for BIP32 derivations and Taproot**: This proposal supports tweaking group public key and signing for this tweaked group public key. We distinguish two modes of tweaking: _Plain_ tweaking can be used to derive child group public keys per [BIP32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki)._X-only_ tweaking, on the other hand, allows creating a [BIP341](https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki) tweak to add script paths to a Taproot output. See [tweaking the group public key](./README.md#tweaking-group-public-key) below for details.
* **Non-interactive signing with preprocessing**: The first communication round, exchanging the nonces, can happen before the message or the exact set of signers is determined. Once the parameters of the signing session are finalized, the signers can send partial signatures without additional interaction.
* **Partial signature independent of order**: The output of the signing algorithm remains consistent regardless of the order in which participant identifiers and public shares are used during the session context initialization. This property is inherent when combining Shamir shares to derive any value.
* **Third-party nonce and partial signature aggregation**: Instead of every signer sending their nonce and partial signature to every other signer, it is possible to use an untrusted third-party _aggregator_ in order to reduce the communication complexity from quadratic to linear in the number of signers. In each of the two rounds, the aggregator collects all signers' contributions (nonces or partial signatures), aggregates them, and broadcasts the aggregate back to the signers. A malicious aggregator can force the signing session to fail to produce a valid Schnorr signature but cannot negatively affect the unforgeability of the scheme.
* **Partial signature verification**: If any signer sends a partial signature contribution that was not created by honestly following the signing protocol, the signing session will fail to produce a valid Schnorr signature. This proposal specifies a partial signature verification algorithm to identify disruptive signers. It is incompatible with third-party nonce aggregation because the individual nonce is required for partial verification.
* **Size of the nonce**: In the FROST3 variant, each signer's nonce consists of two elliptic curve points.
## 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 Generation
We distinguish between two public key types, namely _plain public keys_, the key type traditionally used in Bitcoin, and _X-only public keys_.
Plain public keys are byte strings of length 33 (often called _compressed_ format).
In contrast, X-only public keys are 32-byte strings defined in [BIP340](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki).
FROST generates signatures that are verifiable as if produced by a single signer using a secret key _s_ with the corresponding public key. As a threshold signing protocol, the group secret key _s_ is shared among all _MAX_PARTICIPANTS_ participants using Shamir's secret sharing, and at least _MIN_PARTICIPANTS_ participants must collaborate to issue a valid signature.
&emsp;&ensp;_MIN_PARTICIPANTS_ is a positive non-zero integer lesser than or equal to _MAX_PARTICIPANTS_
&emsp;&ensp;_MAX_PARTICIPANTS_ MUST be a positive integer less than 2^32.
In particular, FROST signing assumes each participant is configured with the following information:
- An identifier _id_, which is an integer in the range _[0, MAX_PARTICIPANTS-1]_ and MUST be distinct from the identifier of every other participant.
<!-- REVIEW we haven't introduced participant identifier yet. So, don't use them here -->
- A secret share _secshare<sub>id</sub>_, which is a positive non-zero integer less than the secp256k1 curve order. This value represents the _i_-th Shamir secret share of the group secret key _s_. In particular, _secshare<sub>id</sub>_ is the value _f(id+1)_ on a secret polynomial _f_ of degree _(MIN_PARTICIPANTS - 1)_, where _s_ is _f(0)_.
- A Group public key _group_pk_, which is point on the secp256k1 curve.
- A public share _pubshare<sub>id</sub>_, which is point on the secp256k1 curve.
> [!NOTE]
> The definitions for the secp256k1 curve and its order can be found in the [Notation section](./README.md#notation).
As key generation for FROST signing is beyond the scope of this document, we do not specify how this information is configured and distributed to the participants. Generally, there are two possible key generation mechanisms: one involves a single, trusted dealer (see Appendix D of [FROST RFC draft](https://datatracker.ietf.org/doc/draft-irtf-cfrg-frost/15/)), and the other requires performing a distributed key generation protocol (see [BIP FROST DKG draft](https://github.com/BlockstreamResearch/bip-frost-dkg)).
For a key generation mechanism to be compatible with FROST signing, the participant information it generates MUST successfully pass both the _ValidateGroupPubkey_ and _ValidatePubshares_ functions (see [Key Generation Compatibility](./README.md#key-generation-compatibility)).
> [!IMPORTANT]
> It should be noted that while passing these functions ensures functional compatibility, it does not guarantee the security of the key generation mechanism.
### General Signing Flow
FROST signing is designed to be executed by a predetermined number of signer participants, referred to as _NUM_PARTICIPANTS_. This value is a positive non-zero integer that MUST be at least _MIN_PARTICIPANTS_ and MUST NOT exceed _MAX_PARTICIPANTS_. Therefore, the selection of signing participants from the participant group must be performed outside the signing protocol, prior to its initiation.
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:**
The signers start the signing session by running _NonceGen_ to compute _secnonce_ and _pubnonce_.[^nonce-serialization-detail]
Then, the signers broadcast their _pubnonce_ to each other and run _NonceAgg_ to compute an aggregate nonce.
**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](./README.md#session-context).
Every signer computes a partial signature by running _Sign_ with the participant identifier, the secret share, the _secnonce_ and the session context.
Then, the signers broadcast their partial signatures to each other and run _PartialSigAgg_ to obtain the final signature.
If all signers behaved honestly, the result passes [BIP340](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki) verification.
Both broadcast rounds can be optimized by using an aggregator who collects all signers' nonces or partial signatures, aggregates them using _NonceAgg_ or _PartialSigAgg_, respectively, and broadcasts the aggregate result back to the signers. A malicious aggregator can force the signing session to fail to produce a valid Schnorr signature but cannot negatively affect the unforgeability of the scheme, i.e., even a malicious aggregator colluding with all but one signer cannot forge a signature.
> [!IMPORTANT]
> 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](./README.md#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
> [!IMPORTANT]
> _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 _k<sub>1</sub>_ and _k<sub>2</sub>_ **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_, _group_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, it is beneficial to generate and send a _pubnonce_ before the other signers, their _pubshare_, or the message to sign is known.
In this case, only the available arguments are provided to the _NonceGen_ algorithm.
After this preprocessing phase, the _Sign_ algorithm can be run immediately when the message and set of signers 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.
Instead of every signer broadcasting their _pubnonce_ to every other signer, the signers can send their _pubnonce_ to a single aggregator node that runs _NonceAgg_ and sends the _aggnonce_ back to the signers.
This technique reduces the overall communication.
A malicious aggregator can force the signing session to fail to produce a valid Schnorr signature but cannot negatively affect the unforgeability of the scheme.
In general, FROST signers are stateful in the sense that they first generate _secnonce_ and then need to store it until they receive the other signers' _pubnonces_ or the _aggnonce_.
However, it is possible for one of the signers to be stateless.
This signer waits until it receives the _pubnonce_ of all the other signers and until session parameters such as a message to sign, participant identifiers, participant public shares, and tweaks are determined.
Then, the signer can run _NonceGen_, _NonceAgg_ and _Sign_ in sequence and send out its _pubnonce_ along with its partial signature.
Stateless signers may want to consider signing deterministically (see [Modifications to Nonce Generation](./README.md#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 aggregator 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 aggregator) 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 aggregator.
While this is possible for some particular misbehavior of the aggregator, it is not guaranteed that a malicious aggregator can be identified.
More specifically, a malicious aggregator (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.
### Tweaking the Group Public Key
The group public key can be _tweaked_, which modifies the key as defined in the [Tweaking Definition](./README.md#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 group 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 aggregate public key and some other information.
Depending on the specific scheme that is used for tweaking, either the plain or the X-only aggregate public key is required.
For example, to do [BIP32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki) derivation, you call _GetPlainPubkey_ to be able to compute the tweak, whereas [BIP341](https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki) TapTweaks require X-only public keys that are obtained with _GetXonlyPubkey_.
The tweak mode provided to _ApplyTweak_ depends on the application:
Plain tweaking can be used to derive child public keys from an aggregate public key using [BIP32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki).
On the other hand, X-only tweaking is required for Taproot tweaking per [BIP341](https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki).
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](./README.md#general-signing-flow)).
### Notation
The following conventions are used, with constants as defined for [secp256k1](https://www.secg.org/sec2-v2.pdf). We note that adapting this proposal to other elliptic curves is not straightforward and can result in an insecure scheme.
- Lowercase variables represent integers or byte arrays.
- The constant _p_ refers to the field size, _0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F_.
- The constant _n_ refers to the curve order, _0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141_.
- The constant _num_participants_ refers to number of participants involved in the signing process, must be at least _min_participants_ but must not be larger than _max_participants_.
- Uppercase variables refer to points on the curve with equation _y<sup>2</sup> = x<sup>3</sup> + 7_ over the integers modulo _p_.
- _is_infinite(P)_ returns whether _P_ is the point at infinity.
- _x(P)_ and _y(P)_ are integers in the range _0..p-1_ and refer to the X and Y coordinates of a point _P_ (assuming it is not infinity).
- The constant _G_ refers to the base point, for which _x(G) = 0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798_ and _y(G) = 0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8_.
- Addition of points refers to the usual [elliptic curve group operation](https://en.wikipedia.org/wiki/Elliptic_curve#The_group_law).
- [Multiplication (⋅) of an integer and a point](https://en.wikipedia.org/wiki/Elliptic_curve_point_multiplication) refers to the repeated application of the group operation.
- Functions and operations:
- _||_ refers to byte array concatenation.
- The function _x[i:j]_, where _x_ is a byte array and _i, j ≥ 0_, returns a _(j - i)_-byte array with a copy of the _i_th byte (inclusive) to the _j_th byte (exclusive) of _x_.
- The function _bytes(n, x)_, where _x_ is an integer, returns the n-byte encoding of _x_, most significant byte first.
- The constant _empty_bytestring_ refers to the empty byte array. It holds that _len(empty_bytestring) = 0_.
- The function _xbytes(P)_, where _P_ is a point for which _not is_infinite(P)_, returns _bytes(32, x(P))_.
- The function _len(x)_ where _x_ is a byte array returns the length of the array.
- The function _has_even_y(P)_, where _P_ is a point for which _not is_infinite(P)_, returns _y(P) mod 2 == 0_.
- The function _with_even_y(P)_, where _P_ is a point, returns _P_ if _is_infinite(P)_ or _has_even_y(P)_. Otherwise, _with_even_y(P)_ returns _-P_.
- The function _cbytes(P)_, where _P_ is a point for which _not is_infinite(P)_, returns _a || xbytes(P)_ where _a_ is a byte that is _2_ if _has_even_y(P)_ and _3_ otherwise.
- The function _cbytes_ext(P)_, where _P_ is a point, returns _bytes(33, 0)_ if _is_infinite(P)_. Otherwise, it returns _cbytes(P)_.
- The function _int(x)_, where _x_ is a 32-byte array, returns the 256-bit unsigned integer whose most significant byte first encoding is _x_.
- The function _lift_x(x)_, where _x_ is an integer in range _0..2<sup>256</sup>-1_, returns the point _P_ for which _x(P) = x_[^liftx-soln] and _has_even_y(P)_, or fails if _x_ is greater than _p-1_ or no such point exists. The function _lift_x(x)_ is equivalent to the following pseudocode:
- Fail if _x > p-1_.
- Let _c = x<sup>3</sup> + 7 mod p_.
- Let _y' = c<sup>(p+1)/4</sup> mod p_.
- Fail if _c ≠ y'<sup>2</sup> mod p_.
- Let _y = y'_ if _y' mod 2 = 0_, otherwise let _y = p - y'_ .
- Return the unique point _P_ such that _x(P) = x_ and _y(P) = y_.
- The function _cpoint(x)_, where _x_ is a 33-byte array (compressed serialization), sets _P = lift_x(int(x[1:33]))_ and fails if that fails. If _x[0] = 2_ it returns _P_ and if _x[0] = 3_ it returns _-P_. Otherwise, it fails.
- The function _cpoint_ext(x)_, where _x_ is a 33-byte array (compressed serialization), returns the point at infinity if _x = bytes(33, 0)_. Otherwise, it returns _cpoint(x)_ and fails if that fails.
- The function _hash<sub>tag</sub>(x)_ where _tag_ is a UTF-8 encoded tag name and _x_ is a byte array returns the 32-byte hash _SHA256(SHA256(tag) || SHA256(tag) || x)_.
- The function _count(lst, x)_, where _lst_ is a list of integers containing _x_, returns the number of times _x_ appears in _lst_.
- The function _has_unique_elements(lst)_, where _lst_ is a list of integers, returns True if _count(lst, x)_ returns 1 for all _x_ in _lst_. Otherwise returns False. The function _has_unique_elements(lst)_ is equivalent to the following pseudocode:
- For _x_ in _lst_:
- if _count(lst, x)_ > 1:
- Return False
- Return True
- The function _sorted(lst)_, where _lst_ is a list of integers, returns a new list of integers in ascending order.
- Other:
- Tuples are written by listing the elements within parentheses and separated by commas. For example, _(2, 3, 1)_ is a tuple.
### Key Generation Compatibility
Internal Algorithm _PlainPubkeyGen(sk):_[^pubkey-gen-ecdsa]
- Input:
- The secret key _sk_: a 32-byte array, freshly generated uniformly at random
- Let _d' = int(sk)_.
- Fail if _d' = 0_ or _d' &ge; n_.
- Return _cbytes(d'⋅G)_.
<!-- REVIEW maybe write scripts to automate these italics (math symbols)? -->
Algorithm _ValidatePubshares(secshare<sub>1..u</sub>, pubshare<sub>1..u</sub>)_
- Inputs:
- The number _u_ of participants involved in keygen: an integer equal to _max_participants_
- The participant secret shares _secshare<sub>1..u</sub>_: _u_ 32-byte arrays
- The corresponding public shares _pubshare<sub>1..u</sub>_: _u_ 33-byte arrays
- For _i = 1 .. u_:
- Fail if _PlainPubkeyGen(secshare<sub>i</sub>)_ ≠ _pubshare<sub>i</sub>_
- Return success iff no failure occurred before reaching this point.
Algorithm _ValidateGroupPubkey(threshold, group_pk, id<sub>1..u</sub>, pubshare<sub>1..u</sub>)_:
- Inputs:
- The number _u_ of participants involved in keygen: an integer equal to _max_participants_
- The number _threshold_ of participants required to issue a signature: an integer equal to _min_participants_
- The group public key _group_pk_: a 33-byte array
- The participant identifiers _id<sub>1..u</sub>_: _u_ integers, each with 0 ≤ _id<sub>i</sub>_ < _max_participants_
- The participant public shares _pubshares<sub>1..u</sub>_: _u_ 33-byte arrays
- Fail if _threshold_ > _u_
- For _t_ = _threshold..u_:
- For each combination of _t_ elements from _id<sub>1..u</sub>_:[^itertools-combinations]
- Let _signer_id<sub>1..t</sub>_ be the current combination of participant identifiers
- Let _signer_pubshare<sub>1..t</sub>_ be their corresponding participant pubshares[^calc-signer-pubshares]
- _expected_pk_ = _DeriveGroupPubkey(signer_id<sub>1..t</sub>, signer_pubshare<sub>1..t</sub>)_
- Fail if _group_pk_ ≠ _expected_pk_
- Return success iff no failure occurred before reaching this point.
### Key Derivation and Tweaking
#### Tweak Context
The Tweak Context is a data structure consisting of the following elements:
- The point _Q_ representing the potentially tweaked group public key: an elliptic curve point
- The accumulated tweak _tacc_: an integer with _0 ≤ tacc < n_
- The value _gacc_: 1 or -1 mod n
We write "Let _(Q, gacc, tacc) = tweak_ctx_" to assign names to the elements of a Tweak Context.
Algorithm _TweakCtxInit(id<sub>1..u</sub>, pubshare<sub>1..u</sub>):_
- Input:
- The number _u_ of participants available in the signing session with _min_participants ≤ u ≤ max_participants_
- The participant identifiers _id<sub>1..u</sub>_: _u_ integers, each with 0 ≤ _id<sub>i</sub>_ < _max_participants_
- The individual public shares _pubshare<sub>1..u</sub>_: _u_ 33-byte arrays
- Let _group_pk = DeriveGroupPubkey(id<sub>1..u</sub>, pubshare<sub>1..u</sub>)_; fail if that fails
- Let _Q = cpoint(group_pk)_
- Fail if _is_infinite(Q)_.
- Let _gacc = 1_
- Let _tacc = 0_
- Return _tweak_ctx = (Q, gacc, tacc)_.
Internal Algorithm _DeriveGroupPubkey(id<sub>1..u</sub>, pubshare<sub>1..u</sub>)_
- _inf_point = bytes(33, 0)_
- _Q_ = _cpoint_ext(inf_point)_
- For _i_ = _1..u_:
- _P_ = _cpoint(pubshare<sub>i</sub>)_; fail if that fails
- _lambda_ = _DeriveInterpolatingValue(id<sub>1..u</sub>, id<sub>i</sub>)_
- _Q_ = _Q_ + _lambda⋅P_
- Return _cbytes(Q)_
Internal Algorithm _DeriveInterpolatingValue(id<sub>1..u</sub>, my_id):_
- Fail if _my_id_ not in _id<sub>1..u</sub>_
- Fail if not _has_unique_elements(id<sub>1..u</sub>)
- Let _num = 1_
- Let _denom = 1_
- For _i = 1..u_:
- If _id<sub>i</sub>_ ≠ _my_id_:
- Let _num_ = _num⋅(id<sub>i</sub>_ + 1)
- Let _denom_ = _denom⋅(id<sub>i</sub> - my_id)_
- _lambda_ = _num⋅denom<sup>-1</sup> mod n_
- Return _lambda_
Algorithm _GetXonlyPubkey(tweak_ctx)_:
- Let _(Q, _, _) = tweak_ctx_
- Return _xbytes(Q)_
Algorithm _GetPlainPubkey(tweak_ctx)_:
- Let _(Q, _, _) = tweak_ctx_
- Return _cbytes(Q)_
#### Applying Tweaks
Algorithm _ApplyTweak(tweak_ctx, tweak, is_xonly_t)_:
- Inputs:
- The _tweak_ctx_: a [Tweak Context](./README.md#tweak-context) data structure
- The _tweak_: a 32-byte array
- 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 = -1 mod n_
- Else:
- Let _g = 1_
- Let _t = int(tweak)_; fail if _t ≥ n_
- Let _Q' = g⋅Q + t⋅G_
- Fail if _is_infinite(Q')_
- Let _gacc' = g⋅gacc mod n_
- Let _tacc' = t + g⋅tacc mod n_
- Return _tweak_ctx' = (Q', gacc', tacc')_
### Nonce Generation
Algorithm _NonceGen(secshare, pubshare, group_pk, m, extra_in)_:
- Inputs:
- The participant’s secret share _secshare_: a 32-byte array (optional argument)
- The corresponding public share _pubshare_: a 33-byte array (optional argument)
- The x-only group public key _group_pk_: a 32-byte array (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) ≤ 2<sup>32</sup>-1_ (optional argument)
- Let _rand'_ be a 32-byte array freshly drawn uniformly at random
- If the optional argument _secshare_ is present:
- Let _rand_ be the byte-wise xor of _secshare_ and _hash<sub>FROST/aux</sub>(rand')_[^sk-xor-rand]
- Else:
- Let _rand = rand'_
- If the optional argument _pubshare_ is not present:
- Let _pubshare_ = _empty_bytestring_
- If the optional argument _group_pk_ is not present:
- Let _group_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 _k<sub>i</sub> = int(hash<sub>FROST/nonce</sub>(rand || bytes(1, len(pubshare)) || pubshare || bytes(1, len(group_pk)) || group_pk || m_prefixed || bytes(4, len(extra_in)) || extra_in || bytes(1, i - 1))) mod n_ for _i = 1,2_
- Fail if _k<sub>1</sub> = 0_ or _k<sub>2</sub> = 0_
- Let _R<sub>⁎,1</sub> = k<sub>1</sub>⋅G, R<sub>⁎,2</sub> = k<sub>2</sub>⋅G_
- Let _pubnonce = cbytes(R<sub>,1</sub>) || cbytes(R<sub>⁎,2</sub>)_
- Let _secnonce = bytes(32, k<sub>1</sub>) || bytes(32, k<sub>2</sub>)_[^secnonce-ser]
- Return _(secnonce, pubnonce)_
### Nonce Aggregation
Algorithm _NonceAgg(pubnonce<sub>1..u</sub>, id<sub>1..u</sub>)_:
- Inputs:
- The number of signers _u_: an integer with _min_participants ≤ u ≤ max_participants_
- The public nonces _pubnonce<sub>1..u</sub>_: _u_ 66-byte arrays
- The participant identifiers _id<sub>1..u</sub>_: _u_ integers, each with 0 ≤ _id<sub>i</sub>_ < _max_participants_
- For _j = 1 .. 2_:
- For _i = 1 .. u_:
- Let _R<sub>i,j</sub> = cpoint(pubnonce<sub>i</sub>[(j-1)_33:j_33])_; fail if that fails and blame signer _id<sub>i</sub>_ for invalid _pubnonce_.
- Let _R<sub>j</sub> = R<sub>1,j</sub> + R<sub>2,j</sub> + ... + R<sub>u,j</sub>_
- Return _aggnonce = cbytes_ext(R<sub>1</sub>) || cbytes_ext(R<sub>2</sub>)_
### Session Context
The Session Context is a data structure consisting of the following elements:
- The number _u_ of participants available in the signing session with _min_participants ≤ u ≤ max_participants_
- The participant identifiers _id<sub>1..u</sub>_: _u_ integers, each with 0 ≤ _id<sub>i</sub>_ < _max_participants_
- The individual public shares _pubshare<sub>1..u</sub>_: _u_ 33-byte arrays
- The aggregate public nonce of signers _aggnonce_: a 66-byte array
- The number _v_ of tweaks with _0 ≤ v < 2^32_
- The tweaks _tweak<sub>1..v</sub>_: _v_ 32-byte arrays
- The tweak modes _is_xonly_t<sub>1..v</sub>_ : _v_ booleans
- The message _m_: a byte array[^max-msg-len]
We write "Let _(u, id<sub>1..u</sub>, pubshare<sub>1..u</sub>, aggnonce, v, tweak<sub>1..v</sub>, is_xonly_t<sub>1..v</sub>, m) = session_ctx_" to assign names to the elements of a Session Context.
Algorithm _GetSessionValues(session_ctx)_:
- Let _(u, id<sub>1..u</sub>, pubshare<sub>1..u</sub>, aggnonce, v, tweak<sub>1..v</sub>, is_xonly_t<sub>1..v</sub>, m) = session_ctx_
- Let _tweak_ctx<sub>0</sub> = TweakCtxInit(id<sub>1..u</sub>, pubshare<sub>1..u</sub>)_; fail if that fails
- For _i = 1 .. v_:
- Let _tweak_ctx<sub>i</sub> = ApplyTweak(tweak_ctx<sub>i-1</sub>, tweak<sub>i</sub>, is_xonly_t<sub>i</sub>)_; fail if that fails
- Let _(Q, gacc, tacc) = tweak_ctx<sub>v</sub>_
- Let _ser_ids_ = _SerializeIds(id<sub>1..u</sub>)_
- Let b = _int(hash<sub>FROST/noncecoef</sub>(ser_ids || aggnonce || xbytes(Q) || m)) mod n_
- Let _R<sub>1</sub> = cpoint_ext(aggnonce[0:33]), R<sub>2</sub> = cpoint_ext(aggnonce[33:66])_; fail if that fails and blame nonce aggregator for invalid _aggnonce_.
- Let _R' = R<sub>1</sub> + b⋅R<sub>2</sub>_
- If _is_infinite(R'):_
- _Let final nonce_ R = G _([see Dealing with Infinity in Nonce Aggregation](./README.md#dealing-with-infinity-in-nonce-aggregation))_
- _Else:_
- _Let final nonce_ R = R'
- Let _e = int(hash<sub>BIP0340/challenge</sub>((xbytes(R) || xbytes(Q) || m))) mod n_
- _Return_ (Q, gacc, tacc, b, R, e)
<!-- REVIEW should we check for duplicates and id value range here? -->
Internal Algorithm _SerializeIds(id<sub>1..u</sub>)_:
- _res = empty_bytestring_
- For _id_ in _sorted(id<sub>1..u</sub>)_:
- _res = res || bytes(4, id)_
- Return _res_
Algorithm _GetSessionInterpolatingValue(session_ctx, my_id)_:
- Let _(u, id<sub>1..u</sub>, _, _, _, _, _) = session_ctx_
- Return _DeriveInterpolatingValue(id<sub>1..u</sub>, my_id)_; fail if that fails
Algorithm _SessionHasSignerPubshare(session_ctx, signer_pubshare)_:
- Let _(u, _, pubshare<sub>1..u</sub>, _, _, _, _) = session_ctx_
- If _signer_pubshare in pubshare<sub>1..u</sub>_
- Return True
- Otherwise Return False
### 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 secret signing key _secshare_: a 32-byte array
- The identifier of the signing participant _my_id_: an integer with 0 _≤ my_id < max_participants_
- The _session_ctx_: a [Session Context](./README.md#session-context) data structure
- Let _(Q, gacc, _, b, R, e) = GetSessionValues(session_ctx)_; fail if that fails
- Let _k<sub>1</sub>' = int(secnonce[0:32]), k<sub>2</sub>' = int(secnonce[32:64])_
- Fail if _k<sub>i</sub>' = 0_ or _k<sub>i</sub>' ≥ n_ for _i = 1..2_
- Let _k<sub>1</sub> = k<sub>1</sub>', k<sub>2</sub> = k<sub>2</sub>'_ if _has_even_y(R)_, otherwise let _k<sub>1</sub> = n - k<sub>1</sub>', k<sub>2</sub> = n - k<sub>2</sub>'_
- Let _d' = int(secshare)_
- Fail if _d' = 0_ or _d' ≥ n_
- Let _P = d'⋅G_
- Let _pubshare = cbytes(P)_
- Fail if _SessionHasSignerPubshare(session_ctx, pubshare) = False_
- Let _&lambda; = GetSessionInterpolatingValue(session_ctx, my_id)_; fail if that fails
- Let _g = 1_ if _has_even_y(Q)_, otherwise let _g = -1 mod n_
- Let _d = g⋅gacc⋅d' mod n_ (See [_Negation of Secret Share When Signing_](./README.md#negation-of-the-secret-share-when-signing))
- Let _s = (k<sub>1</sub> + b⋅k<sub>2</sub> + e⋅&lambda;⋅d) mod n_
- Let _psig = bytes(32, s)_
- Let _pubnonce = cbytes(k<sub>1</sub>'⋅G) || cbytes(k<sub>2</sub>'⋅G)_
- If _PartialSigVerifyInternal(psig, my_id, pubnonce, pubshare, session_ctx)_ (see below) returns failure, fail[^why-verify-partialsig]
- Return partial signature _psig_
### Partial Signature Verification
Algorithm _PartialSigVerify(psig, id<sub>1..u</sub>, pubnonce<sub>1..u</sub>, pubshare<sub>1..u</sub>, tweak<sub>1..v</sub>, is_xonly_t<sub>1..v</sub>, m, i)_:
- Inputs:
- The partial signature _psig_: a 32-byte array
- The number _u_ of identifiers, public nonces, and individual public shares with _min_participants ≤ u ≤ max_participants_
- The participant identifiers _id<sub>1..u</sub>_: _u_ integers, each with 0 ≤ _id<sub>i</sub>_ < _max_participants_
- The public nonces _pubnonce<sub>1..u</sub>_: _u_ 66-byte arrays
- The individual public shares _pubshare<sub>1..u</sub>_: _u_ 33-byte arrays
- The number _v_ of tweaks with _0 ≤ v < 2^32_
- The tweaks _tweak<sub>1..v</sub>_: _v_ 32-byte arrays
- The tweak modes _is_xonly_t<sub>1..v</sub>_ : _v_ booleans
- The message _m_: a byte array[^max-msg-len]
- The index _i_ of the signer in the list of identifiers, public nonces, and individual public shares where _0 < i u_
- Let _aggnonce = NonceAgg(pubnonce<sub>1..u</sub>)_; fail if that fails
- Let _session_ctx = (u, id<sub>1..u</sub>, pubshare<sub>1..u</sub>, aggnonce, v, tweak<sub>1..v</sub>, is_xonly_t<sub>1..v</sub>, m)_
- Run _PartialSigVerifyInternal(psig, id<sub>i</sub>, pubnonce<sub>i</sub>, pubshare<sub>i</sub>, 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, _, b, R, e) = GetSessionValues(session_ctx)_; fail if that fails
- Let _s = int(psig)_; fail if _s ≥ n_
- Fail if _SessionHasSignerPubshare(session_ctx, pubshare) = False_
- Let _R<sub>⁎,1</sub> = cpoint(pubnonce[0:33]), R<sub>⁎,2</sub> = cpoint(pubnonce[33:66])_
- Let _Re<sub></sub>' = R<sub>⁎,1</sub> + b⋅R<sub>⁎,2</sub>_
- Let effective nonce _Re<sub>⁎</sub> = Re<sub>⁎</sub>'_ if _has_even_y(R)_, otherwise let _Re<sub></sub> = -Re<sub></sub>'_
- Let _P = cpoint(pubshare)_; fail if that fails
- Let _&lambda; = GetSessionInterpolatingValue(session_ctx, my_id)_[^lambda-cant-fail]
- Let _g = 1_ if _has_even_y(Q)_, otherwise let _g = -1 mod n_
- Let _g' = g⋅gacc mod n_ (See [_Negation of Pubshare When Partially Verifying_](./README.md#negation-of-the-pubshare-when-partially-verifying))
- Fail if _s⋅G ≠ Re<sub></sub> + e⋅&lambda;⋅g'⋅P_
- Return success iff no failure occurred before reaching this point.
### Partial Signature Aggregation
Algorithm _PartialSigAgg(psig<sub>1..u</sub>, id<sub>1..u</sub>, session_ctx)_:
- Inputs:
- The number _u_ of signatures with _min_participants ≤ u ≤ max_participants_
- The partial signatures _psig<sub>1..u</sub>_: _u_ 32-byte arrays
- The participant identifiers _id<sub>1..u</sub>_: _u_ integers, each with 0 ≤ _id<sub>i</sub>_ < _max_participants_
- The _session_ctx_: a [Session Context](./README.md#session-context) data structure
- Let _(Q, _, tacc, _, _, R, e) = GetSessionValues(session_ctx)_; fail if that fails
- For _i = 1 .. u_:
- Let _s<sub>i</sub> = int(psig<sub>i</sub>)_; fail if _s<sub>i</sub> ≥ n_ and blame signer _id<sub>i</sub>_ for invalid partial signature.
- Let _g = 1_ if _has_even_y(Q)_, otherwise let _g = -1 mod n_
- Let _s = s<sub>1</sub> + ... + s<sub>u</sub> + e⋅g⋅tacc mod n_
- Return _sig =_ xbytes(R) || bytes(32, s)
### Test Vectors & Reference Code
We provide a naive, highly inefficient, and non-constant time [pure Python 3 reference implementation of the group 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 is supposed to send the _pubnonce_ last, 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](./README.md#nonce-generation) section.
<!-- REVIEW just say max_participants is < 2^32 during intro, than mentioning it everywhere -->
#### Deterministic and Stateless Signing for a Single Signer
Algorithm _DeterministicSign(secshare, my_id, aggothernonce, id<sub>1..u</sub>, pubshare<sub>1..u</sub>, tweak<sub>1..v</sub>, is_xonly_t<sub>1..v</sub>, m, rand)_:
- Inputs:
- The secret share _secshare_: a 32-byte array
- The identifier of the signing participant _my_id_: an integer with 0 _≤ my_id < max_participants_
- The aggregate public nonce _aggothernonce_ (see [above](./README.md#modifications-to-nonce-generation)): a 66-byte array
- The number _u_ of identifiers and participant public shares with _min_participants ≤ u ≤ max_participants_
- The participant identifiers _id<sub>1..u</sub>_: _u_ integers, each with 0 ≤ _id<sub>i</sub>_ < _max_participants_
- The individual public shares _pubshare<sub>1..u</sub>_: _u_ 33-byte arrays
- The number _v_ of tweaks with _0 &le; v < 2^32_
- The tweaks _tweak<sub>1..v</sub>_: _v_ 32-byte arrays
- The tweak methods _is_xonly_t<sub>1..v</sub>_: _v_ booleans
- The message _m_: a byte array[^max-msg-len]
- The auxiliary randomness _rand_: a 32-byte array (optional argument)
- If the optional argument _rand_ is present:
- Let _secshare'_ be the byte-wise xor of _secshare_ and _hash<sub>FROST/aux</sub>(rand)_
- Else:
- Let _secshare' = secshare_
- Let _tweak_ctx<sub>0</sub> = TweakCtxInit(id<sub>1..u</sub>, pubshare<sub>1..u</sub>)_; fail if that fails
- For _i = 1 .. v_:
- Let _tweak_ctx<sub>i</sub> = ApplyTweak(tweak_ctx<sub>i-1</sub>, tweak<sub>i</sub>, is_xonly_t<sub>i</sub>)_; fail if that fails
- Let _tweaked_gpk = GetXonlyPubkey(tweak_ctx<sub>v</sub>)_
- Let _k<sub>i</sub> = int(hash<sub>FROST/deterministic/nonce</sub>(secshare' || aggothernonce || tweaked_gpk || bytes(8, len(m)) || m || bytes(1, i - 1))) mod n_ for _i = 1,2_
- Fail if _k<sub>1</sub> = 0_ or _k<sub>2</sub> = 0_
- Let _R<sub>⁎,1</sub> = k<sub>1</sub>⋅G, R<sub>⁎,2</sub> = k<sub>2</sub>⋅G_
- Let _pubnonce = cbytes(R<sub>⁎,2</sub>) || cbytes(R<sub>⁎,2</sub>)_
- Let _d = int(secshare)_
- Fail if _d = 0_ or _d &ge; n_
- Let _signer_pubshare = cbytes(d⋅G)_
- Fail if _signer_pubshare_ is not present in _pubshare<sub>1..u</sub>_
- Let _secnonce = bytes(32, k<sub>1</sub>) || bytes(32, k<sub>2</sub>)_
- Let _aggnonce = NonceAgg((pubnonce, aggothernonce))_; fail if that fails and blame nonce aggregator for invalid _aggothernonce_.
- Let _session_ctx = (u, id<sub>1..u</sub>, pubshare<sub>1..u</sub>, aggnonce, v, tweak<sub>1..v</sub>, is_xonly_t<sub>1..v</sub>, m)_
- Return _(pubnonce, Sign(secnonce, secshare, my_id, session_ctx))_
### Tweaking Definition
Two modes of tweaking the group public key are supported. They correspond to the following algorithms:
Algorithm _ApplyPlainTweak(P, t)_:
- Inputs:
- _P_: a point
- The tweak _t_: an integer with _0 ≤ t < n_
- Return _P + t⋅G_
Algorithm _ApplyXonlyTweak(P, t)_:
- Return _with_even_y(P) + t⋅G_
### Negation of the Secret Share when Signing
During the signing process, the *[Sign](./README.md#signing)* algorithm might have to negate the secret share in order to produce a partial signature for an X-only group public key. This public key is derived from *u* public shares and *u* participant identifiers (denoted by the signer set *U*) and then tweaked *v* times (X-only or plain).
The following elliptic curve points arise as intermediate steps when creating a signature:
_P<sub>i</sub>_ as computed in any compatible key generation method is the point corresponding to the *i*-th signer's public share. Defining *d<sub>i</sub>'* to be the *i*-th signer's secret share as an integer, i.e., the *d’* value as computed in the *Sign* algorithm of the *i*-th signer, we have:
&emsp;&ensp;*P<sub>i</sub> = d<sub>i</sub>'⋅G*
*Q<sub>0</sub>* is the group public key derived from the signer’s public shares. It is identical to the value *Q* computed in *DeriveGroupPubkey* and therefore defined as:
&emsp;&ensp;_Q<sub>0</sub> = &lambda;<sub>1, U</sub>⋅P<sub>1</sub> + &lambda;<sub>2, U</sub>⋅P<sub>2</sub> + ... + &lambda;<sub>u, U</sub>⋅P<sub>u</sub>_
*Q<sub>i</sub>* is the tweaked group public key after the *i*-th execution of *ApplyTweak* for *1 ≤ i ≤ v*. It holds that
&emsp;&ensp;*Q<sub>i</sub> = f(i-1) + t<sub>i</sub>⋅G* for *i = 1, ..., v* where
&emsp;&ensp;&emsp;&ensp;*f(i-1) := with_even_y(Q<sub>i-1</sub>)* if *is_xonly_t<sub>i</sub>* and
&emsp;&ensp;&emsp;&ensp;*f(i-1) := Q<sub>i-1</sub>* otherwise.
*with_even_y(Q*<sub>v</sub>*)* is the final result of the group public key derivation and 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 group pubkey derivation and tweaking, i.e., the X-only public key *with_even_y(Q<sub>v</sub>)*.
For _1 ≤ i ≤ v_, we denote the value _g_ computed in the _i_-th execution of _ApplyTweak_ by _g<sub>i-1</sub>_. Therefore, _g<sub>i-1</sub>_ is _-1 mod n_ if and only if _is_xonly_t<sub>i</sub>_ is true and _Q<sub>i-1</sub>_ has an odd Y coordinate. In other words, _g<sub>i-1</sub>_ indicates whether _Q<sub>i-1</sub>_ needed to be negated to apply an X-only tweak:
&emsp;&ensp;_f(i-1) = g<sub>i-1</sub>⋅Q<sub>i-1</sub>_ for _1 ≤ i ≤ v_.
Furthermore, the _Sign_ and _PartialSigVerify_ algorithms set value _g_ depending on whether Q<sub>v</sub> needed to be negated to produce the (X-only) final output. For consistency, this value _g_ is referred to as _g<sub>v</sub>_ in this section.
&emsp;&ensp;_with_even_y(Q<sub>v</sub>) = g<sub>v</sub>⋅Q<sub>v</sub>_.
So, the (X-only) final public key is
&emsp;&ensp;_with_even_y(Q<sub>v</sub>)_
&emsp;&ensp;&emsp;&ensp;= _g<sub>v</sub>⋅Q<sub>v</sub>_
&emsp;&ensp;&emsp;&ensp;= _g<sub>v</sub>⋅(f(v-1)_ + _t<sub>v</sub>⋅G)_
&emsp;&ensp;&emsp;&ensp;= _g<sub>v</sub>⋅(g<sub>v-1</sub>⋅(f(v-2)_ + _t<sub>v-1</sub>⋅G)_ + _t<sub>v</sub>⋅G)_
&emsp;&ensp;&emsp;&ensp;= _g<sub>v</sub>⋅g<sub>v-1</sub>⋅f(v-2)_ + _g<sub>v</sub>⋅(t<sub>v</sub>_ + _g<sub>v-1</sub>⋅t<sub>v-1</sub>)⋅G_
&emsp;&ensp;&emsp;&ensp;= _g<sub>v</sub>⋅g<sub>v-1</sub>⋅f(v-2)_ + _(sum<sub>i=v-1..v</sub> t<sub>i</sub>⋅prod<sub>j=i..v</sub> g<sub>j</sub>)⋅G_
&emsp;&ensp;&emsp;&ensp;= _g<sub>v</sub>⋅g<sub>v-1</sub>⋅...⋅g<sub>1</sub>⋅f(0)_ + _(sum<sub>i=1..v</sub> t<sub>i</sub>⋅prod<sub>j=i..v</sub> g<sub>j</sub>)⋅G_
&emsp;&ensp;&emsp;&ensp;= _g<sub>v</sub>⋅...⋅g<sub>0</sub>⋅Q<sub>0</sub>_ + _g<sub>v</sub>⋅tacc<sub>v</sub>⋅G_
&emsp;&ensp;where _tacc<sub>i</sub>_ is computed by _TweakCtxInit_ and _ApplyTweak_ as follows:
&emsp;&ensp;&emsp;&ensp;_tacc<sub>0</sub>_ = _0_
&emsp;&ensp;&emsp;&ensp;_tacc<sub>i</sub>_ = _t<sub>i</sub>_ + _g<sub>i-1</sub>⋅tacc<sub>i-1</sub> for i=1..v mod n_
&emsp;&ensp;for which it holds that _g<sub>v</sub>⋅tacc<sub>v</sub>_ = _sum<sub>i=1..v</sub> t<sub>i</sub>⋅prod<sub>j=i..v</sub> g<sub>j</sub>_.
_TweakCtxInit_ and _ApplyTweak_ compute
&emsp;&ensp;_gacc<sub>0</sub>_ = 1
&emsp;&ensp;_gacc<sub>i</sub>_ = _g<sub>i-1</sub>⋅gacc<sub>i-1</sub> for i=1..v mod n_
So we can rewrite above equation for the final public key as
&emsp;&ensp;_with_even_y(Q<sub>v</sub>)_ = _g<sub>v</sub>⋅gacc<sub>v</sub>⋅Q<sub>0</sub>_ + _g<sub>v</sub>⋅tacc<sub>v</sub>⋅G._
Then we have
&emsp;&ensp;_with_even_y(Q<sub>v</sub>)_ - _g<sub>v</sub>⋅tacc<sub>v</sub>⋅G_
&emsp;&ensp;&emsp;&ensp;= _g<sub>v</sub>⋅gacc<sub>v</sub>⋅Q<sub>0</sub>_
&emsp;&ensp;&emsp;&ensp;= _g<sub>v</sub>⋅gacc<sub>v</sub>⋅(&lambda;<sub>1, U</sub>⋅P<sub>1</sub> + ... + &lambda;<sub>u, U</sub>⋅P<sub>u</sub>)_
&emsp;&ensp;&emsp;&ensp;= _g<sub>v</sub>⋅gacc<sub>v</sub>⋅(&lambda;<sub>1, U</sub>⋅d<sub>1</sub>'⋅G + ... + &lambda;<sub>u, U</sub>⋅d<sub>u</sub>'⋅G)_
&emsp;&ensp;&emsp;&ensp;= _sum<sub>i=1..u</sub>(g<sub>v</sub>⋅gacc<sub>v</sub>⋅&lambda;<sub>i, U</sub>⋅d<sub>i</sub>')*G._
Intuitively, _gacc<sub>i</sub>_ tracks accumulated sign flipping and _tacc<sub>i</sub>_ tracks the accumulated tweak value after applying the first _i_ individual tweaks. Additionally, _g<sub>v</sub>_ indicates whether _Q<sub>v</sub>_ needed to be negated to produce the final X-only result. Thus, signer _i_ multiplies its secret share _d<sub>i</sub>'_ with _g<sub>v</sub>⋅gacc<sub>v</sub>_ in the [_Sign_](./README.md#signing) algorithm.
#### Negation of the Pubshare when Partially Verifying
As explained in [Negation Of The Secret Share When Signing](./README.md#negation-of-the-secret-share-when-signing) the signer uses a possibly negated secret share
&emsp;&ensp;_d = g<sub>v</sub>⋅gacc<sub>v</sub>⋅d' mod n_
when producing a partial signature to ensure that the aggregate signature will correspond to a group public key with even Y coordinate.
The [_PartialSigVerifyInternal_](./README.md#partial-signature-verification) algorithm is supposed to check
&emsp;&ensp;_s⋅G = Re<sub></sub> + e⋅&lambda;⋅d⋅G_.
The verifier doesn't have access to _d⋅G_ but can construct it using the participant public share _pubshare_ as follows:
_d⋅G
&emsp;&ensp;= g<sub>v</sub>⋅gacc<sub>v</sub>⋅d'⋅G
&emsp;&ensp;= g<sub>v</sub>⋅gacc<sub>v</sub>⋅cpoint(pubshare)_
Note that the group public key and list of tweaks are inputs to partial signature verification, so the verifier can also construct _g<sub>v</sub>_ and _gacc<sub>v</sub>_.
### Dealing with Infinity in Nonce Aggregation
If the nonce aggregator provides _aggnonce = bytes(33,0) || bytes(33,0)_, either the nonce aggregator 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 (which is defined in the section 2.3 of the [ROAST paper](https://eprint.iacr.org/2022/550.pdf)) 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](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki). FROST is _not_ compatible with ECDSA signatures traditionally used in Bitcoin.
## Changelog
To help the reader understand updates to this document, we attach a version number that resembles "semantic versioning" (`MAJOR.MINOR.PATCH`).
The `MAJOR` version is incremented if changes to the BIP are introduced that are incompatible with prior versions.
An exception to this rule is `MAJOR` version zero (0.y.z) which is for development and does not need to be incremented if backwards-incompatible changes are introduced.
The `MINOR` version is incremented whenever the inputs or the output of an algorithm changes in a backward-compatible way or new backward-compatible functionality is added.
The `PATCH` version is incremented for other noteworthy changes (bug fixes, test vectors, important clarifications, etc.).
* *0.2.0* (2025-04-11): Includes minor fixes and the following major changes:
- Initialize `TweakCtxInit` using individual `pubshares` instead of the group public key.
- Add Python script to automate generation of test vectors.
- Represent participant identifiers as 4-byte integers in the range `0..MAX_PARTICIPANTS - 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.
<!-- Footnotes -->
[^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](bip-0327.mediawiki)) 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](bip-0340.mediawiki) key pair and transmitting the key pair to every other signer, who can check its consistency and then simply use the ordinary [BIP340](bip-0340.mediawiki) signing algorithm.
Signers still need to ensure that they agree on a key pair. A detailed specification for this key sharing protocol is not in the scope of this document.
[^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.
[^pubkey-gen-ecdsa]: The _PlainPubkeyGen_ algorithm matches the key generation procedure traditionally used for ECDSA in Bitcoin
[^itertools-combinations]: This line represents a loop over every possible combination of `t` elements sourced from the `int_ids` array. This operation is equivalent to invoking the [`itertools.combinations(int_ids, t)`](https://docs.python.org/3/library/itertools.html#itertools.combinations) function call in Python.
[^calc-signer-pubshares]: This _signer_pubshare<sub>1..t</sub>_ list can be computed from the input _pubshare<sub>1..u</sub>_ list.
Method 1 - use `itertools.combinations(zip(int_ids, pubshares), t)`
Method 2 - For _i = 1..t_ : signer_pubshare<sub>i</sub> = pubshare<sub>signer_id<sub>i</sub></sub>
[^arbitrary-tweaks]: It is an open question whether allowing arbitrary tweaks from an adversary affects the unforgeability of FROST.
[^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](docs/partialsig_forgery.md).
[^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 = x<sup>3</sup> + 7 mod p_ and they can be computed as _y = &plusmn;c<sup>(p+1)/4</sup> 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_.
[^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).
[^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 key itself. It is xored with the secret key (rather than combined with it in a hash) to reduce the number of operations exposed to the actual secret key.
[^secnonce-ser]: The algorithms as specified here assume that the _secnonce_ is stored as a 64-byte array using the serialization _secnonce = bytes(32, k<sub>1</sub>) || bytes(32, k<sub>2</sub>)_. 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.<br />
The _secnonce_ is effectively a local data structure of the signer which comprises the value triple _(k<sub>1</sub>, k<sub>2</sub>)_, 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.
[^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 key. It is recommended but can be omitted if the computation cost is prohibitive.
[^lambda-cant-fail]: _GetSessionInterpolatingValue(session_ctx, my_id)_ cannot fail when called from _PartialSigVerifyInternal_.

298
src/jmfrost/frost_ref/reference.py

@ -10,12 +10,15 @@
# be used in production environments. The code is vulnerable to timing attacks,
# for example.
from typing import Any, List, Optional, Tuple, NewType, NamedTuple
from typing import Any, List, Optional, Tuple, NewType, NamedTuple, Sequence
import itertools
import secrets
import time
from .utils.bip340 import *
from ..secp256k1lab.bip340 import schnorr_verify
from ..secp256k1lab.keys import pubkey_gen_plain
from ..secp256k1lab.secp256k1 import G, GE, Scalar
from ..secp256k1lab.util import bytes_from_int, int_from_bytes, tagged_hash
PlainPk = NewType('PlainPk', bytes)
XonlyPk = NewType('XonlyPk', bytes)
@ -42,84 +45,45 @@ class InvalidContributionError(Exception):
# contrib is one of "pubkey", "pubnonce", "aggnonce", or "psig".
self.contrib = contrib
infinity = None
def xbytes(P: GE) -> bytes:
return P.to_bytes_xonly()
def xbytes(P: Point) -> bytes:
return bytes_from_int(x(P))
def cbytes(P: GE) -> bytes:
return P.to_bytes_compressed()
def cbytes(P: Point) -> bytes:
a = b'\x02' if has_even_y(P) else b'\x03'
return a + xbytes(P)
def cbytes_ext(P: Optional[Point]) -> bytes:
if is_infinite(P):
def cbytes_ext(P: GE) -> bytes:
if P.infinity:
return (0).to_bytes(33, byteorder='big')
assert P is not None
return cbytes(P)
def point_negate(P: Optional[Point]) -> Optional[Point]:
if P is None:
return P
return (x(P), p - y(P))
def cpoint(x: bytes) -> GE:
return GE.from_bytes_compressed(x)
def cpoint(x: bytes) -> Point:
if len(x) != 33:
raise ValueError('x is not a valid compressed point.')
P = lift_x(x[1:33])
if P is None:
raise ValueError('x is not a valid compressed point.')
if x[0] == 2:
return P
elif x[0] == 3:
P = point_negate(P)
assert P is not None
return P
else:
raise ValueError('x is not a valid compressed point.')
def cpoint_ext(x: bytes) -> Optional[Point]:
def cpoint_ext(x: bytes) -> GE:
if x == (0).to_bytes(33, 'big'):
return None
return GE()
else:
return cpoint(x)
def int_ids(lst: List[bytes]) -> List[int]:
res = []
for x in lst:
id_ = int_from_bytes(x)
#todo: add check for < max_participants?
if not 1 <= id_ < n:
raise ValueError('x is not a valid participant identifier.')
res.append(id_)
return res
# Return the plain public key corresponding to a given secret key
def individual_pk(seckey: bytes) -> PlainPk:
d0 = int_from_bytes(seckey)
if not (1 <= d0 <= n - 1):
raise ValueError('The secret key must be an integer in the range 1..n-1.')
P = point_mul(G, d0)
assert P is not None
return PlainPk(cbytes(P))
def derive_interpolating_value_internal(L: List[int], x_i: int) -> int:
num, deno = 1, 1
for x_j in L:
if x_j == x_i:
continue
num *= x_j
deno *= (x_j - x_i)
return num * pow(deno, n - 2, n) % n
return PlainPk(pubkey_gen_plain(seckey))
def derive_interpolating_value(ids: List[bytes], my_id: bytes) -> int:
# TODO: add my_id < max_participants check
def derive_interpolating_value(ids: List[int], my_id: int) -> Scalar:
if not my_id in ids:
raise ValueError('The signer\'s id must be present in the participant identifier list.')
if not all(ids.count(my_id) <= 1 for my_id in ids):
raise ValueError('The participant identifier list must contain unique elements.')
#todo: turn this into raise ValueError?
assert 1 <= int_from_bytes(my_id) < n
integer_ids = int_ids(ids)
return derive_interpolating_value_internal(integer_ids, int_from_bytes(my_id))
assert 0 <= my_id < 2**32
num, deno = 1, 1
for curr_id in ids:
if curr_id == my_id:
continue
num *= curr_id + 1
deno *= (curr_id - my_id)
return Scalar.from_int_wrapping(num * pow(deno, GE.ORDER - 2, GE.ORDER))
def check_pubshares_correctness(secshares: List[bytes], pubshares: List[PlainPk]) -> bool:
assert len(secshares) == len(pubshares)
@ -128,7 +92,7 @@ def check_pubshares_correctness(secshares: List[bytes], pubshares: List[PlainPk]
return False
return True
def check_group_pubkey_correctness(min_participants: int, group_pk: PlainPk, ids: List[bytes], pubshares: List[PlainPk]) -> bool:
def check_group_pubkey_correctness(min_participants: int, group_pk: PlainPk, ids: List[int], pubshares: List[PlainPk]) -> bool:
assert len(ids) == len(pubshares)
assert len(ids) >= min_participants
@ -144,7 +108,7 @@ def check_group_pubkey_correctness(min_participants: int, group_pk: PlainPk, ids
return False
return True
def check_frost_key_compatibility(max_participants: int, min_participants: int, group_pk: PlainPk, ids: List[bytes], secshares: List[bytes], pubshares: List[PlainPk]) -> bool:
def check_frost_key_compatibility(max_participants: int, min_participants: int, group_pk: PlainPk, ids: List[int], secshares: List[bytes], pubshares: List[PlainPk]) -> bool:
if not max_participants >= min_participants > 1:
return False
if not len(ids) == len(secshares) == len(pubshares) == max_participants:
@ -153,10 +117,10 @@ def check_frost_key_compatibility(max_participants: int, min_participants: int,
group_pk_check = check_group_pubkey_correctness(min_participants, group_pk, ids, pubshares)
return pubshare_check and group_pk_check
TweakContext = NamedTuple('TweakContext', [('Q', Point),
TweakContext = NamedTuple('TweakContext', [('Q', GE),
('gacc', int),
('tacc', int)])
AGGREGATOR_ID = b'aggregator'
AGGREGATOR_ID = None
def get_xonly_pk(tweak_ctx: TweakContext) -> XonlyPk:
Q, _, _ = tweak_ctx
@ -167,22 +131,22 @@ def get_plain_pk(tweak_ctx: TweakContext) -> PlainPk:
return PlainPk(cbytes(Q))
#nit: switch the args ordering
def derive_group_pubkey(pubshares: List[PlainPk], ids: List[bytes]) -> PlainPk:
def derive_group_pubkey(pubshares: List[PlainPk], ids: List[int]) -> PlainPk:
assert len(pubshares) == len(ids)
assert AGGREGATOR_ID not in ids
Q = infinity
# assert AGGREGATOR_ID not in ids
Q = GE()
for my_id, pubshare in zip(ids, pubshares):
try:
X_i = cpoint(pubshare)
except ValueError:
raise InvalidContributionError(int_from_bytes(my_id), "pubshare")
raise InvalidContributionError(my_id, "pubshare")
lam_i = derive_interpolating_value(ids, my_id)
Q = point_add(Q, point_mul(X_i, lam_i))
Q = Q + lam_i * X_i
# Q is not the point at infinity except with negligible probability.
assert(Q is not infinity)
assert not Q.infinity
return PlainPk(cbytes(Q))
def tweak_ctx_init(pubshares: List[PlainPk], ids: List[bytes]) -> TweakContext:
def tweak_ctx_init(pubshares: List[PlainPk], ids: List[int]) -> TweakContext:
group_pk = derive_group_pubkey(pubshares, ids)
Q = cpoint(group_pk)
gacc = 1
@ -193,18 +157,19 @@ def apply_tweak(tweak_ctx: TweakContext, tweak: bytes, is_xonly: bool) -> TweakC
if len(tweak) != 32:
raise ValueError('The tweak must be a 32-byte array.')
Q, gacc, tacc = tweak_ctx
if is_xonly and not has_even_y(Q):
g = n - 1
if is_xonly and not Q.has_even_y():
g = Scalar(-1)
else:
g = 1
t = int_from_bytes(tweak)
if t >= n:
g = Scalar(1)
try:
t = Scalar.from_bytes_checked(tweak)
except ValueError:
raise ValueError('The tweak must be less than n.')
Q_ = point_add(point_mul(Q, g), point_mul(G, t))
if Q_ is None:
Q_ = g * Q + t * G
if Q_.infinity:
raise ValueError('The result of tweaking cannot be infinity.')
gacc_ = g * gacc % n
tacc_ = (t + g * tacc) % n
gacc_ = g * gacc
tacc_ = t + g * tacc
return TweakContext(Q_, gacc_, tacc_)
def bytes_xor(a: bytes, b: bytes) -> bytes:
@ -240,22 +205,23 @@ def nonce_gen_internal(rand_: bytes, secshare: Optional[bytes], pubshare: Option
msg_prefixed += msg
if extra_in is None:
extra_in = b''
k_1 = nonce_hash(rand, pubshare, group_pk, 0, msg_prefixed, extra_in) % n
k_2 = nonce_hash(rand, pubshare, group_pk, 1, msg_prefixed, extra_in) % n
k_1 = Scalar.from_int_wrapping(nonce_hash(rand, pubshare, group_pk, 0, msg_prefixed, extra_in))
k_2 = Scalar.from_int_wrapping(nonce_hash(rand, pubshare, group_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
R_s1 = point_mul(G, k_1)
R_s2 = point_mul(G, k_2)
assert R_s1 is not None
assert R_s2 is not None
R_s1 = k_1 * G
R_s2 = k_2 * G
assert not R_s1.infinity
assert not R_s2.infinity
pubnonce = cbytes(R_s1) + cbytes(R_s2)
# use mutable `bytearray` since secnonce need to be replaced with zeros during signing.
secnonce = bytearray(bytes_from_int(k_1) + bytes_from_int(k_2))
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 group_pk be XOnlyPk here? Shouldn't it be PlainPk?
def nonce_gen(secshare: Optional[bytes], pubshare: Optional[PlainPk], group_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.')
@ -267,30 +233,37 @@ def nonce_gen(secshare: Optional[bytes], pubshare: Optional[PlainPk], group_pk:
rand_ = secrets.token_bytes(32)
return nonce_gen_internal(rand_, secshare, pubshare, group_pk, msg, extra_in)
def nonce_agg(pubnonces: List[bytes], ids: List[bytes]) -> bytes:
# 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 = infinity
for my_id_, pubnonce in zip(ids, pubnonces):
R_j = GE()
for my_id, pubnonce in zip(ids, pubnonces):
try:
R_ij = cpoint(pubnonce[(j-1)*33:j*33])
except ValueError:
my_id = int_from_bytes(my_id_) if my_id_ != AGGREGATOR_ID else my_id_
raise InvalidContributionError(my_id, "pubnonce")
R_j = point_add(R_j, R_ij)
R_j = R_j + R_ij
aggnonce += cbytes_ext(R_j)
return aggnonce
SessionContext = NamedTuple('SessionContext', [('aggnonce', bytes),
('identifiers', List[bytes]),
('identifiers', List[int]),
('pubshares', List[PlainPk]),
('tweaks', List[bytes]),
('is_xonly', List[bool]),
('msg', bytes)])
def group_pubkey_and_tweak(pubshares: List[PlainPk], ids: List[bytes], tweaks: List[bytes], is_xonly: List[bool]) -> TweakContext:
def group_pubkey_and_tweak(pubshares: List[PlainPk], ids: List[int], tweaks: List[bytes], is_xonly: List[bool]) -> TweakContext:
if len(pubshares) != len(ids):
raise ValueError('The pubshares and ids arrays must have the same length.')
if len(tweaks) != len(is_xonly):
@ -301,25 +274,33 @@ def group_pubkey_and_tweak(pubshares: List[PlainPk], ids: List[bytes], tweaks: L
tweak_ctx = apply_tweak(tweak_ctx, tweaks[i], is_xonly[i])
return tweak_ctx
def get_session_values(session_ctx: SessionContext) -> Tuple[Point, int, int, int, Point, int]:
def get_session_values(session_ctx: SessionContext) -> Tuple[GE, int, int, Scalar, GE, Scalar]:
(aggnonce, ids, pubshares, tweaks, is_xonly, msg) = session_ctx
Q, gacc, tacc = group_pubkey_and_tweak(pubshares, ids, tweaks, is_xonly)
# sort the ids before serializing because ROAST paper considers them as a set
concat_ids = b''.join(sorted(ids))
b = int_from_bytes(tagged_hash('FROST/noncecoef', concat_ids + aggnonce + xbytes(Q) + msg)) % n
ser_ids = serialize_ids(ids)
b = Scalar.from_bytes_wrapping(tagged_hash('FROST/noncecoef', ser_ids + aggnonce + xbytes(Q) + msg))
try:
R_1 = cpoint_ext(aggnonce[0:33])
R_2 = cpoint_ext(aggnonce[33:66])
except ValueError:
# Nonce aggregator sent invalid nonces
raise InvalidContributionError(None, "aggnonce")
R_ = point_add(R_1, point_mul(R_2, b))
R = R_ if not is_infinite(R_) else G
assert R is not None
e = int_from_bytes(tagged_hash('BIP0340/challenge', xbytes(R) + xbytes(Q) + msg)) % n
R_ = R_1 + b * R_2
R = R_ if not R_.infinity else G
assert not R.infinity
e = Scalar.from_bytes_wrapping(tagged_hash('BIP0340/challenge', xbytes(R) + xbytes(Q) + msg))
return (Q, gacc, tacc, b, R, e)
def get_session_interpolating_value(session_ctx: SessionContext, my_id: bytes) -> int:
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 get_session_interpolating_value(session_ctx: SessionContext, my_id: int) -> Scalar:
(_, ids, _, _, _, _) = session_ctx
return derive_interpolating_value(ids, my_id)
@ -327,46 +308,54 @@ def session_has_signer_pubshare(session_ctx: SessionContext, pubshare: bytes) ->
(_, _, pubshares_list, _, _, _) = session_ctx
return pubshare in pubshares_list
def sign(secnonce: bytearray, secshare: bytes, my_id: bytes, session_ctx: SessionContext) -> bytes:
def sign(secnonce: bytearray, secshare: bytes, my_id: int, session_ctx: SessionContext) -> bytes:
# do we really need the below check?
# add test vector for this check if confirmed
if not 0 < int_from_bytes(my_id) < n:
if not 0 <= my_id < 2**32:
raise ValueError('The signer\'s participant identifier is out of range')
(Q, gacc, _, b, R, e) = get_session_values(session_ctx)
k_1_ = int_from_bytes(secnonce[0:32])
k_2_ = int_from_bytes(secnonce[32:64])
# TODO: use `Scalar.from_bytes_nonzero_checked` for deserializing k1 and k2, once available
# in a secp256k1lab release (see https://github.com/secp256k1lab/secp256k1lab/pull/8)
try:
k_1_ = Scalar.from_bytes_checked(secnonce[0:32])
if k_1_ == 0: # treat zero exactly like any other bad input
raise ValueError
except ValueError:
raise ValueError('first secnonce value is out of range.')
try:
k_2_ = Scalar.from_bytes_checked(secnonce[32:64])
if k_2_ == 0: # treat zero exactly like any other bad input
raise ValueError
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)
if not 0 < k_1_ < n:
raise ValueError('first secnonce value is out of range.')
if not 0 < k_2_ < n:
raise ValueError('second secnonce value is out of range.')
k_1 = k_1_ if has_even_y(R) else n - k_1_
k_2 = k_2_ if has_even_y(R) else n - k_2_
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_ < n:
if not 0 < d_ < GE.ORDER:
raise ValueError('The signer\'s secret share value is out of range.')
P = point_mul(G, d_)
assert P is not None
P = d_ * G
assert not P.infinity
pubshare = cbytes(P)
if not session_has_signer_pubshare(session_ctx, pubshare):
raise ValueError('The signer\'s pubshare must be included in the list of pubshares.')
a = get_session_interpolating_value(session_ctx, my_id)
g = 1 if has_even_y(Q) else n - 1
d = g * gacc * d_ % n
s = (k_1 + b * k_2 + e * a * d) % n
psig = bytes_from_int(s)
R_s1 = point_mul(G, k_1_)
R_s2 = point_mul(G, k_2_)
assert R_s1 is not None
assert R_s2 is not None
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()
R_s1 = k_1_ * G
R_s2 = k_2_ * G
assert not R_s1.infinity
assert not R_s2.infinity
pubnonce = cbytes(R_s1) + cbytes(R_s2)
# Optional correctness check. The result of signing should pass signature verification.
assert partial_sig_verify_internal(psig, my_id, pubnonce, pubshare, session_ctx)
return psig
#todo: should we hash the signer set (or pubshares) too? Otherwise same nonce will be generate even if the signer set changes
# 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_gpk: bytes, msg: bytes, i: int) -> int:
buf = b''
buf += secshare_
@ -377,7 +366,7 @@ def det_nonce_hash(secshare_: bytes, aggothernonce: bytes, tweaked_gpk: bytes, m
buf += i.to_bytes(1, 'big')
return int_from_bytes(tagged_hash('FROST/deterministic/nonce', buf))
def deterministic_sign(secshare: bytes, my_id: bytes, aggothernonce: bytes, ids: List[bytes], pubshares: List[PlainPk], tweaks: List[bytes], is_xonly: List[bool], msg: bytes, rand: Optional[bytes]) -> Tuple[bytes, bytes]:
def deterministic_sign(secshare: bytes, my_id: int, aggothernonce: bytes, ids: List[int], pubshares: List[PlainPk], tweaks: List[bytes], is_xonly: List[bool], msg: bytes, rand: Optional[bytes]) -> Tuple[bytes, bytes]:
if rand is not None:
secshare_ = bytes_xor(secshare, tagged_hash('FROST/aux', rand))
else:
@ -385,27 +374,29 @@ def deterministic_sign(secshare: bytes, my_id: bytes, aggothernonce: bytes, ids:
tweaked_gpk = get_xonly_pk(group_pubkey_and_tweak(pubshares, ids, tweaks, is_xonly))
k_1 = det_nonce_hash(secshare_, aggothernonce, tweaked_gpk, msg, 0) % n
k_2 = det_nonce_hash(secshare_, aggothernonce, tweaked_gpk, msg, 1) % n
k_1 = Scalar.from_int_wrapping(det_nonce_hash(secshare_, aggothernonce, tweaked_gpk, msg, 0))
k_2 = Scalar.from_int_wrapping(det_nonce_hash(secshare_, aggothernonce, tweaked_gpk, msg, 1))
# k_1 == 0 or k_2 == 0 cannot occur except with negligible probability.
assert k_1 != 0
assert k_2 != 0
R_s1 = point_mul(G, k_1)
R_s2 = point_mul(G, k_2)
assert R_s1 is not None
assert R_s2 is not None
R_s1 = k_1 * G
R_s2 = k_2 * G
assert not R_s1.infinity
assert not R_s2.infinity
pubnonce = cbytes(R_s1) + cbytes(R_s2)
secnonce = bytearray(bytes_from_int(k_1) + bytes_from_int(k_2))
secnonce = bytearray(k_1.to_bytes() + k_2.to_bytes())
try:
aggnonce = nonce_agg([pubnonce, aggothernonce], [my_id, AGGREGATOR_ID])
except Exception:
raise InvalidContributionError(None, "aggothernonce")
# Since `pubnonce` can never be invalid, blame aggregator's pubnonce.
# REVIEW: should we introduce an unknown participant or aggregator error?
raise InvalidContributionError(AGGREGATOR_ID, "aggothernonce")
session_ctx = SessionContext(aggnonce, ids, pubshares, tweaks, is_xonly, msg)
psig = sign(secnonce, secshare, my_id, session_ctx)
return (pubnonce, psig)
def partial_sig_verify(psig: bytes, ids: List[bytes], pubnonces: List[bytes], pubshares: List[PlainPk], tweaks: List[bytes], is_xonly: List[bool], msg: bytes, i: int) -> bool:
def partial_sig_verify(psig: bytes, ids: List[int], pubnonces: List[bytes], pubshares: List[PlainPk], tweaks: List[bytes], is_xonly: List[bool], msg: bytes, i: int) -> bool:
if not len(ids) == len(pubnonces) == len(pubshares):
raise ValueError('The ids, pubnonces and pubshares arrays must have the same length.')
if len(tweaks) != len(is_xonly):
@ -415,36 +406,39 @@ def partial_sig_verify(psig: bytes, ids: List[bytes], pubnonces: List[bytes], pu
return partial_sig_verify_internal(psig, ids[i], pubnonces[i], pubshares[i], session_ctx)
#todo: catch `cpoint`` ValueError and return false
def partial_sig_verify_internal(psig: bytes, my_id: bytes, pubnonce: bytes, pubshare: bytes, session_ctx: SessionContext) -> bool:
def partial_sig_verify_internal(psig: bytes, my_id: int, pubnonce: bytes, pubshare: bytes, session_ctx: SessionContext) -> bool:
(Q, gacc, _, b, R, e) = get_session_values(session_ctx)
s = int_from_bytes(psig)
if s >= n:
try:
s = Scalar.from_bytes_checked(psig)
except ValueError:
return False
if not session_has_signer_pubshare(session_ctx, pubshare):
return False
R_s1 = cpoint(pubnonce[0:33])
R_s2 = cpoint(pubnonce[33:66])
Re_s_ = point_add(R_s1, point_mul(R_s2, b))
Re_s = Re_s_ if has_even_y(R) else point_negate(Re_s_)
Re_s_ = R_s1 + b * R_s2
Re_s = Re_s_ if R.has_even_y() else -Re_s_
P = cpoint(pubshare)
if P is None:
return False
a = get_session_interpolating_value(session_ctx, my_id)
g = 1 if has_even_y(Q) else n - 1
g_ = g * gacc % n
return point_mul(G, s) == point_add(Re_s, point_mul(P, e * a * g_ % n))
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[bytes], session_ctx: SessionContext) -> bytes:
def partial_sig_agg(psigs: List[bytes], ids: List[int], session_ctx: SessionContext) -> bytes:
assert AGGREGATOR_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 = 0
s = Scalar(0)
for my_id, psig in zip(ids, psigs):
s_i = int_from_bytes(psig)
if s_i >= n:
raise InvalidContributionError(int_from_bytes(my_id), "psig")
s = (s + s_i) % n
g = 1 if has_even_y(Q) else n - 1
s = (s + e * g * tacc) % n
return xbytes(R) + bytes_from_int(s)
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 xbytes(R) + s.to_bytes()

1
src/jmfrost/frost_ref/utils/__init__.py

@ -1 +0,0 @@
# -*- coding: utf-8 -*-

93
src/jmfrost/frost_ref/utils/bip340.py

@ -1,93 +0,0 @@
#
# The following helper functions were copied from the BIP-340 reference implementation:
# https://github.com/bitcoin/bips/blob/master/bip-0340/reference.py
#
from typing import Tuple, Optional
import hashlib
p = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F
n = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141
# Points are tuples of X and Y coordinates and the point at infinity is
# represented by the None keyword.
G = (0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798, 0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8)
Point = Tuple[int, int]
# 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 is_infinite(P: Optional[Point]) -> bool:
return P is None
def x(P: Point) -> int:
assert not is_infinite(P)
return P[0]
def y(P: Point) -> int:
assert not is_infinite(P)
return P[1]
def point_add(P1: Optional[Point], P2: Optional[Point]) -> Optional[Point]:
if P1 is None:
return P2
if P2 is None:
return P1
if (x(P1) == x(P2)) and (y(P1) != y(P2)):
return None
if P1 == P2:
lam = (3 * x(P1) * x(P1) * pow(2 * y(P1), p - 2, p)) % p
else:
lam = ((y(P2) - y(P1)) * pow(x(P2) - x(P1), p - 2, p)) % p
x3 = (lam * lam - x(P1) - x(P2)) % p
return (x3, (lam * (x(P1) - x3) - y(P1)) % p)
def point_mul(P: Optional[Point], n: int) -> Optional[Point]:
R = None
for i in range(256):
if (n >> i) & 1:
R = point_add(R, P)
P = point_add(P, P)
return R
def bytes_from_int(x: int) -> bytes:
return x.to_bytes(32, byteorder="big")
def lift_x(b: bytes) -> Optional[Point]:
x = int_from_bytes(b)
if x >= p:
return None
y_sq = (pow(x, 3, p) + 7) % p
y = pow(y_sq, (p + 1) // 4, p)
if pow(y, 2, p) != y_sq:
return None
return (x, y if y & 1 == 0 else p-y)
def int_from_bytes(b: bytes) -> int:
return int.from_bytes(b, byteorder="big")
def has_even_y(P: Point) -> bool:
assert not is_infinite(P)
return y(P) % 2 == 0
def schnorr_verify(msg: bytes, pubkey: bytes, sig: bytes) -> bool:
if len(msg) != 32:
raise ValueError('The message must be a 32-byte array.')
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.')
P = lift_x(pubkey)
r = int_from_bytes(sig[0:32])
s = int_from_bytes(sig[32:64])
if (P is None) or (r >= p) or (s >= n):
return False
e = int_from_bytes(tagged_hash("BIP0340/challenge", sig[0:32] + pubkey + msg)) % n
R = point_add(point_mul(G, s), point_mul(P, n - e))
if (R is None) or (not has_even_y(R)) or (x(R) != r):
return False
return True

4
test/jmclient/test_frost_clients.py

@ -801,7 +801,7 @@ class FROSTClientTestCase(DKGClientTestCaseBase):
assert ready_list == set([self.nick2])
assert nonce_agg and len(nonce_agg)== 66
assert dkg_session_id and dkg_session_id == self.dkg_session_id
assert ids == [1, 2]
assert ids == [0, 1]
assert msg and len(msg) == 32 and msg == msg_bytes
# miminum pub_nonce set already presented, ignoring additional
@ -870,7 +870,7 @@ class FROSTClientTestCase(DKGClientTestCaseBase):
) = self.fc1.frost_agg1(session_id)
assert nonce_agg and len(nonce_agg)== 66
assert dkg_session_id and dkg_session_id == self.dkg_session_id
assert ids == [1, 2]
assert ids == [0, 1]
assert msg and len(msg) == 32 and msg == msg_bytes
def test_frost_round2(self):

85
test/jmfrost/test_frost_ref.py

@ -14,8 +14,9 @@ from jmfrost.frost_ref.reference import (
check_frost_key_compatibility, check_pubshares_correctness,
check_group_pubkey_correctness, nonce_gen_internal, AGGREGATOR_ID,
partial_sig_verify_internal, nonce_gen)
from jmfrost.frost_ref.utils.bip340 import (
schnorr_verify, bytes_from_int, int_from_bytes, point_mul, G, n)
from jmfrost.secp256k1lab.bip340 import (
schnorr_verify, bytes_from_int, int_from_bytes, G)
from jmfrost.secp256k1lab.secp256k1 import Scalar
from trusted_keygen import trusted_dealer_keygen
@ -39,33 +40,35 @@ def assert_raises(exception, try_fn, except_fn):
def get_error_details(test_case):
error = test_case["error"]
if error["type"] == "invalid_contribution":
if error["type"] == "InvalidContributionError":
exception = InvalidContributionError
if "contrib" in error:
except_fn = lambda e: e.id == error["signer_id"] and e.contrib == error["contrib"]
except_fn = lambda e: e.id == error["id"] and e.contrib == error["contrib"]
else:
except_fn = lambda e: e.id == error["signer_id"]
elif error["type"] == "value":
except_fn = lambda e: e.id == error["id"]
elif error["type"] == "ValueError":
exception = ValueError
# except_fn = except_fn1
except_fn = lambda e: str(e) == error["message"]
else:
raise RuntimeError(f"Invalid error type: {error['type']}")
return exception, except_fn
def generate_frost_keys(max_participants: int, min_participants: int) -> Tuple[PlainPk, List[bytes], List[bytes], List[PlainPk]]:
def generate_frost_keys(max_participants: int, min_participants: int) -> Tuple[PlainPk, List[int], List[bytes], List[PlainPk]]:
if not (2 <= min_participants <= max_participants):
raise ValueError('values must satisfy: 2 <= min_participants <= max_participants')
secret = secrets.randbelow(n - 1) + 1
secret = Scalar.from_bytes_wrapping(secrets.token_bytes(32))
P, secshares, pubshares = trusted_dealer_keygen(secret, max_participants, min_participants)
group_pk = PlainPk(cbytes(P))
ser_identifiers = [bytes_from_int(secshare_i[0]) for secshare_i in secshares]
# we need decrement by one, since our identifiers represent integer indices
identifiers = [int(secshare_i[0] - 1) for secshare_i in secshares]
ser_secshares = [bytes_from_int(secshare_i[1]) for secshare_i in secshares]
ser_pubshares = [PlainPk(cbytes(pubshare_i)) for pubshare_i in pubshares]
return (group_pk, ser_identifiers, ser_secshares, ser_pubshares)
return (group_pk, identifiers, ser_secshares, ser_pubshares)
# REVIEW we might not need this vectors, as `check_pubshares_correctness`
# can't be implemented securely (they need secshares!!).
def test_keygen_vectors():
with open(os.path.join(sys.path[0], 'vectors', 'keygen_vectors.json')) as f:
test_data = json.load(f)
@ -76,7 +79,7 @@ def test_keygen_vectors():
min_participants = test_case["min_participants"]
group_pk = bytes.fromhex(test_case["group_public_key"])
# assert the length using min & max participants?
ids = [bytes_from_int(i) for i in test_case["participant_identifiers"]]
ids = test_case["participant_identifiers"]
pubshares = fromhex_all(test_case["participant_pubshares"])
secshares = fromhex_all(test_case["participant_secshares"])
@ -94,7 +97,7 @@ def test_keygen_vectors():
max_participants = test_case["max_participants"]
min_participants = test_case["min_participants"]
group_pk = bytes.fromhex(test_case["group_public_key"])
ids = [bytes_from_int(i) for i in test_case["participant_identifiers"]]
ids = test_case["participant_identifiers"]
pubshares = fromhex_all(test_case["participant_pubshares"])
secshares = fromhex_all(test_case["participant_secshares"])
@ -141,14 +144,14 @@ def test_nonce_agg_vectors():
#todo: assert the min_participants <= len(pubnonces, ids) <= max_participants
#todo: assert the values of ids too? 1 <= id <= max_participants?
pubnonces = [pubnonces_list[i] for i in test_case["pubnonce_indices"]]
ids = [bytes_from_int(i) for i in test_case["participant_identifiers"]]
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 = [bytes_from_int(i) for i in test_case["participant_identifiers"]]
ids = test_case["participant_identifiers"]
assert_raises(exception, lambda: nonce_agg(pubnonces, ids), except_fn)
# todo: include vectors from the frost draft too
@ -171,9 +174,9 @@ def test_sign_verify_vectors():
# The public nonce corresponding to first participant (secnonce_p1[0]) is at index 0
k_1 = int_from_bytes(secnonces_p1[0][0:32])
k_2 = int_from_bytes(secnonces_p1[0][32:64])
R_s1 = point_mul(G, k_1)
R_s2 = point_mul(G, k_2)
assert R_s1 is not None and R_s2 is not None
R_s1 = k_1 * G
R_s2 = k_2 * G
assert not R_s1.infinity and not R_s2.infinity
assert pubnonces[0] == cbytes(R_s1) + cbytes(R_s2)
aggnonces = fromhex_all(test_data["aggnonces"])
@ -185,7 +188,7 @@ def test_sign_verify_vectors():
verify_error_test_cases = test_data["verify_error_test_cases"]
for test_case in valid_test_cases:
ids_tmp = [bytes_from_int(ids[i]) for i in test_case["id_indices"]]
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"]]
@ -206,12 +209,12 @@ def test_sign_verify_vectors():
for test_case in sign_error_test_cases:
exception, except_fn = get_error_details(test_case)
ids_tmp = [bytes_from_int(ids[i]) for i in test_case["id_indices"]]
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 = bytes_from_int(test_case["signer_id"]) if signer_index is None else ids_tmp[signer_index]
my_id = test_case["signer_id"] if signer_index is None else ids_tmp[signer_index]
secnonce_tmp = bytearray(secnonces_p1[test_case["secnonce_index"]])
session_ctx = SessionContext(aggnonce_tmp, ids_tmp, pubshares_tmp, [], [], msg)
@ -219,7 +222,7 @@ def test_sign_verify_vectors():
for test_case in verify_fail_test_cases:
psig = bytes.fromhex(test_case["psig"])
ids_tmp = [bytes_from_int(ids[i]) for i in test_case["id_indices"]]
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"]]
@ -231,7 +234,7 @@ def test_sign_verify_vectors():
exception, except_fn = get_error_details(test_case)
psig = bytes.fromhex(test_case["psig"])
ids_tmp = [bytes_from_int(ids[i]) for i in test_case["id_indices"]]
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"]]
@ -254,11 +257,11 @@ def test_tweak_vectors():
secnonce_p1 = bytearray(bytes.fromhex(test_data["secnonce_p1"]))
pubnonces = fromhex_all(test_data["pubnonces"])
# The public nonce corresponding to first participant (secnonce_p1[0]) is at index 0
k_1 = int_from_bytes(secnonce_p1[0:32])
k_2 = int_from_bytes(secnonce_p1[32:64])
R_s1 = point_mul(G, k_1)
R_s2 = point_mul(G, k_2)
assert R_s1 is not None and R_s2 is not None
k_1 = Scalar.from_bytes_checked(secnonce_p1[0:32])
k_2 = Scalar.from_bytes_checked(secnonce_p1[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] == cbytes(R_s1) + cbytes(R_s2)
aggnonces = fromhex_all(test_data["aggnonces"])
@ -270,7 +273,7 @@ def test_tweak_vectors():
error_test_cases = test_data["error_test_cases"]
for test_case in valid_test_cases:
ids_tmp = [bytes_from_int(ids[i]) for i in test_case["id_indices"]]
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"]]
@ -292,7 +295,7 @@ def test_tweak_vectors():
for test_case in error_test_cases:
exception, except_fn = get_error_details(test_case)
ids_tmp = [bytes_from_int(ids[i]) for i in test_case["id_indices"]]
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"]]
@ -319,10 +322,10 @@ def test_det_sign_vectors():
msgs = fromhex_all(test_data["msgs"])
valid_test_cases = test_data["valid_test_cases"]
sign_error_test_cases = test_data["sign_error_test_cases"]
error_test_cases = test_data["error_test_cases"]
for test_case in valid_test_cases:
ids_tmp = [bytes_from_int(ids[i]) for i in test_case["id_indices"]]
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"])
@ -342,16 +345,16 @@ def test_det_sign_vectors():
session_ctx = SessionContext(aggnonce_tmp, ids_tmp, pubshares_tmp, tweaks, is_xonly, msg)
assert partial_sig_verify_internal(psig, my_id, pubnonce, pubshares_tmp[signer_index], session_ctx)
for test_case in sign_error_test_cases:
for test_case in error_test_cases:
exception, except_fn = get_error_details(test_case)
ids_tmp = [bytes_from_int(ids[i]) for i in test_case["id_indices"]]
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 = bytes_from_int(test_case["signer_id"]) if signer_index is None else ids_tmp[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
try_fn = lambda: deterministic_sign(secshare_p1, my_id, aggothernonce, ids_tmp, pubshares_tmp, tweaks, is_xonly, msg, rand)
@ -371,14 +374,13 @@ def test_sig_agg_vectors():
pubnonces = fromhex_all(test_data["pubnonces"])
tweaks = fromhex_all(test_data["tweaks"])
psigs = fromhex_all(test_data["psigs"])
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 = [bytes_from_int(ids[i]) for i in test_case["id_indices"]]
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"])
@ -387,7 +389,7 @@ def test_sig_agg_vectors():
tweaks_tmp = [tweaks[i] for i in test_case["tweak_indices"]]
tweak_modes_tmp = test_case["is_xonly"]
psigs_tmp = [psigs[i] for i in test_case["psig_indices"]]
psigs_tmp = fromhex_all(test_case["psigs"])
expected = bytes.fromhex(test_case["expected"])
session_ctx = SessionContext(aggnonce_tmp, ids_tmp, pubshares_tmp, tweaks_tmp, tweak_modes_tmp, msg)
@ -403,20 +405,19 @@ def test_sig_agg_vectors():
for test_case in error_test_cases:
exception, except_fn = get_error_details(test_case)
ids_tmp = [bytes_from_int(ids[i]) for i in test_case["id_indices"]]
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 = [psigs[i] for i in test_case["psig_indices"]]
psigs_tmp = fromhex_all(test_case["psigs"])
session_ctx = SessionContext(aggnonce_tmp, ids_tmp, pubshares_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() -> None:
iterations = 4
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

48
test/jmfrost/trusted_keygen.py

@ -12,42 +12,40 @@ from typing import Tuple, List, NewType
import unittest
# todo: replace random module with secrets
import random
import secrets
# for [1] import functions from reference
# [2] specify path for bip340 when running reference.py
# import sys, os
# script_dir = os.path.dirname(os.path.abspath(__file__))
# parent_dir = os.path.abspath(os.path.join(script_dir, '..'))
# sys.path.append(parent_dir)
from jmfrost.frost_ref.utils.bip340 import (
Point, n as curve_order, bytes_from_int,
point_mul, G, has_even_y, x
)
from jmfrost.secp256k1lab.secp256k1 import G, GE, Scalar
curve_order = GE.ORDER
# point on the secret polynomial, represents a signer's secret share
PolyPoint = Tuple[int, int]
# point on the secp256k1 curve, represents a signer's public share
ECPoint = Point
ECPoint = GE
#
# The following helper functions and types were copied from reference.py
#
PlainPk = NewType('PlainPk', bytes)
def xbytes(P: Point) -> bytes:
return bytes_from_int(x(P))
def xbytes(P: GE) -> bytes:
return P.to_bytes_xonly()
def cbytes(P: Point) -> bytes:
a = b'\x02' if has_even_y(P) else b'\x03'
return a + xbytes(P)
def cbytes(P: GE) -> bytes:
return P.to_bytes_compressed()
def derive_interpolating_value_internal(L: List[int], x_i: int) -> int:
def derive_interpolating_value_internal(L: List[int], x_i: int) -> Scalar:
num, deno = 1, 1
for x_j in L:
if x_j == x_i:
continue
num *= x_j
deno *= (x_j - x_i)
return num * pow(deno, curve_order - 2, curve_order) % curve_order
return Scalar.from_int_wrapping(num * pow(deno, curve_order - 2, curve_order))
#
# End of helper functions and types copied from reference.py.
#
@ -61,16 +59,16 @@ def polynomial_evaluate(coeffs: List[int], x: int) -> int:
return res % curve_order
def secret_share_combine(shares: List[PolyPoint]) -> int:
def secret_share_combine(shares: List[PolyPoint]) -> Scalar:
x_coords = []
for (x, y) in shares:
x_coords.append(x)
secret = 0
secret = Scalar(0)
for (x, y) in shares:
delta = y * derive_interpolating_value_internal(x_coords, x)
secret += delta
return secret % curve_order
return secret
# coeffs shouldn't include the const term (i.e. secret)
def secret_share_shard(secret: int, coeffs: List[int], max_participants: int) -> List[PolyPoint]:
@ -83,21 +81,21 @@ def secret_share_shard(secret: int, coeffs: List[int], max_participants: int) ->
secshares.append(secshare_i)
return secshares
def trusted_dealer_keygen(secret_key: int, max_participants: int, min_participants: int) -> Tuple[ECPoint, List[PolyPoint], List[ECPoint]]:
assert (1 <= secret_key <= curve_order - 1)
def trusted_dealer_keygen(secret_key: Scalar, max_participants: int, min_participants: int) -> Tuple[ECPoint, List[PolyPoint], List[ECPoint]]:
assert secret_key != 0
assert (2 <= min_participants <= max_participants)
# we don't force BIP340 compatibility of group pubkey in keygen
P = point_mul(G, secret_key)
assert P is not None
P = secret_key * G
assert not P.infinity
coeffs = []
for i in range(min_participants - 1):
coeffs.append(random.randint(1, curve_order - 1))
secshares = secret_share_shard(secret_key, coeffs, max_participants)
secshares = secret_share_shard(int(secret_key), coeffs, max_participants)
pubshares = []
for secshare in secshares:
X = point_mul(G, secshare[1])
assert X is not None
X = secshare[1] * G
assert not X.infinity
pubshares.append(X)
return (P, secshares, pubshares)
@ -134,19 +132,19 @@ class Tests(unittest.TestCase):
self.assertEqual(secret_share_combine(shares), expected_secret)
def test_trusted_dealer_keygen(self) -> None:
secret_key = random.randint(1, curve_order - 1)
secret_key = Scalar.from_bytes_wrapping(secrets.token_bytes(32))
max_participants = 5
min_participants = 3
group_pk, secshares, pubshares = trusted_dealer_keygen(secret_key, max_participants, min_participants)
# group_pk need not be xonly (i.e., have even y always)
self.assertEqual(group_pk, point_mul(G, secret_key))
self.assertEqual(group_pk, secret_key * G)
self.assertEqual(secret_share_combine(secshares), secret_key)
self.assertEqual(len(secshares), max_participants)
self.assertEqual(len(pubshares), max_participants)
for i in range(len(pubshares)):
with self.subTest(i=i):
self.assertEqual(pubshares[i], point_mul(G, secshares[i][1]))
self.assertEqual(pubshares[i], secshares[i][1] * G)
if __name__=='__main__':
unittest.main()

384
test/jmfrost/vectors/det_sign_vectors.json

@ -1,15 +1,21 @@
{
"max_participants": 5,
"min_participants": 3,
"group_public_key": "037940B3ED1FDC360252A6F48058C7B94276DFB6AA2B7D51706FB48326B19E7AE1",
"secshare_p1":"81D0D40CDF044588167A987C14552954DB187AC5AD3B1CA40D7B03DCA32AFDFB",
"identifiers": [1, 2, 3, 4, 5],
"group_public_key": "03F9186397E61022663935B3FDFF7880A9F0EC288D8B054DF6AC2BC5777B5FBBB1",
"secshare_p1": "6FF0F78C1F0E76C4AB67C1B32E0B4B1652120B794AC1AE7EC3992DE06092B566",
"identifiers": [
0,
1,
2,
3,
4
],
"pubshares": [
"02BB66437FCAA01292BFB4BB6F19D67818FE693215C36C4663857F1DC8AB8BF4FA",
"02C3250013C86AA9C3011CD40B2658CBC5B950DD21FFAA4EDE1BB66E18A063CED5",
"03259D7068335012C08C5D80E181969ED7FFA08F7973E3ED9C8C0BFF3EC03C223E",
"02A22971750242F6DA35B8DB0DFE74F38A3227118B296ADD2C65E324E2B7EB20AD",
"03541293535BB662F8294C4BEB7EA25F55FEAE86C6BAE0CEBD741EAAA28639A6E6",
"0260C5B10BAF5D471F0D09ED9BDED80B23CEFE0C9DC0F26AD1A0453A6FDFF663E2",
"028B96AE32F17C49C6111D6BF7D17E89428734D6DED0E31C480F2BABD263DDFA28",
"029456C5A981CF9DA72BF7AF0F82C44A343DC08419911286E975D16697D93B9A61",
"030CAA62081616E0B833FBD39A1058C1A11A23FB8307127D486914E8BF2E5935BB",
"03E483B7D41072D6E883447EB85617A086290EB67B40C89F3A787CF1B66005F488",
"020000000000000000000000000000000000000000000000000000000000000007"
],
"msgs": [
@ -20,183 +26,314 @@
"valid_test_cases": [
{
"rand": "0000000000000000000000000000000000000000000000000000000000000000",
"aggothernonce": "02FCDBEE416E4426FB4004BAB2B416164845DEC27337AD2B96184236D715965AB2039F71F389F6808DC6176F062F80531E13EA5BC2612B690FC284AE66C2CD859CE9",
"id_indices": [0, 1, 2],
"pubshare_indices": [0, 1, 2],
"aggothernonce": "0213D29E3E76BC742FD1229AAB5B723D5B6BF9F0C9A08E7FAED8389728EEE319720223AAACF1C1200E764BE964AB3A0D72EC2EDB68C0B750AAD6430D30DC3DB948FD",
"id_indices": [
0,
1,
2
],
"pubshare_indices": [
0,
1,
2
],
"tweaks": [],
"is_xonly": [],
"msg_index": 0,
"signer_index": 0,
"expected": [
"038E14A90FB2C66535B42850F009E2F1857000433042EE647066034FDE7F5A3F3C026CD7BDD51BE1490486F1E905B90020CB8294AFE7B6A051069C07D3B2FD9DC12A",
"89FA301CA35D6BD839089D0EBA7EA16B2C90818103BAA85F92FE6C01F0E0FB61"
"02797030BB92CCC543328AE3694912E0C695B31434BED9B1F54179070F6A5CC23F02E0A46F1F31AAF21697649D9C610D5069183C97BF55BE5CD8205D95B97E29AD58",
"41A225B68A2E3DE5001D580F178216009ED30039AB1D1F44EDE6E1BEAE67C167"
],
"comment": "Signing with minimum number of participants"
},
{
"rand": "0000000000000000000000000000000000000000000000000000000000000000",
"aggothernonce": "02FCDBEE416E4426FB4004BAB2B416164845DEC27337AD2B96184236D715965AB2039F71F389F6808DC6176F062F80531E13EA5BC2612B690FC284AE66C2CD859CE9",
"id_indices": [1, 0, 2],
"pubshare_indices": [1, 0, 2],
"aggothernonce": "020E0F2E2DB618128F7FAEC01D8C861BB62DF24751AA4D4C72FD121B900B5228AD03CB1A8E5F9B695FA0D98BAC1D9D6A7EE841E578FDD2A92154F7E3868B7124DA1C",
"id_indices": [
1,
0,
2
],
"pubshare_indices": [
1,
0,
2
],
"tweaks": [],
"is_xonly": [],
"msg_index": 0,
"signer_index": 1,
"expected": [
"038E14A90FB2C66535B42850F009E2F1857000433042EE647066034FDE7F5A3F3C026CD7BDD51BE1490486F1E905B90020CB8294AFE7B6A051069C07D3B2FD9DC12A",
"89FA301CA35D6BD839089D0EBA7EA16B2C90818103BAA85F92FE6C01F0E0FB61"
"0298181EF98F083A6532C9927D862E695988C32248803E0DF9E6EE928C414E72310227C3976F753B6E19ED05B626D175BBFBBDC50D2F3ECDF242ED0DE650190B6477",
"0301992329720756528A28275620472B25A9CC9C0B742E607051319C07593505"
],
"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": "02FCDBEE416E4426FB4004BAB2B416164845DEC27337AD2B96184236D715965AB2039F71F389F6808DC6176F062F80531E13EA5BC2612B690FC284AE66C2CD859CE9",
"id_indices": [2, 1, 0],
"pubshare_indices": [2, 1, 0],
"aggothernonce": "027BB05D8B1B72ADF014CA75FFD1DBC0751CF6F6701DCB8F00EE8D14819D36FA0103E43F43CD74416248AEE796A9E55EC5AC5F335B37AB9D9A2D8BC7B988719E577C",
"id_indices": [
0,
3,
4
],
"pubshare_indices": [
0,
3,
4
],
"tweaks": [],
"is_xonly": [],
"msg_index": 0,
"signer_index": 2,
"signer_index": 0,
"expected": [
"038E14A90FB2C66535B42850F009E2F1857000433042EE647066034FDE7F5A3F3C026CD7BDD51BE1490486F1E905B90020CB8294AFE7B6A051069C07D3B2FD9DC12A",
"89FA301CA35D6BD839089D0EBA7EA16B2C90818103BAA85F92FE6C01F0E0FB61"
"0302D3C4D08CC1CCA3D3247A0F9037DE6BBB4BBBAE103C1F07F3B19D0BD821288803B34C402DF30B8FF47652C4CF54C04416D7676F2AFBF12C1ED1DD2B31F4BA5229",
"24F39311027EE1579E1E08FE3C34393475766CAC1D23789CB8A71C29537A436E"
],
"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"
"comment": "Partial-signature changes if the members of signers set changes"
},
{
"rand": "0000000000000000000000000000000000000000000000000000000000000000",
"aggothernonce": "02FCDBEE416E4426FB4004BAB2B416164845DEC27337AD2B96184236D715965AB2039F71F389F6808DC6176F062F80531E13EA5BC2612B690FC284AE66C2CD859CE9",
"id_indices": [0, 3, 4],
"pubshare_indices": [0, 3, 4],
"rand": null,
"aggothernonce": "028ECB34F618EDD8E01F9DDE4BC303D5C8C081C0AE7C5E2703A593DB8B410EC68602F5401EA7D2CEB7CEBA7DCD944427E8E7AB2CDB0893F54ACA8002D4F629B56B9F",
"id_indices": [
0,
1,
2
],
"pubshare_indices": [
0,
1,
2
],
"tweaks": [],
"is_xonly": [],
"msg_index": 0,
"signer_index": 0,
"expected": [
"038E14A90FB2C66535B42850F009E2F1857000433042EE647066034FDE7F5A3F3C026CD7BDD51BE1490486F1E905B90020CB8294AFE7B6A051069C07D3B2FD9DC12A",
"E5C27E441A9D433CDC4A36F669967E4304435CE5E6E7722D871237C3B4A2EC99"
"03ED0354DE40A57BF7DD8AE974DF7E924CCBE3E98635CEE0CEBF9122B0A87776E40230AC4F0B056F5C695BE64B7EB671C34046F1F6886B2CAD6E67DD525B9FFB2678",
"C46C61A6C37136C91A906417321FAFD9ECE23E376706AC1ED266999C63475D35"
],
"comment": "Partial-signature changes if the members of signers set changes"
"comment": "Signing without auxiliary randomness"
},
{
"rand": null,
"aggothernonce": "02D26EF7E09A4BC0A2CF295720C64BAD56A28EF50B6BECBD59AF6F3ADE6C2480C503D11B9993AE4C2D38EA2591287F7B744976F0F0B79104B96D6399507FC533E893",
"id_indices": [0, 1, 2, 3],
"pubshare_indices": [0, 1, 2, 3],
"rand": "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF",
"aggothernonce": "02AEB6CB8631282E221B7688E3147BB544A1038AF0D09B81769E4656D3CF8C5AD202A5AC7DDF391347D3520C5B45BEAEC5D4A3C3971DD36961D3D5F51BFD39DEDD38",
"id_indices": [
0,
1,
2
],
"pubshare_indices": [
0,
1,
2
],
"tweaks": [],
"is_xonly": [],
"msg_index": 0,
"signer_index": 0,
"expected": [
"02EEE6300500FB508012424A0F47621F9A844A939020DD64C4254939D848B675A5037BDEA362CBE55D6D36A7635FC21ED8AC2FA05E9B63A8242E07969F6E2D4E36E5",
"97440C51FCB602911455E6147938F5B81C0C1AF32ADAFD98F5A66A4616289D5D"
"031E9E4C323358CA94230A58BB2106FF1E7DB61D39140A72C5977B99F4DBC55CAA02FE99098A3A88283143F01F603387F164E3CC239AB9F3947A5695054CCA7AB1E2",
"AE6B3D7B276A534B083089B5447F739495E29E7D82EAB8F826B7EBA15AC352B6"
],
"comment": "Signing without auxiliary randomness"
"comment": "Signing with max auxiliary randomness"
},
{
"rand": "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF",
"aggothernonce": "03C7E3D6456228347B658911BF612967F36C7791C24F9607ADB34E09F8CC1126D803D2C9C6E3D1A11463F8C2D57B145A814F5D44FD1A42F7A024140AC30D48EE0BEE",
"id_indices": [0, 1, 2, 3, 4],
"pubshare_indices": [0, 1, 2, 3, 4],
"rand": "0000000000000000000000000000000000000000000000000000000000000000",
"aggothernonce": "0285527346501F0CF58180DDACEC0303C440A5F1979E1D5DDC095BC69E90C890AE02D83EAF404F8334B3718E93B4A6D400116A3BE5650060E259FD3F9583238FE719",
"id_indices": [
0,
1,
2,
3
],
"pubshare_indices": [
0,
1,
2,
3
],
"tweaks": [],
"is_xonly": [],
"msg_index": 0,
"signer_index": 0,
"expected": [
"020EBAD8A2F6099A0A0A62439F0A2A0E7DF6918DDE55183AFFF112DF2940FF76DE026C4A1C132CF16CFCFC28FEB02651C44719C900DF6F16407711CA8DB31E2A46B8",
"83271933ECB71C566F3BA61A645B1396251CBF7EDA77B1D2AF5C689003AB631B"
"02D33E23B5027B30C2728D2A2C5517C64113B99CB730ED6EE2D6C3ED870D6C27460318EFCB7B6D506C64DB2AD4139576096E77594840C8ED759E3E12AC084EA605C1",
"A37FB870C75150EE624C756883F717D3D71D449BD9D2A93127C65BEA02EBC316"
],
"comment": "Signing with maximum number of participants and maximum auxiliary randomness value"
"comment": "Signing with t < no of participants < n"
},
{
"rand": "0000000000000000000000000000000000000000000000000000000000000000",
"aggothernonce": "02FCDBEE416E4426FB4004BAB2B416164845DEC27337AD2B96184236D715965AB2039F71F389F6808DC6176F062F80531E13EA5BC2612B690FC284AE66C2CD859CE9",
"id_indices": [0, 1, 2],
"pubshare_indices": [0, 1, 2],
"aggothernonce": "033106B0B7046D4268BE15D33BFF0FA6BEB7AC6C63E1A7CA076CB2299F9C7F4F5403AD2DFC59616CC47CBB6EAE1D0F80ABEB29558A47CC3096153B3F89B95681790E",
"id_indices": [
0,
1,
2,
3,
4
],
"pubshare_indices": [
0,
1,
2,
3,
4
],
"tweaks": [],
"is_xonly": [],
"msg_index": 0,
"signer_index": 0,
"expected": [
"02353FEFB48A09807D37D6040CB6CB9335D4A0F870A01C7399C4CCDD3E154E5802028E4150FD0C02FD4CC2BF63A428D1BD8CCC9A582B198C0EC767A66B6DB0E853BB",
"E3A1005520BE1A090843D2DEBB0ED58763B9BAC68BDD022BB9C4BF7AAF2B12FF"
],
"comment": "Signing with maximum number of participants"
},
{
"rand": "0000000000000000000000000000000000000000000000000000000000000000",
"aggothernonce": "0366A9BBD0FC622AB6FE672AAF557253B53F907A830DBE1772738510A0DA2EDEC1037F2ED964BE01FDC02D095C5BBFC25CCD6378FE1EF43DFC5225CA17D088C3190D",
"id_indices": [
0,
1,
2
],
"pubshare_indices": [
0,
1,
2
],
"tweaks": [],
"is_xonly": [],
"msg_index": 1,
"signer_index": 0,
"expected": [
"0203375B47194F99B8B682B9DCDFB972A066C243BC7AA951A792FF02A707A3C7870367C40EE43583D0FC0F44696BED09D9B89652FC45B738FF03AF8ECA854A5424B1",
"2D2F6A697B0632291E3240D9E48F82A454EEB9F566987CB5E7612C0B75D41208"
"025DE0E382D19FBA9B6184B53E677DF2ABE80BEFB6673973EDEB0353010FA3052002F798E4794B686B9F8F4A0B7AC785345FC46C7B1E196643152814B8FF93A4B636",
"5D5F50CDF275E8FC0C0C30244858FB0A053924E76C0468176418528A514E280B"
],
"comment": "Empty message"
},
{
"rand": "0000000000000000000000000000000000000000000000000000000000000000",
"aggothernonce": "02FCDBEE416E4426FB4004BAB2B416164845DEC27337AD2B96184236D715965AB2039F71F389F6808DC6176F062F80531E13EA5BC2612B690FC284AE66C2CD859CE9",
"id_indices": [0, 1, 2],
"pubshare_indices": [0, 1, 2],
"aggothernonce": "031111FEA894361402C6BCAA1A62116041E1188BB6D1736A77830EF08F377FBAB003C2B19EB56ABBD76B037A4274E7A33C276A8467297575E7CF577385253252EC92",
"id_indices": [
0,
1,
2
],
"pubshare_indices": [
0,
1,
2
],
"tweaks": [],
"is_xonly": [],
"msg_index": 2,
"signer_index": 0,
"expected": [
"0256B5FD4623C09A0E073CE04FF488DA0C4319A528CBA3FC26307682AD2CAD069003F8E94981F0D4A0A879CFAEEE0A060DF1E12889FB7C3CEAC498310827F63CBDE2",
"347C67E959FCA9460F907C0D2CAF5DD427E5CFD7E15330BA38DA6E986ED91B0E"
"031E524A3AB3A4AB6DCA2D3CF299D9D713A0F0F361AF3946314B2520981885EBBD03040C42A117CDA8A27A2E276EDE3F2BE89381AC67D69A5F746143257D6C9DE305",
"0BC055DE21CE6D55994E46C14E7B9DB20D2C6E4517DFCC3395A108A6C0F6680E"
],
"comment": "Message longer than 32 bytes (38-byte msg)"
},
{
"rand": "0000000000000000000000000000000000000000000000000000000000000000",
"aggothernonce": "02FCDBEE416E4426FB4004BAB2B416164845DEC27337AD2B96184236D715965AB2039F71F389F6808DC6176F062F80531E13EA5BC2612B690FC284AE66C2CD859CE9",
"id_indices": [0, 1, 2],
"pubshare_indices": [0, 1, 2],
"tweaks": ["E8F791FF9225A2AF0102AFFF4A9A723D9612A682A25EBE79802B263CDFCD83BB"],
"is_xonly": [true],
"aggothernonce": "0213D29E3E76BC742FD1229AAB5B723D5B6BF9F0C9A08E7FAED8389728EEE319720223AAACF1C1200E764BE964AB3A0D72EC2EDB68C0B750AAD6430D30DC3DB948FD",
"id_indices": [
0,
1,
2
],
"pubshare_indices": [
0,
1,
2
],
"tweaks": [
"E8F791FF9225A2AF0102AFFF4A9A723D9612A682A25EBE79802B263CDFCD83BB"
],
"is_xonly": [
true
],
"msg_index": 0,
"signer_index": 0,
"expected": [
"0341E28C13AB55A689C4698F31AD68250636B9E41FACCB0D358B4BD9A3DF09B1920311E0CED48F4B3B51E010159D3657FD8EC9DFF1FD30AD28FC126F62AA1C53C451",
"817169757CF62879BCB2F1DFE895E6781664CA0D18534290C22EC0E40187B7FC"
"0344F294A4446CCCF497F28A3B7CE0AC31EC138E701121C933E73272B95786B28502CB2AF7D73448FB2DD11ED0856F6707164F89A3C417B8B15FC3E7B88DA21A7E49",
"8F4E139B534E8F38ED20BF5DDAF63272EE22CA294A3C8287C150BD9DD38748D5"
],
"comment": "Tweaked group public key"
"comment": "Signing with tweaks"
}
],
"sign_error_test_cases": [
"error_test_cases": [
{
"rand": "0000000000000000000000000000000000000000000000000000000000000000",
"aggothernonce": "02FCDBEE416E4426FB4004BAB2B416164845DEC27337AD2B96184236D715965AB2039F71F389F6808DC6176F062F80531E13EA5BC2612B690FC284AE66C2CD859CE9",
"id_indices": [3, 1, 2],
"pubshare_indices": [0, 1, 2],
"id_indices": [
3,
1,
2
],
"pubshare_indices": [
0,
1,
2
],
"tweaks": [],
"is_xonly": [],
"msg_index": 0,
"signer_index": null,
"signer_id": 1,
"signer_id": 0,
"error": {
"type": "value",
"type": "ValueError",
"message": "The signer's id must be present in the participant identifier list."
},
"comment": "The signer's id is not in the participant identifier list."
"comment": "The signer's id is not in the participant identifier list"
},
{
"rand": "0000000000000000000000000000000000000000000000000000000000000000",
"aggothernonce": "02FCDBEE416E4426FB4004BAB2B416164845DEC27337AD2B96184236D715965AB2039F71F389F6808DC6176F062F80531E13EA5BC2612B690FC284AE66C2CD859CE9",
"id_indices": [0, 1, 2, 1],
"pubshare_indices": [0, 1, 2, 1],
"aggothernonce": "02AC09CB4AFB2C0E17E511E328C2F554CEC4EBF351A3E5A0DAB9411C338900A04E02282EFBABFAA97CD281FBB1D8DFD1AF839FBC28129DB52E09BEF43C3657BD729D",
"id_indices": [
0,
1,
2,
1
],
"pubshare_indices": [
0,
1,
2,
1
],
"tweaks": [],
"is_xonly": [],
"msg_index": 0,
"signer_index": 0,
"error": {
"type": "value",
"type": "ValueError",
"message": "The participant identifier list must contain unique elements."
},
"comment": "The participant identifier list contains duplicate elements."
"comment": "The participant identifier list contains duplicate elements"
},
{
"rand": "0000000000000000000000000000000000000000000000000000000000000000",
"aggothernonce": "02FCDBEE416E4426FB4004BAB2B416164845DEC27337AD2B96184236D715965AB2039F71F389F6808DC6176F062F80531E13EA5BC2612B690FC284AE66C2CD859CE9",
"id_indices": [0, 1, 2],
"pubshare_indices": [3, 1, 2],
"aggothernonce": "0213D29E3E76BC742FD1229AAB5B723D5B6BF9F0C9A08E7FAED8389728EEE319720223AAACF1C1200E764BE964AB3A0D72EC2EDB68C0B750AAD6430D30DC3DB948FD",
"id_indices": [
0,
1,
2
],
"pubshare_indices": [
3,
1,
2
],
"tweaks": [],
"is_xonly": [],
"msg_index": 0,
"signer_index": 0,
"error": {
"type": "value",
"type": "ValueError",
"message": "The signer's pubshare must be included in the list of pubshares."
},
"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."
@ -204,30 +341,45 @@
{
"rand": "0000000000000000000000000000000000000000000000000000000000000000",
"aggothernonce": "02FCDBEE416E4426FB4004BAB2B416164845DEC27337AD2B96184236D715965AB2039F71F389F6808DC6176F062F80531E13EA5BC2612B690FC284AE66C2CD859CE9",
"id_indices": [0, 1, 2],
"pubshare_indices": [0, 1],
"id_indices": [
0,
1,
2
],
"pubshare_indices": [
0,
1
],
"tweaks": [],
"is_xonly": [],
"msg_index": 0,
"signer_index": 0,
"error": {
"type": "value",
"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": "02FCDBEE416E4426FB4004BAB2B416164845DEC27337AD2B96184236D715965AB2039F71F389F6808DC6176F062F80531E13EA5BC2612B690FC284AE66C2CD859CE9",
"id_indices": [0, 1, 2],
"pubshare_indices": [0, 1, 5],
"aggothernonce": "0213D29E3E76BC742FD1229AAB5B723D5B6BF9F0C9A08E7FAED8389728EEE319720223AAACF1C1200E764BE964AB3A0D72EC2EDB68C0B750AAD6430D30DC3DB948FD",
"id_indices": [
0,
1,
2
],
"pubshare_indices": [
0,
1,
5
],
"tweaks": [],
"is_xonly": [],
"msg_index": 0,
"signer_index": 0,
"error": {
"type": "invalid_contribution",
"signer_id": 3,
"type": "InvalidContributionError",
"id": 2,
"contrib": "pubshare"
},
"comment": "Signer 3 provided an invalid participant public share"
@ -235,15 +387,23 @@
{
"rand": "0000000000000000000000000000000000000000000000000000000000000000",
"aggothernonce": "048465FCF0BBDBCF443AABCCE533D42B4B5A10966AC09A49655E8C42DAAB8FCD61037496A3CC86926D452CAFCFD55D25972CA1675D549310DE296BFF42F72EEEA8C9",
"id_indices": [0, 1, 2],
"pubshare_indices": [0, 1, 2],
"id_indices": [
0,
1,
2
],
"pubshare_indices": [
0,
1,
2
],
"tweaks": [],
"is_xonly": [],
"msg_index": 0,
"signer_index": 0,
"error": {
"type": "invalid_contribution",
"signer_id": null,
"type": "InvalidContributionError",
"id": null,
"contrib": "aggothernonce"
},
"comment": "aggothernonce is invalid due wrong tag, 0x04, in the first half"
@ -251,33 +411,53 @@
{
"rand": "0000000000000000000000000000000000000000000000000000000000000000",
"aggothernonce": "0000000000000000000000000000000000000000000000000000000000000000000287BF891D2A6DEAEBADC909352AA9405D1428C15F4B75F04DAE642A95C2548480",
"id_indices": [0, 1, 2],
"pubshare_indices": [0, 1, 2],
"id_indices": [
0,
1,
2
],
"pubshare_indices": [
0,
1,
2
],
"tweaks": [],
"is_xonly": [],
"msg_index": 0,
"signer_index": 0,
"error": {
"type": "invalid_contribution",
"signer_id": null,
"type": "InvalidContributionError",
"id": null,
"contrib": "aggothernonce"
},
"comment": "aggothernonce is invalid because first half corresponds to point at infinity"
},
{
"rand": "0000000000000000000000000000000000000000000000000000000000000000",
"aggothernonce": "02FCDBEE416E4426FB4004BAB2B416164845DEC27337AD2B96184236D715965AB2039F71F389F6808DC6176F062F80531E13EA5BC2612B690FC284AE66C2CD859CE9",
"id_indices": [0, 1, 2],
"pubshare_indices": [0, 1, 2],
"tweaks": ["FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141"],
"is_xonly": [false],
"aggothernonce": "0213D29E3E76BC742FD1229AAB5B723D5B6BF9F0C9A08E7FAED8389728EEE319720223AAACF1C1200E764BE964AB3A0D72EC2EDB68C0B750AAD6430D30DC3DB948FD",
"id_indices": [
0,
1,
2
],
"pubshare_indices": [
0,
1,
2
],
"tweaks": [
"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141"
],
"is_xonly": [
false
],
"msg_index": 0,
"signer_index": 0,
"error": {
"type": "value",
"type": "ValueError",
"message": "The tweak must be less than n."
},
"comment": "Tweak is invalid because it exceeds group size"
}
]
}
}

110
test/jmfrost/vectors/keygen_vectors.json

@ -1,78 +1,86 @@
{
"valid_test_cases": [
{
"max_participants": 3,
"min_participants": 2,
"group_public_key": "02F37C34B66CED1FB51C34A90BDAE006901F10625CC06C4F64663B0EAE87D87B4F",
"participant_identifiers": [1, 2, 3],
"participant_pubshares": [
"026BAEE4BF7D4B9C4567DFFF6F3C2C76DF5C082E9320CD8187D6AB5965BC5A119A",
"03DACC9463E5186F3C81AE1B314F7B09001A22B28BB56AD0ABD3F376818F9604AB",
"031404710E938032DB0D4F6A4CD20AE37384BE98BA9FE05B42D139361202B391E6"
],
"participant_secshares": [
"08F89FFE80AC94DCB920C26F3F46140BFC7F95B493F8310F5FC1EA2B01F4254C",
"04F0FEAC2EDCEDC6CE1253B7FAB8C86B856A797F44D83D82A385554E6E401984",
"00E95D59DD0D46B0E303E500B62B7CCB0E555D49F5B849F5E748C071DA8C0DBC"
]
},
{
"max_participants": 5,
"min_participants": 3,
"group_public_key": "037940B3ED1FDC360252A6F48058C7B94276DFB6AA2B7D51706FB48326B19E7AE1",
"participant_identifiers": [1, 2, 3, 4, 5],
"group_public_key": "03F9186397E61022663935B3FDFF7880A9F0EC288D8B054DF6AC2BC5777B5FBBB1",
"participant_identifiers": [
0,
1,
2,
3,
4
],
"participant_pubshares": [
"02BB66437FCAA01292BFB4BB6F19D67818FE693215C36C4663857F1DC8AB8BF4FA",
"02C3250013C86AA9C3011CD40B2658CBC5B950DD21FFAA4EDE1BB66E18A063CED5",
"03259D7068335012C08C5D80E181969ED7FFA08F7973E3ED9C8C0BFF3EC03C223E",
"02A22971750242F6DA35B8DB0DFE74F38A3227118B296ADD2C65E324E2B7EB20AD",
"03541293535BB662F8294C4BEB7EA25F55FEAE86C6BAE0CEBD741EAAA28639A6E6"
"0260C5B10BAF5D471F0D09ED9BDED80B23CEFE0C9DC0F26AD1A0453A6FDFF663E2",
"028B96AE32F17C49C6111D6BF7D17E89428734D6DED0E31C480F2BABD263DDFA28",
"029456C5A981CF9DA72BF7AF0F82C44A343DC08419911286E975D16697D93B9A61",
"030CAA62081616E0B833FBD39A1058C1A11A23FB8307127D486914E8BF2E5935BB",
"03E483B7D41072D6E883447EB85617A086290EB67B40C89F3A787CF1B66005F488"
],
"participant_secshares": [
"81D0D40CDF044588167A987C14552954DB187AC5AD3B1CA40D7B03DCA32AFDFB",
"10130412FDB9A10F7DF862CE8763311B7D1B7AACF211ED32272F0DAC49DF6743",
"1362A14AE07243C93C24E7EEA3FB8C619338C24925F8E5E488DAE1D3DE7B2236",
"8BBFABB4872E2DB5510027DC6A1E3B271D70519A48F006BB327E805360FE2ED4",
"792A234FF1ED5ED3BC8A2297D9CB3D6D61134BB9ABAEAF7A64478A9E01324BDC"
"6FF0F78C1F0E76C4AB67C1B32E0B4B1652120B794AC1AE7EC3992DE06092B566",
"6AA9C77B0E11698988CFC04769B0C1C75302BEA5F13A952A0F6CDE10B7CB5281",
"23D5D746FA6798F85E3E58A02FBBD1529D58651A2F5C986FBA85E0C36E445E84",
"9B7526EFE41105112BB38ABD802C79B6EBC1DBBCB470588B84B6948554341AB0",
"D187B675CB0DADD3F12F569F5B02BAF5839045A6D12D3541AE2C9AC9996445C4"
]
}
],
"pubshare_correctness_fail_test_cases": [
{
"max_participants": 2,
"min_participants": 2,
"group_public_key": "0256C92CA18AD18E5E14075E4CDA4C9471E1F69EFF06DA31B9DB8C431697457C96",
"participant_identifiers": [1, 2],
"max_participants": 5,
"min_participants": 3,
"group_public_key": "03F9186397E61022663935B3FDFF7880A9F0EC288D8B054DF6AC2BC5777B5FBBB1",
"participant_identifiers": [
0,
1,
2,
3,
4
],
"participant_pubshares": [
"02EF27116868EEC72F1AEF13F0383A83479DB7DFBDE55B568ADC0ABC28B0C82AEB",
"0381EE46DB9582B6AA84AB1F39CAAD930899B44ACCB75EDFFBB29CDB8E2136F2A7"
"0360C5B10BAF5D471F0D09ED9BDED80B23CEFE0C9DC0F26AD1A0453A6FDFF663E2",
"028B96AE32F17C49C6111D6BF7D17E89428734D6DED0E31C480F2BABD263DDFA28",
"029456C5A981CF9DA72BF7AF0F82C44A343DC08419911286E975D16697D93B9A61",
"030CAA62081616E0B833FBD39A1058C1A11A23FB8307127D486914E8BF2E5935BB",
"03E483B7D41072D6E883447EB85617A086290EB67B40C89F3A787CF1B66005F488"
],
"participant_secshares": [
"1903097297A1E0FD75FCBCDB66DC21C65ACEC527100566459F1BBF2FA7388D53",
"B9B2CD71F1C09B8D6F675D05CDF1396B28FF626CD8C69B9DF4D3B6BDCB57EFF2"
],
"comment": "Invalid pubshare (parity flipped) for participant 2"
"6FF0F78C1F0E76C4AB67C1B32E0B4B1652120B794AC1AE7EC3992DE06092B566",
"6AA9C77B0E11698988CFC04769B0C1C75302BEA5F13A952A0F6CDE10B7CB5281",
"23D5D746FA6798F85E3E58A02FBBD1529D58651A2F5C986FBA85E0C36E445E84",
"9B7526EFE41105112BB38ABD802C79B6EBC1DBBCB470588B84B6948554341AB0",
"D187B675CB0DADD3F12F569F5B02BAF5839045A6D12D3541AE2C9AC9996445C4"
]
}
],
"group_pubkey_correctness_fail_test_cases": [
{
"max_participants": 3,
"max_participants": 5,
"min_participants": 3,
"group_public_key": "0354F1E67AAFFB49654AF3EE5B0C68D8CF24468D014453F1F13B5221512A0BCE78",
"participant_identifiers": [1, 2, 3],
"group_public_key": "02F9186397E61022663935B3FDFF7880A9F0EC288D8B054DF6AC2BC5777B5FBBB1",
"participant_identifiers": [
0,
1,
2,
3,
4
],
"participant_pubshares": [
"037A01FF2705D679CDC34E04366CC3BA95BD9E883AC7E33B640D744BE6BCC2D140",
"039E2C0AE44EA1203606D04B711667C07D1695ADC36FBF07DD37B7ECA85490262C",
"027C782638AD6A8A95DEDF6CBA940E89E827EC5C4FCF693EAB7D70927C3CA59FDB"
"0260C5B10BAF5D471F0D09ED9BDED80B23CEFE0C9DC0F26AD1A0453A6FDFF663E2",
"028B96AE32F17C49C6111D6BF7D17E89428734D6DED0E31C480F2BABD263DDFA28",
"029456C5A981CF9DA72BF7AF0F82C44A343DC08419911286E975D16697D93B9A61",
"030CAA62081616E0B833FBD39A1058C1A11A23FB8307127D486914E8BF2E5935BB",
"03E483B7D41072D6E883447EB85617A086290EB67B40C89F3A787CF1B66005F488"
],
"participant_secshares": [
"A3236A9D6EF252A5C59F17B544ECE39487FFD80F158EB93F8AA4AF707BFA5511",
"7FA1BE8BCC29555EFAAC4B19D47E26467E056B9DE2F6E0B7B844940FD43D1047",
"8BACD727EA7C2156F476BFC8EF5B332FE0663464AC3F117C0B69D6460A4AD25D"
],
"comment": "Invalid group public key (parity flipped)"
"6FF0F78C1F0E76C4AB67C1B32E0B4B1652120B794AC1AE7EC3992DE06092B566",
"6AA9C77B0E11698988CFC04769B0C1C75302BEA5F13A952A0F6CDE10B7CB5281",
"23D5D746FA6798F85E3E58A02FBBD1529D58651A2F5C986FBA85E0C36E445E84",
"9B7526EFE41105112BB38ABD802C79B6EBC1DBBCB470588B84B6948554341AB0",
"D187B675CB0DADD3F12F569F5B02BAF5839045A6D12D3541AE2C9AC9996445C4"
]
}
]
}

64
test/jmfrost/vectors/nonce_agg_vectors.json

@ -10,47 +10,77 @@
],
"valid_test_cases": [
{
"pubnonce_indices": [0, 1],
"participant_identifiers": [1, 2],
"pubnonce_indices": [
0,
1
],
"participant_identifiers": [
0,
1
],
"expected_aggnonce": "035FE1873B4F2967F52FEA4A06AD5A8ECCBE9D0FD73068012C894E2E87CCB5804B024725377345BDE0E9C33AF3C43C0A29A9249F2F2956FA8CFEB55C8573D0262DC8"
},
{
"pubnonce_indices": [2, 3],
"participant_identifiers": [1, 2],
"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": [1, 2],
"pubnonce_indices": [
0,
4
],
"participant_identifiers": [
0,
1
],
"error": {
"type": "invalid_contribution",
"signer_id": 2,
"type": "InvalidContributionError",
"id": 1,
"contrib": "pubnonce"
},
"comment": "Public nonce from signer 2 is invalid due wrong tag, 0x04, in the first half"
},
{
"pubnonce_indices": [5, 1],
"participant_identifiers": [1, 2],
"pubnonce_indices": [
5,
1
],
"participant_identifiers": [
0,
1
],
"error": {
"type": "invalid_contribution",
"signer_id": 1,
"type": "InvalidContributionError",
"id": 0,
"contrib": "pubnonce"
},
"comment": "Public nonce from signer 1 is invalid because the second half does not correspond to an X coordinate"
},
{
"pubnonce_indices": [6, 1],
"participant_identifiers": [1, 2],
"pubnonce_indices": [
6,
1
],
"participant_identifiers": [
0,
1
],
"error": {
"type": "invalid_contribution",
"signer_id": 1,
"type": "InvalidContributionError",
"id": 0,
"contrib": "pubnonce"
},
"comment": "Public nonce from signer 1 is invalid because second half exceeds field size"
}
]
}
}

34
test/jmfrost/vectors/nonce_gen_vectors.json

@ -2,35 +2,35 @@
"test_cases": [
{
"rand_": "0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F",
"secshare": "0202020202020202020202020202020202020202020202020202020202020202",
"pubshare": "024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D0766",
"group_pk": "0707070707070707070707070707070707070707070707070707070707070707",
"secshare": "6FF0F78C1F0E76C4AB67C1B32E0B4B1652120B794AC1AE7EC3992DE06092B566",
"pubshare": "0260C5B10BAF5D471F0D09ED9BDED80B23CEFE0C9DC0F26AD1A0453A6FDFF663E2",
"group_pk": "F9186397E61022663935B3FDFF7880A9F0EC288D8B054DF6AC2BC5777B5FBBB1",
"msg": "0101010101010101010101010101010101010101010101010101010101010101",
"extra_in": "0808080808080808080808080808080808080808080808080808080808080808",
"expected_secnonce": "6135CE36209DB5E3E7B7A11ADE54D3028D3CFF089DA3C2EC7766921CC4FB3D1BBCD8A7035194A76F43D278C3CD541AEE014663A2251DDE34E8D900EDF1CAA3D9",
"expected_pubnonce": "02A5671568FD7AEA35369FE4A32530FD0D0A23D125627BEA374D9FA5676F645A6103EC4E899B1DBEFC08C51F48E3AFA8503759E9ECD3DE674D3C93FD0D92E15E631A",
"expected_secnonce": "A1A627B12B86D5A2D96F8622B8D8B0BB8063C3E745ADAA856DC6237AC250E3A8D56B4DDBD018B99A5ECA024E3D6A18EA53709B65C09477DCC7A34F1210234815",
"expected_pubnonce": "0308ADA47B1846E72806FE24DC0683213F57DBD845319C8998F499B06729756F0602CEE5EB6C8851988AF649C4FA3164A6C3AEE56801E9ED1F7A3EF3B1DADF3E2CAE",
"comment": ""
},
{
"rand_": "0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F",
"secshare": "0202020202020202020202020202020202020202020202020202020202020202",
"pubshare": "024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D0766",
"group_pk": "0707070707070707070707070707070707070707070707070707070707070707",
"secshare": "6FF0F78C1F0E76C4AB67C1B32E0B4B1652120B794AC1AE7EC3992DE06092B566",
"pubshare": "0260C5B10BAF5D471F0D09ED9BDED80B23CEFE0C9DC0F26AD1A0453A6FDFF663E2",
"group_pk": "F9186397E61022663935B3FDFF7880A9F0EC288D8B054DF6AC2BC5777B5FBBB1",
"msg": "",
"extra_in": "0808080808080808080808080808080808080808080808080808080808080808",
"expected_secnonce": "91EB573A7D57A17F1C7465D7301BCF90915B5731CDA408644819933DA3E366E354C8BF875D966C02C095428B4D2780AC8B929090EEE9AEF5E4DF250533FE9A08",
"expected_pubnonce": "0337513529D07800E8D1B7056456223BFA26B0C12C921ADC87114537D4A65E2E390257723240C10831A1DFD0489DAAA7DF204717EA27147DD9361480D984C763D8A2",
"comment": "Empty message"
"expected_secnonce": "8D9188CDBB2B6ABEB15A69EF0410011F9272070930AE653BA09A0E651406B025AFF6A7F9BE47BDECD4728CFAAE15D8438B824ADE4F6CC24981CA50CF697EF12B",
"expected_pubnonce": "03F52D75517ACCF7D2D0E16EE38F49A1AA6D942C8EE7D95ED118FAF5EA1932247A02B78FF76BC31893B9CE4B9961764B78C03F55299623BD2676C661E4D57CEA5098",
"comment": "Empty Message"
},
{
"rand_": "0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F",
"secshare": "0202020202020202020202020202020202020202020202020202020202020202",
"pubshare": "024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D0766",
"group_pk": "0707070707070707070707070707070707070707070707070707070707070707",
"secshare": "6FF0F78C1F0E76C4AB67C1B32E0B4B1652120B794AC1AE7EC3992DE06092B566",
"pubshare": "0260C5B10BAF5D471F0D09ED9BDED80B23CEFE0C9DC0F26AD1A0453A6FDFF663E2",
"group_pk": "F9186397E61022663935B3FDFF7880A9F0EC288D8B054DF6AC2BC5777B5FBBB1",
"msg": "2626262626262626262626262626262626262626262626262626262626262626262626262626",
"extra_in": "0808080808080808080808080808080808080808080808080808080808080808",
"expected_secnonce": "379F4A71682AEF59A022272B7226F02A870F6958873726E33906E765AA36C71D70418EE5C83B76A6BC0E84F04F4F3D92DE83994400404EC8AE35CEA0ECD378AF",
"expected_pubnonce": "02A5EA11BCF1BC60AA96D1BE0816F373A57FD00991BE6106FD5AB1F6986FAA2BA0030AF6A8479B9C91958F256AEC38339FD25D4F42A073EEB862B42282E00F323A4C",
"expected_secnonce": "360840271C1DFBDF9F8AAF091221B4DAEEF298206455A7AD47FC9EE149E90A35649D63DC966A6A6EE180543AC0C4D8CC54590F0BF310B077962E4B6BA95A34B0",
"expected_pubnonce": "02BF6229F37A207610A515C308806BD60DB173419B3B6346C4F61FFA93D6125F2003DC3564B07BB98F64D76870148666D0B43B54D702857839459056F96FB6E6E2F0",
"comment": "38-byte message"
},
{
@ -45,4 +45,4 @@
"comment": "Every optional parameter is absent"
}
]
}
}

291
test/jmfrost/vectors/sig_agg_vectors.json

@ -1,21 +1,27 @@
{
"max_participants": 5,
"min_participants": 3,
"group_public_key": "037940B3ED1FDC360252A6F48058C7B94276DFB6AA2B7D51706FB48326B19E7AE1",
"identifiers": [1, 2, 3, 4, 5],
"group_public_key": "03F9186397E61022663935B3FDFF7880A9F0EC288D8B054DF6AC2BC5777B5FBBB1",
"identifiers": [
0,
1,
2,
3,
4
],
"pubshares": [
"02BB66437FCAA01292BFB4BB6F19D67818FE693215C36C4663857F1DC8AB8BF4FA",
"02C3250013C86AA9C3011CD40B2658CBC5B950DD21FFAA4EDE1BB66E18A063CED5",
"03259D7068335012C08C5D80E181969ED7FFA08F7973E3ED9C8C0BFF3EC03C223E",
"02A22971750242F6DA35B8DB0DFE74F38A3227118B296ADD2C65E324E2B7EB20AD",
"03541293535BB662F8294C4BEB7EA25F55FEAE86C6BAE0CEBD741EAAA28639A6E6"
"0260C5B10BAF5D471F0D09ED9BDED80B23CEFE0C9DC0F26AD1A0453A6FDFF663E2",
"028B96AE32F17C49C6111D6BF7D17E89428734D6DED0E31C480F2BABD263DDFA28",
"029456C5A981CF9DA72BF7AF0F82C44A343DC08419911286E975D16697D93B9A61",
"030CAA62081616E0B833FBD39A1058C1A11A23FB8307127D486914E8BF2E5935BB",
"03E483B7D41072D6E883447EB85617A086290EB67B40C89F3A787CF1B66005F488"
],
"pubnonces": [
"021AD89905F193EC1CBED15CDD5F4F0E04FF187648390639C88AC291F2F88D407E02FD49462A942948DF6718EEE86FDD858B124375E6A034A4985D19EE95431E9E03",
"03A0640E5746CC90EC3EF04F133AF1B79DE67011927A9BA1510B9254E9C8698062037209BB6915B573D2E6394032E508B8285DD498FE8A85971AAB01ACF0C785A56B",
"02861EFD258C9099BEF14FA9B3B4E6229595D8200FC72D27F789D4CCC4352BB32B038496DA1C20DFE16D24D20F0374812347EE9CFF06928802C04A2D1B2D369F4628",
"0398DD496FFE3C14D2DDFB5D9FD1189DB829301A83C45F2A1DDF07238529F75D1D0233E8FF18899A66276D27AE5CE28A5170EEAAC4F05DEACC8E7DB1C55F8985495F",
"03C7B31E363526D04B5D31148EE6B042AF8CC7DFA922A42A69EB78B816D458D0B20257495EC72B1E59FB90A48B036FBD3D9AE4415C49B6171E108185124B99DE56AA"
"036012764FC86D835893E0E802FD74BF01B80F35E10288CAE866B2ADF3E218017502E128AB0D2112DFFC4852EBF5F62F69770EAD9CB777229FA27C4167F589CCCF63",
"03DB57751EA90F2DFCDF684CE3C47F2CD33E54FB821347827844AE04913F36AB8D02B38858FB1D03A92D1D734DF0445FD8603F861984EC61B368AAE29FE862678700",
"028B115FE43676F134793CD556EFFBE6995C53F8CC4EAFA139008CF0337D9F39D6030D4B01F9BBF4B603C08ECB78C3207E4B43A10273FD82267A40407C864B314F13",
"03AF20782F4A855E5671A0ED5E845EC9D4CA6DC662423C3B2AE098FE315086ED1E0387542FE72832136823C2206385C1935355F1CECF7AA92AA23E33E3B9C43B0C55",
"022525FD3CA2F5C36A5298BFC4E4175620E3DF5C6098337CD484A872F335C43B48037C254E70008552F26D59F4914E0612346E7B6879B84BE46ADA9A7F2862EE9159"
],
"tweaks": [
"B511DA492182A91B0FFB9A98020D55F260AE86D7ECBD0399C7383D59A5F2AF7C",
@ -23,110 +29,223 @@
"75448A87274B056468B977BE06EB1E9F657577B7320B0A3376EA51FD420D18A8"
],
"msg": "599C67EA410D005B9DA90817CF03ED3B1C868E4DA4EDF00A5880B0082C237869",
"psigs": [
"447D69D4E02693E3F6C04E198F34E89E17D65DC29C92E635E8BFB8D2908DCA6A",
"E7E02FDE0FA66D116C0DCF932F7976D611A4D0CF225087C2B8282153E461FA8B",
"E84B98E0B132F4049B061A949EF69E3DFBEB3E2712AEE2DEE0C5B6D517860339",
"714B7FCF4D3EA2F4BB2B22F786AEBF0C65E1A6E6FBEF04C39B60EAA1CA257CD5",
"DA815BBE9D06203D5ADD3AD5D3FE5F0D5405939EFD7EA3FED6DACA9E5449AD80",
"8E367AE4000EEEFCEF7F83DA1AC96181DC51BA0D83E0F834F67A0CFD487DBEF7",
"9CAB74D0FBCF14D89330D81C85482B8C720DC69899187F3A5432A5856609E92D",
"351F38F8B3126944362D9B3F0D83791CF3D623E746B84A58012DF4C9383909EC",
"B9ABA5EE2181EDE7A0D3D29DB147741F66B5A8EF3BB6C9CFD1FAD0D98E5A8A93",
"A2DF2C5ECB1141E0B55F47711BBDAE491F2F22D967FA1D9569200B7FB0754AD6",
"441DFF8E4E0E8368D21BD3DD70F151C7C581EC2B1931B8F041CC8C052FEBF046",
"DDC813A7AA07415634F2F6CC10984EF68216C75EA4F7A8E883DBA163C41CE2BA",
"2D64FC0371D08A7069997C1009814AF9C964DB64AEDB919AC229DA774AB09888",
"5F6481FC18E4CB223CB5BAB966165A1033349267702E7D75B5A0E5CACEA0E6A0",
"312170A9C271F67D09C8BE06A468106505CF6B7CD4DB1A40E02AF13213069EB0",
"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141"
],
"valid_test_cases": [
{
"id_indices": [0, 1, 2],
"pubshare_indices": [0, 1, 2],
"pubnonce_indices": [0, 1, 2],
"aggnonce": "022265D41FDEFDC64072C7E168B345D547208C6E02E4D76E2F0D91C0773CF9FC250230B496E7FC1C45EFFB0687CFFC556FDA507F69CAE9894022828903DC3198DAFE",
"id_indices": [
0,
1,
2
],
"pubshare_indices": [
0,
1,
2
],
"pubnonce_indices": [
0,
1,
2
],
"aggnonce": "038ECCE47C2DEBB46EBD7C04DE2580237AE4C37E565BC10253D925F25257F4F1E903BF7AEBEDAAAEA115592133BE23F991D97DD843F06875CFB678239BFAE407F668",
"tweak_indices": [],
"is_xonly": [],
"psig_indices": [0, 1, 2],
"expected": "8471BE6E49D0E43097DD32DA374039149F5D00165A8AD369AE86E362D13730DA14A93293A0FFF4F9FDD438415DA4FDB4B008B2EB730110600208D3E1EC0945AC",
"psigs": [
"5B4D7781863571D390C8A07AE857758E190D44921AED6E7AF029E001DE7F9ABA",
"2F743D1EBAFB1F5FD8857FFF9BD63CB797604DE408748C137EAA4D7DFCC27879",
"C0A1F2901E4AD0C98D1F311E5E85DD92700A2F9AC6B73DD6140DC7EA6EE4608B"
],
"expected": "CA27779316F7792D24FF139FBEB686B9CC10CC1B2E3A023761725A2E21BD3A4F4B63A7305F7B61FCF66D5198E2B38FD965C8E52A3AD09828C30F96DD79F0327D",
"comment": "Signing with minimum number of participants"
},
{
"id_indices": [2, 0, 1],
"pubshare_indices": [2, 0, 1],
"pubnonce_indices": [2, 0, 1],
"aggnonce": "022265D41FDEFDC64072C7E168B345D547208C6E02E4D76E2F0D91C0773CF9FC250230B496E7FC1C45EFFB0687CFFC556FDA507F69CAE9894022828903DC3198DAFE",
"id_indices": [
2,
0,
1
],
"pubshare_indices": [
2,
0,
1
],
"pubnonce_indices": [
2,
0,
1
],
"aggnonce": "038ECCE47C2DEBB46EBD7C04DE2580237AE4C37E565BC10253D925F25257F4F1E903BF7AEBEDAAAEA115592133BE23F991D97DD843F06875CFB678239BFAE407F668",
"tweak_indices": [],
"is_xonly": [],
"psig_indices": [2, 0, 1],
"expected": "8471BE6E49D0E43097DD32DA374039149F5D00165A8AD369AE86E362D13730DA14A93293A0FFF4F9FDD438415DA4FDB4B008B2EB730110600208D3E1EC0945AC",
"comment": "Order of the singer set shouldn't affect the aggregate signature. The expected value must match the previous test vector. "
"psigs": [
"C0A1F2901E4AD0C98D1F311E5E85DD92700A2F9AC6B73DD6140DC7EA6EE4608B",
"5B4D7781863571D390C8A07AE857758E190D44921AED6E7AF029E001DE7F9ABA",
"2F743D1EBAFB1F5FD8857FFF9BD63CB797604DE408748C137EAA4D7DFCC27879"
],
"expected": "CA27779316F7792D24FF139FBEB686B9CC10CC1B2E3A023761725A2E21BD3A4F4B63A7305F7B61FCF66D5198E2B38FD965C8E52A3AD09828C30F96DD79F0327D",
"comment": "Order of the singer set shouldn't affect the aggregate signature. The expected value must match the previous test vector."
},
{
"id_indices": [1, 3, 4],
"pubshare_indices": [1, 3, 4],
"pubnonce_indices": [1, 3, 4],
"aggnonce": "0248A0DA464AB5C69B7C0159A1D3773478D606AE3BFE38AC26556B3B4E5FA47668023848EEDE8406CDE99E2CA52D2135D9AC31BC5636DE8452C597A77611CBA9AFCC",
"tweak_indices": [0],
"is_xonly": [false],
"psig_indices": [3, 4, 5],
"expected": "BA3388FF06D512B23065196A8F8673EA2A6DBAE6714A3E634C258E176A009172462472BB88A65538F17BF5DC433EC01C1770CA5F233A2718662EA1019FDC3BB8",
"id_indices": [
0,
1,
2
],
"pubshare_indices": [
0,
1,
2
],
"pubnonce_indices": [
0,
1,
2
],
"aggnonce": "038ECCE47C2DEBB46EBD7C04DE2580237AE4C37E565BC10253D925F25257F4F1E903BF7AEBEDAAAEA115592133BE23F991D97DD843F06875CFB678239BFAE407F668",
"tweak_indices": [
0,
1,
2
],
"is_xonly": [
true,
false,
false
],
"psigs": [
"04D49E93C507A18D0C65EA9F366A53F7F02EB9ACE8FC92A6F4648D3CDBD040A8",
"E3804CDE8A1E5F2F6E3680561B194C0412DABE0BAFDBECA4AE15FEE8616E84A3",
"4A0101D2BB5D6C21CC7A91EF0B85B66E6B2CC9B68FF5F9246CC18D7C1DC3DAFE"
],
"expected": "A20BE914C439559FA5F0DE232AC7807D50D73197AA375873F13F1499CD4AB803C6A7B1CCF4C3E151ED738920850AA3246670F0BE9872FBB656AA692C964CD393",
"comment": "Signing with tweaked group public key"
},
{
"id_indices": [0, 1, 2, 3],
"pubshare_indices": [0, 1, 2, 3],
"pubnonce_indices": [0, 1, 2, 3],
"aggnonce": "03EE8C3A0DCB63B05B93370561E80BDA65BDB7412BD947F8CED8CE0B5574D87FC002D5E954284D0198FC64FFD0ABB50DF8B0C3A6B2B369A5DB3E318A058482B29BA1",
"tweak_indices": [0, 1, 2],
"is_xonly": [true, false, true],
"psig_indices": [6, 7, 8, 9],
"expected": "143C2B3A3F4847D0D9FA3D3D7EEF6135148345C0BB620707334ADF5F1395A17BB02DA3FEDB9108179331D06E0BBD34B19E3FFF0893616A2310D47F73077C5CD5",
"comment": ""
"id_indices": [
0,
1,
2,
3
],
"pubshare_indices": [
0,
1,
2,
3
],
"pubnonce_indices": [
0,
1,
2,
3
],
"aggnonce": "0218CA838EE6DE2703318E7399F23A1EE6DE41461DB51D7A8EEB626E66FC5D64270294D9CFC121378285C93B1BE040B24F78A4D33810C1889DB8D4CA19C453F28C73",
"tweak_indices": [],
"is_xonly": [],
"psigs": [
"4962F8B71C1FB75B76FF20899369D43F5D1745FC8697212913EEE1C524906CEC",
"49309C46397413B8EF6F20987FFB17E8C5E30ECF632EA953726B540D9E757575",
"0E48FDE21F2286EACD8075C9DB77CD64E6866DD01F40C403836B9E808ADAED17",
"7F6C824A35B8C2F87EF2215C7C3EB8317C68417E0AFCAC30679381D141165A19"
],
"expected": "6D07AA848B83EF162EBDE98D8E857BDCFA927601A6F080BEA6E364E337A4552520491529AA6F14F7B2E0D8486B1B71BFCB3A273364BA9A74B186F797BEC0E850",
"comment": "Signing with t < number of participants < n"
},
{
"id_indices": [0, 1, 2, 3, 4],
"pubshare_indices": [0, 1, 2, 3, 4],
"pubnonce_indices": [0, 1, 2, 3, 4],
"aggnonce": "03C03DE1E69FABAFE2BC9FF8940CD50BCCA1B5A35CB56A719264F8C93DA006837C03F59B87EEF390D4189504FFDE2EE709372E036DE71E0633A6B1D30D3A10EC6FFE",
"tweak_indices": [0, 1, 2],
"is_xonly": [true, false, true],
"psig_indices": [10, 11, 12, 13, 14],
"expected": "64F75B69667302B459330DE1221AEF5C8F04C44635E6078ED068344EF04FBA00273493772CABC9C2C87515F916118CCAB2D3902A6F5EAC6F155725B58DFCBBD3",
"id_indices": [
0,
1,
2,
3,
4
],
"pubshare_indices": [
0,
1,
2,
3,
4
],
"pubnonce_indices": [
0,
1,
2,
3,
4
],
"aggnonce": "02FEC5F20C003199EE392537E83AEEA5EA8A1C565C71959B844990A3C079FCCD640334C5B98145FDC9444C322220F53D7A314D4962450B3BBA68916C3FFB2824740E",
"tweak_indices": [],
"is_xonly": [],
"psigs": [
"9F3CF5ED5C970D7761FCE1289268E0AE14ECE831EF9C203C7A5791905B678FF2",
"63482B88CEE87AF15606F10357B4C08784EBC274C35EB500D3E6E60B1AAC0E63",
"ACEDE34DC30F0BE4BC282C7001A2B20532395C8979C9AD17F875674A51675535",
"69292182C37E386D35E8E7672B057E2A4C02C24D24208218BF1497FA41B929B6",
"A4056C3CC737D7D2A702A88CA491659FB4513CE6D11F48D84F43EAFA349034CA"
],
"expected": "9D549680421FD8F5FC5100A071E027F6CF74FB093695C1FD15D9114765AF96CCBCA192837944A48D51178E8FBB57370757084C96C3730CCED567A4C09D57CF88",
"comment": "Signing with max number of participants and tweaked group public key"
}
],
"error_test_cases": [
{
"id_indices": [0, 1, 2, 3, 4],
"pubshare_indices": [0, 1, 2, 3, 4],
"pubnonce_indices": [0, 1, 2, 3, 4],
"aggnonce": "03C03DE1E69FABAFE2BC9FF8940CD50BCCA1B5A35CB56A719264F8C93DA006837C03F59B87EEF390D4189504FFDE2EE709372E036DE71E0633A6B1D30D3A10EC6FFE",
"tweak_indices": [0, 1, 2],
"is_xonly": [true, false, true],
"psig_indices": [10, 11, 15, 13, 14],
"id_indices": [
0,
1,
2
],
"pubshare_indices": [
0,
1,
2
],
"pubnonce_indices": [
0,
1,
2
],
"aggnonce": "038ECCE47C2DEBB46EBD7C04DE2580237AE4C37E565BC10253D925F25257F4F1E903BF7AEBEDAAAEA115592133BE23F991D97DD843F06875CFB678239BFAE407F668",
"tweak_indices": [],
"is_xonly": [],
"psigs": [
"5B4D7781863571D390C8A07AE857758E190D44921AED6E7AF029E001DE7F9ABA",
"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141",
"C0A1F2901E4AD0C98D1F311E5E85DD92700A2F9AC6B73DD6140DC7EA6EE4608B"
],
"error": {
"type": "invalid_contribution",
"signer_id": 3,
"type": "InvalidContributionError",
"id": 1,
"contrib": "psig"
},
"comment": "Partial signature is invalid because it exceeds group size"
},
{
"id_indices": [0, 1, 2, 3, 4],
"pubshare_indices": [0, 1, 2, 3, 4],
"pubnonce_indices": [0, 1, 2, 3, 4],
"aggnonce": "03C03DE1E69FABAFE2BC9FF8940CD50BCCA1B5A35CB56A719264F8C93DA006837C03F59B87EEF390D4189504FFDE2EE709372E036DE71E0633A6B1D30D3A10EC6FFE",
"tweak_indices": [0, 1, 2],
"is_xonly": [true, false, true],
"psig_indices": [10, 11, 12, 13],
"id_indices": [
0,
1,
2
],
"pubshare_indices": [
0,
1,
2
],
"pubnonce_indices": [
0,
1,
2
],
"aggnonce": "038ECCE47C2DEBB46EBD7C04DE2580237AE4C37E565BC10253D925F25257F4F1E903BF7AEBEDAAAEA115592133BE23F991D97DD843F06875CFB678239BFAE407F668",
"tweak_indices": [],
"is_xonly": [],
"psigs": [
"5B4D7781863571D390C8A07AE857758E190D44921AED6E7AF029E001DE7F9ABA",
"2F743D1EBAFB1F5FD8857FFF9BD63CB797604DE408748C137EAA4D7DFCC27879"
],
"error": {
"type": "value",
"type": "ValueError",
"message": "The psigs and ids arrays must have the same length."
},
"comment": "Partial signature count doesn't match the signer set count"
}
]
}
}

524
test/jmfrost/vectors/sign_verify_vectors.json

@ -1,35 +1,41 @@
{
"max_participants": 5,
"min_participants": 3,
"group_public_key": "037940B3ED1FDC360252A6F48058C7B94276DFB6AA2B7D51706FB48326B19E7AE1",
"secshare_p1":"81D0D40CDF044588167A987C14552954DB187AC5AD3B1CA40D7B03DCA32AFDFB",
"identifiers": [1, 2, 3, 4, 5],
"group_public_key": "03F9186397E61022663935B3FDFF7880A9F0EC288D8B054DF6AC2BC5777B5FBBB1",
"secshare_p1": "6FF0F78C1F0E76C4AB67C1B32E0B4B1652120B794AC1AE7EC3992DE06092B566",
"identifiers": [
0,
1,
2,
3,
4
],
"pubshares": [
"02BB66437FCAA01292BFB4BB6F19D67818FE693215C36C4663857F1DC8AB8BF4FA",
"02C3250013C86AA9C3011CD40B2658CBC5B950DD21FFAA4EDE1BB66E18A063CED5",
"03259D7068335012C08C5D80E181969ED7FFA08F7973E3ED9C8C0BFF3EC03C223E",
"02A22971750242F6DA35B8DB0DFE74F38A3227118B296ADD2C65E324E2B7EB20AD",
"03541293535BB662F8294C4BEB7EA25F55FEAE86C6BAE0CEBD741EAAA28639A6E6",
"0260C5B10BAF5D471F0D09ED9BDED80B23CEFE0C9DC0F26AD1A0453A6FDFF663E2",
"028B96AE32F17C49C6111D6BF7D17E89428734D6DED0E31C480F2BABD263DDFA28",
"029456C5A981CF9DA72BF7AF0F82C44A343DC08419911286E975D16697D93B9A61",
"030CAA62081616E0B833FBD39A1058C1A11A23FB8307127D486914E8BF2E5935BB",
"03E483B7D41072D6E883447EB85617A086290EB67B40C89F3A787CF1B66005F488",
"020000000000000000000000000000000000000000000000000000000000000007"
],
"secnonces_p1": [
"96DF27F46CB6E0399C7A02811F6A4D695BBD7174115477679E956658FF2E83D618E4F670DF3DEB215934E4F68D4EEC71055B87288947D75F6E1EA9037FF62173",
"05AB25AF7D0F8EE4231470B5CF9DCBE7069E007FDDBD7053D5436CED37926CCBDE406FFC1E658CB2B870296D5913305AABC90CD56AEC7066051EB3A63C025C17",
"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
],
"pubnonces": [
"02FCDBEE416E4426FB4004BAB2B416164845DEC27337AD2B96184236D715965AB2039F71F389F6808DC6176F062F80531E13EA5BC2612B690FC284AE66C2CD859CE9",
"02D26EF7E09A4BC0A2CF295720C64BAD56A28EF50B6BECBD59AF6F3ADE6C2480C503D11B9993AE4C2D38EA2591287F7B744976F0F0B79104B96D6399507FC533E893",
"03C7E3D6456228347B658911BF612967F36C7791C24F9607ADB34E09F8CC1126D803D2C9C6E3D1A11463F8C2D57B145A814F5D44FD1A42F7A024140AC30D48EE0BEE",
"036409E6BA4A00E148E9BE2D3B4217A74B3A65F0D75489176EF8A7D2BD699B949002B1E9FA2A8AE80CD7CE1593B51402B980B56896DB5B5C2B07EDA2C0CFEB08AD93",
"02464144C7AFAEF651F63E330B1FFF6EEC43991F9AE75AE6069796C097B04DAE720288B464788E5DFC9C2CCD6A3CCBBED643666749250012DA220D1C9FC559214270",
"036012764FC86D835893E0E802FD74BF01B80F35E10288CAE866B2ADF3E218017502E128AB0D2112DFFC4852EBF5F62F69770EAD9CB777229FA27C4167F589CCCF63",
"03DB57751EA90F2DFCDF684CE3C47F2CD33E54FB821347827844AE04913F36AB8D02B38858FB1D03A92D1D734DF0445FD8603F861984EC61B368AAE29FE862678700",
"028B115FE43676F134793CD556EFFBE6995C53F8CC4EAFA139008CF0337D9F39D6030D4B01F9BBF4B603C08ECB78C3207E4B43A10273FD82267A40407C864B314F13",
"03AF20782F4A855E5671A0ED5E845EC9D4CA6DC662423C3B2AE098FE315086ED1E0387542FE72832136823C2206385C1935355F1CECF7AA92AA23E33E3B9C43B0C55",
"022525FD3CA2F5C36A5298BFC4E4175620E3DF5C6098337CD484A872F335C43B48037C254E70008552F26D59F4914E0612346E7B6879B84BE46ADA9A7F2862EE9159",
"0200000000000000000000000000000000000000000000000000000000000000090287BF891D2A6DEAEBADC909352AA9405D1428C15F4B75F04DAE642A95C2548480",
"03135EFD879EC3BC76E953758E0611E07057CA4F1EA935E8BA6151ED4696B7827A0397A1B70CF6403286EE0DD153DBFDCFBEE3D7A15745569C097A328C7CCB36E7E5"
"03C2193CFB6E402E369064FBAC870DAFABCCF6D7096C3CFE0DE30417A9368E4C33029CAF67D6AF4D6A7BBCC6FF86A8773DCE31FA0D7D2AE757C0C90E29729B84BA06"
],
"aggnonces": [
"02047C99228CEA528AE200A82CBE4CD188BC67D58F537D1904A16B07FCDE07C3A6038708199DFA5BC5C41A0DD0FBD7D0620ADB4AC9991F7DB55A155CE9396AA80D1A",
"0365B60FA963FCB2ED1454264942397DBFC244A4B6CBE8FDEAF6FB23F14F76610002433AB9A295A67CD2B45B001B6F8154DC6619C994776EBF65A3C88A4BC94DBC98",
"03AB37C47419536990037B903428008878E4F395823A135C2B39E67FA850CFF41F028967ECFE399759125F59F7142B6580D91F70DE1C9E9C6B0F56754B64370A4438",
"0353365AF75F7C246089940D57D3265947A1D27576E411AE9C98702516C72DB51B02F5483E63F474BDD8EAC03F99276ED5A2ED31786F5B0F1A8706BE7367BC1D4555",
"038ECCE47C2DEBB46EBD7C04DE2580237AE4C37E565BC10253D925F25257F4F1E903BF7AEBEDAAAEA115592133BE23F991D97DD843F06875CFB678239BFAE407F668",
"021EC32479E77C7FBB505FE86AF25E48D5CB887BE51567CF5C270781FBBBD78C6F02CDFAE98BEB8951A47ECBB10AF98131D999ABF7EA70ECC6E97BDE9CD5CD261408",
"0218CA838EE6DE2703318E7399F23A1EE6DE41461DB51D7A8EEB626E66FC5D64270294D9CFC121378285C93B1BE040B24F78A4D33810C1889DB8D4CA19C453F28C73",
"02FEC5F20C003199EE392537E83AEEA5EA8A1C565C71959B844990A3C079FCCD640334C5B98145FDC9444C322220F53D7A314D4962450B3BBA68916C3FFB2824740E",
"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"048465FCF0BBDBCF443AABCCE533D42B4B5A10966AC09A49655E8C42DAAB8FCD61037496A3CC86926D452CAFCFD55D25972CA1675D549310DE296BFF42F72EEEA8C9",
"028465FCF0BBDBCF443AABCCE533D42B4B5A10966AC09A49655E8C42DAAB8FCD61020000000000000000000000000000000000000000000000000000000000000009",
@ -42,214 +48,383 @@
],
"valid_test_cases": [
{
"id_indices": [0, 1, 2],
"pubshare_indices": [0, 1, 2],
"pubnonce_indices": [0, 1, 2],
"id_indices": [
0,
1,
2
],
"pubshare_indices": [
0,
1,
2
],
"pubnonce_indices": [
0,
1,
2
],
"aggnonce_index": 0,
"msg_index": 0,
"signer_index": 0,
"expected": "DEDAA44E6DB7FF1B40D8CBAA44DF3F8C80BD7CEC6A21AE22F34ED7ABC59E2AEC",
"expected": "B45B8C1195CBFC1904D09BE6E58DDB9A7DFC4100A8DEC40F9E5B80347FA6E1F3",
"comment": "Signing with minimum number of participants"
},
{
"id_indices": [1, 0, 2],
"pubshare_indices": [1, 0, 2],
"pubnonce_indices": [1, 0, 2],
"id_indices": [
1,
0,
2
],
"pubshare_indices": [
1,
0,
2
],
"pubnonce_indices": [
1,
0,
2
],
"aggnonce_index": 0,
"msg_index": 0,
"signer_index": 1,
"expected": "DEDAA44E6DB7FF1B40D8CBAA44DF3F8C80BD7CEC6A21AE22F34ED7ABC59E2AEC",
"comment": "Partial-signature shouldn't change if the order of signers set changes (without changing secnonces)"
},
{
"id_indices": [2, 1, 0],
"pubshare_indices": [2, 1, 0],
"pubnonce_indices": [2, 1, 0],
"aggnonce_index": 0,
"msg_index": 0,
"signer_index": 2,
"expected": "DEDAA44E6DB7FF1B40D8CBAA44DF3F8C80BD7CEC6A21AE22F34ED7ABC59E2AEC",
"comment": "Partial-signature shouldn't change if the order of signers set changes (without changing secnonces)"
"expected": "B45B8C1195CBFC1904D09BE6E58DDB9A7DFC4100A8DEC40F9E5B80347FA6E1F3",
"comment": "Partial-signature doesn't change if the order of signers set changes (without changing secnonces)"
},
{
"id_indices": [0, 3, 4],
"pubshare_indices": [0, 3, 4],
"pubnonce_indices": [0, 3, 4],
"id_indices": [
0,
3,
4
],
"pubshare_indices": [
0,
3,
4
],
"pubnonce_indices": [
0,
3,
4
],
"aggnonce_index": 1,
"msg_index": 0,
"signer_index": 0,
"expected": "599723B8E16DA7D67A43EB09E6A990BF5BA7CD441657FE14D654E8C0523D0814",
"expected": "3411850FF9714CA76FB447F8A1AC65AB629CEFF559A37316925A3C2FE2E159CA",
"comment": "Partial-signature changes if the members of signers set changes"
},
{
"id_indices": [0, 1, 2, 3],
"pubshare_indices": [0, 1, 2, 3],
"pubnonce_indices": [0, 1, 2, 3],
"id_indices": [
0,
1,
2,
3
],
"pubshare_indices": [
0,
1,
2,
3
],
"pubnonce_indices": [
0,
1,
2,
3
],
"aggnonce_index": 2,
"msg_index": 0,
"signer_index": 0,
"expected": "6762C37ABF433C029E6698B435D5F7BE634D7B64A8151ACB07402465DB7D4057"
"expected": "C4CD316B07118DC18F82D251F0055643A938AF61325DE8CC1BD2093DE8083365",
"comment": "Signing with t < number of participants < n"
},
{
"id_indices": [0, 1, 2, 3, 4],
"pubshare_indices": [0, 1, 2, 3, 4],
"pubnonce_indices": [0, 1, 2, 3, 4],
"id_indices": [
0,
1,
2,
3,
4
],
"pubshare_indices": [
0,
1,
2,
3,
4
],
"pubnonce_indices": [
0,
1,
2,
3,
4
],
"aggnonce_index": 3,
"msg_index": 0,
"signer_index": 0,
"expected": "32D17330BF21D4D058E52A07E86F21D653ED697C1CCFE6F4D17084EF5C99FF18",
"comment": "Signing with maximum number of participants"
"expected": "4B0BBE3946CEB999546213FA4CEBF328C48265822C45A11A84E0C9BB15990292",
"comment": "Signing with max number of participants"
},
{
"id_indices": [0, 1, 2],
"pubshare_indices": [0, 1, 2],
"pubnonce_indices": [0, 1, 6],
"id_indices": [
0,
1,
2
],
"pubshare_indices": [
0,
1,
2
],
"pubnonce_indices": [
0,
1,
6
],
"aggnonce_index": 4,
"msg_index": 0,
"signer_index": 0,
"expected": "16B1E11E2BB93911E0422715FD03C0C8F1B7845B6A69F8BB8AB90155D91C25B5",
"expected": "06512BBB8AA35AA2916D3A181562FB1876BF21A3B32E81A7C208DEBAAB61D0A8",
"comment": "Both halves of aggregate nonce correspond to point at infinity"
},
{
"id_indices": [0, 1, 2],
"pubshare_indices": [0, 1, 2],
"pubnonce_indices": [0, 1, 2],
"id_indices": [
0,
1,
2
],
"pubshare_indices": [
0,
1,
2
],
"pubnonce_indices": [
0,
1,
2
],
"aggnonce_index": 0,
"msg_index": 1,
"signer_index": 0,
"expected": "E1915436DC7D4BC162842A3C1BAA16E82A8D64056A02C5D2BD75784B604C23CD",
"expected": "583031C55BA1A0FB625DA9D6081AC88FB03EF181C43025AE6975F48825E73559",
"comment": "Empty message"
},
{
"id_indices": [0, 1, 2],
"pubshare_indices": [0, 1, 2],
"pubnonce_indices": [0, 1, 2],
"id_indices": [
0,
1,
2
],
"pubshare_indices": [
0,
1,
2
],
"pubnonce_indices": [
0,
1,
2
],
"aggnonce_index": 0,
"msg_index": 2,
"signer_index": 0,
"expected": "78E04F68D813CBAF68CB5E19A835C69B833138ED18BDE63CB399F52F559EAA17",
"expected": "195084A23012D70E44D20169B5ED8694B9DEC24B1E4AF1178F814D1FE3C76E91",
"comment": "Message longer than 32 bytes (38-byte msg)"
}
],
"sign_error_test_cases": [
{
"id_indices": [3, 1, 2],
"pubshare_indices": [0, 1, 2],
"id_indices": [
3,
1,
2
],
"pubshare_indices": [
0,
1,
2
],
"aggnonce_index": 0,
"msg_index": 0,
"signer_index": null,
"signer_id": 1,
"signer_id": 0,
"secnonce_index": 0,
"error": {
"type": "value",
"type": "ValueError",
"message": "The signer's id must be present in the participant identifier list."
},
"comment": "The signer's id is not in the participant identifier list."
"comment": "The signer's id is not in the participant identifier list"
},
{
"id_indices": [0, 1, 2, 1],
"pubshare_indices": [0, 1, 2, 1],
"id_indices": [
0,
1,
2,
1
],
"pubshare_indices": [
0,
1,
2,
1
],
"aggnonce_index": 0,
"msg_index": 0,
"signer_index": 0,
"secnonce_index": 0,
"error": {
"type": "value",
"type": "ValueError",
"message": "The participant identifier list must contain unique elements."
},
"comment": "The participant identifier list contains duplicate elements."
"comment": "The participant identifier list contains duplicate elements"
},
{
"id_indices": [0, 1, 2],
"pubshare_indices": [3, 1, 2],
"id_indices": [
0,
1,
2
],
"pubshare_indices": [
3,
1,
2
],
"aggnonce_index": 0,
"msg_index": 0,
"signer_index": 0,
"secnonce_index": 0,
"error": {
"type": "value",
"type": "ValueError",
"message": "The signer's pubshare must be included in the list of pubshares."
},
"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],
"id_indices": [
0,
1,
2
],
"pubshare_indices": [
0,
1
],
"aggnonce_index": 0,
"msg_index": 0,
"signer_index": 0,
"secnonce_index": 0,
"error": {
"type": "value",
"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, 2],
"pubshare_indices": [0, 1, 5],
"id_indices": [
0,
1,
2
],
"pubshare_indices": [
0,
1,
5
],
"aggnonce_index": 0,
"msg_index": 0,
"signer_index": 0,
"secnonce_index": 0,
"error": {
"type": "invalid_contribution",
"signer_id": 3,
"type": "InvalidContributionError",
"id": 2,
"contrib": "pubshare"
},
"comment": "Signer 3 provided an invalid participant public share"
},
{
"id_indices": [0, 1, 2],
"pubshare_indices": [0, 1, 2],
"id_indices": [
0,
1,
2
],
"pubshare_indices": [
0,
1,
2
],
"aggnonce_index": 5,
"msg_index": 0,
"signer_index": 0,
"secnonce_index": 0,
"error": {
"type": "invalid_contribution",
"signer_id": null,
"type": "InvalidContributionError",
"id": null,
"contrib": "aggnonce"
},
"comment": "Aggregate nonce is invalid due wrong tag, 0x04, in the first half"
},
{
"id_indices": [0, 1, 2],
"pubshare_indices": [0, 1, 2],
"id_indices": [
0,
1,
2
],
"pubshare_indices": [
0,
1,
2
],
"aggnonce_index": 6,
"msg_index": 0,
"signer_index": 0,
"secnonce_index": 0,
"error": {
"type": "invalid_contribution",
"signer_id": null,
"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, 2],
"pubshare_indices": [0, 1, 2],
"id_indices": [
0,
1,
2
],
"pubshare_indices": [
0,
1,
2
],
"aggnonce_index": 7,
"msg_index": 0,
"signer_index": 0,
"secnonce_index": 0,
"error": {
"type": "invalid_contribution",
"signer_id": null,
"type": "InvalidContributionError",
"id": null,
"contrib": "aggnonce"
},
"comment": "Aggregate nonce is invalid because second half exceeds field size"
},
{
"id_indices": [0, 1, 2],
"pubshare_indices": [0, 1, 2],
"id_indices": [
0,
1,
2
],
"pubshare_indices": [
0,
1,
2
],
"aggnonce_index": 0,
"msg_index": 0,
"signer_index": 0,
"secnonce_index": 1,
"error": {
"type": "value",
"type": "ValueError",
"message": "first secnonce value is out of range."
},
"comment": "Secnonce is invalid which may indicate nonce reuse"
@ -257,83 +432,168 @@
],
"verify_fail_test_cases": [
{
"psig": "21255BB1924800E4BF273455BB20C07239F15FFA4526F218CC8386E10A981655",
"id_indices": [0, 1, 2],
"pubshare_indices": [0, 1, 2],
"pubnonce_indices": [0, 1, 2],
"psig": "4BA473EE6A3403E6FB2F64191A7224643CB29BE60669DC2C2176DE58508F5F4E",
"id_indices": [
0,
1,
2
],
"pubshare_indices": [
0,
1,
2
],
"pubnonce_indices": [
0,
1,
2
],
"msg_index": 0,
"signer_index": 0,
"comment": "Wrong signature (which is equal to the negation of valid signature)"
},
{
"psig": "DEDAA44E6DB7FF1B40D8CBAA44DF3F8C80BD7CEC6A21AE22F34ED7ABC59E2AEC",
"id_indices": [0, 1, 2],
"pubshare_indices": [0, 1, 2],
"pubnonce_indices": [0, 1, 2],
"psig": "B45B8C1195CBFC1904D09BE6E58DDB9A7DFC4100A8DEC40F9E5B80347FA6E1F3",
"id_indices": [
0,
1,
2
],
"pubshare_indices": [
0,
1,
2
],
"pubnonce_indices": [
0,
1,
2
],
"msg_index": 0,
"signer_index": 1,
"comment": "Wrong signer"
"comment": "Wrong signer index"
},
{
"psig": "DEDAA44E6DB7FF1B40D8CBAA44DF3F8C80BD7CEC6A21AE22F34ED7ABC59E2AEC",
"id_indices": [0, 1, 2],
"pubshare_indices": [3, 1, 2],
"pubnonce_indices": [0, 1, 2],
"psig": "B45B8C1195CBFC1904D09BE6E58DDB9A7DFC4100A8DEC40F9E5B80347FA6E1F3",
"id_indices": [
0,
1,
2
],
"pubshare_indices": [
3,
1,
2
],
"pubnonce_indices": [
0,
1,
2
],
"msg_index": 0,
"signer_index": 0,
"comment": "The signer's pubshare is not in the list of pubshares"
},
{
"psig": "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141",
"id_indices": [0, 1, 2],
"pubshare_indices": [0, 1, 2],
"pubnonce_indices": [0, 1, 2],
"id_indices": [
0,
1,
2
],
"pubshare_indices": [
0,
1,
2
],
"pubnonce_indices": [
0,
1,
2
],
"msg_index": 0,
"signer_index": 0,
"comment": "Signature exceeds group size"
"comment": "Signature value is out of range"
}
],
"verify_error_test_cases": [
{
"psig": "DEDAA44E6DB7FF1B40D8CBAA44DF3F8C80BD7CEC6A21AE22F34ED7ABC59E2AEC",
"id_indices": [0, 1, 2],
"pubshare_indices": [0, 1, 2],
"pubnonce_indices": [5, 1, 2],
"psig": "B45B8C1195CBFC1904D09BE6E58DDB9A7DFC4100A8DEC40F9E5B80347FA6E1F3",
"id_indices": [
0,
1,
2
],
"pubshare_indices": [
0,
1,
2
],
"pubnonce_indices": [
5,
1,
2
],
"msg_index": 0,
"signer_index": 0,
"error": {
"type": "invalid_contribution",
"signer_id": 1,
"type": "InvalidContributionError",
"id": 0,
"contrib": "pubnonce"
},
"comment": "Invalid pubnonce"
},
{
"psig": "DEDAA44E6DB7FF1B40D8CBAA44DF3F8C80BD7CEC6A21AE22F34ED7ABC59E2AEC",
"id_indices": [0, 1, 2],
"pubshare_indices": [5, 1, 2],
"pubnonce_indices": [0, 1, 2],
"psig": "B45B8C1195CBFC1904D09BE6E58DDB9A7DFC4100A8DEC40F9E5B80347FA6E1F3",
"id_indices": [
0,
1,
2
],
"pubshare_indices": [
5,
1,
2
],
"pubnonce_indices": [
0,
1,
2
],
"msg_index": 0,
"signer_index": 0,
"error": {
"type": "invalid_contribution",
"signer_id": 1,
"type": "InvalidContributionError",
"id": 0,
"contrib": "pubshare"
},
"comment": "Invalid pubshare"
},
{
"psig": "DEDAA44E6DB7FF1B40D8CBAA44DF3F8C80BD7CEC6A21AE22F34ED7ABC59E2AEC",
"id_indices": [0, 1, 2],
"pubshare_indices": [0, 1, 2],
"pubnonce_indices": [0, 1, 2, 3],
"psig": "B45B8C1195CBFC1904D09BE6E58DDB9A7DFC4100A8DEC40F9E5B80347FA6E1F3",
"id_indices": [
0,
1,
2
],
"pubshare_indices": [
0,
1,
2
],
"pubnonce_indices": [
0,
1,
2,
3
],
"msg_index": 0,
"signer_index": 0,
"error": {
"type": "value",
"type": "ValueError",
"message": "The ids, pubnonces and pubshares arrays must have the same length."
},
"comment": "public nonces count is greater than ids and pubshares"
}
]
}
}

378
test/jmfrost/vectors/tweak_vectors.json

@ -1,28 +1,36 @@
{
"max_participants": 5,
"min_participants": 3,
"group_public_key": "037940B3ED1FDC360252A6F48058C7B94276DFB6AA2B7D51706FB48326B19E7AE1",
"secshare_p1":"81D0D40CDF044588167A987C14552954DB187AC5AD3B1CA40D7B03DCA32AFDFB",
"identifiers": [1, 2, 3, 4, 5],
"group_public_key": "03F9186397E61022663935B3FDFF7880A9F0EC288D8B054DF6AC2BC5777B5FBBB1",
"secshare_p1": "6FF0F78C1F0E76C4AB67C1B32E0B4B1652120B794AC1AE7EC3992DE06092B566",
"identifiers": [
0,
1,
2,
3,
4
],
"pubshares": [
"02BB66437FCAA01292BFB4BB6F19D67818FE693215C36C4663857F1DC8AB8BF4FA",
"02C3250013C86AA9C3011CD40B2658CBC5B950DD21FFAA4EDE1BB66E18A063CED5",
"03259D7068335012C08C5D80E181969ED7FFA08F7973E3ED9C8C0BFF3EC03C223E",
"02A22971750242F6DA35B8DB0DFE74F38A3227118B296ADD2C65E324E2B7EB20AD",
"03541293535BB662F8294C4BEB7EA25F55FEAE86C6BAE0CEBD741EAAA28639A6E6"
"0260C5B10BAF5D471F0D09ED9BDED80B23CEFE0C9DC0F26AD1A0453A6FDFF663E2",
"028B96AE32F17C49C6111D6BF7D17E89428734D6DED0E31C480F2BABD263DDFA28",
"029456C5A981CF9DA72BF7AF0F82C44A343DC08419911286E975D16697D93B9A61",
"030CAA62081616E0B833FBD39A1058C1A11A23FB8307127D486914E8BF2E5935BB",
"03E483B7D41072D6E883447EB85617A086290EB67B40C89F3A787CF1B66005F488",
"020000000000000000000000000000000000000000000000000000000000000007"
],
"secnonce_p1":"96DF27F46CB6E0399C7A02811F6A4D695BBD7174115477679E956658FF2E83D618E4F670DF3DEB215934E4F68D4EEC71055B87288947D75F6E1EA9037FF62173",
"secnonce_p1": "05AB25AF7D0F8EE4231470B5CF9DCBE7069E007FDDBD7053D5436CED37926CCBDE406FFC1E658CB2B870296D5913305AABC90CD56AEC7066051EB3A63C025C17",
"pubnonces": [
"02FCDBEE416E4426FB4004BAB2B416164845DEC27337AD2B96184236D715965AB2039F71F389F6808DC6176F062F80531E13EA5BC2612B690FC284AE66C2CD859CE9",
"02D26EF7E09A4BC0A2CF295720C64BAD56A28EF50B6BECBD59AF6F3ADE6C2480C503D11B9993AE4C2D38EA2591287F7B744976F0F0B79104B96D6399507FC533E893",
"03C7E3D6456228347B658911BF612967F36C7791C24F9607ADB34E09F8CC1126D803D2C9C6E3D1A11463F8C2D57B145A814F5D44FD1A42F7A024140AC30D48EE0BEE",
"036409E6BA4A00E148E9BE2D3B4217A74B3A65F0D75489176EF8A7D2BD699B949002B1E9FA2A8AE80CD7CE1593B51402B980B56896DB5B5C2B07EDA2C0CFEB08AD93",
"02464144C7AFAEF651F63E330B1FFF6EEC43991F9AE75AE6069796C097B04DAE720288B464788E5DFC9C2CCD6A3CCBBED643666749250012DA220D1C9FC559214270"
"036012764FC86D835893E0E802FD74BF01B80F35E10288CAE866B2ADF3E218017502E128AB0D2112DFFC4852EBF5F62F69770EAD9CB777229FA27C4167F589CCCF63",
"03DB57751EA90F2DFCDF684CE3C47F2CD33E54FB821347827844AE04913F36AB8D02B38858FB1D03A92D1D734DF0445FD8603F861984EC61B368AAE29FE862678700",
"028B115FE43676F134793CD556EFFBE6995C53F8CC4EAFA139008CF0337D9F39D6030D4B01F9BBF4B603C08ECB78C3207E4B43A10273FD82267A40407C864B314F13",
"03AF20782F4A855E5671A0ED5E845EC9D4CA6DC662423C3B2AE098FE315086ED1E0387542FE72832136823C2206385C1935355F1CECF7AA92AA23E33E3B9C43B0C55",
"022525FD3CA2F5C36A5298BFC4E4175620E3DF5C6098337CD484A872F335C43B48037C254E70008552F26D59F4914E0612346E7B6879B84BE46ADA9A7F2862EE9159"
],
"aggnonces": [
"02047C99228CEA528AE200A82CBE4CD188BC67D58F537D1904A16B07FCDE07C3A6038708199DFA5BC5C41A0DD0FBD7D0620ADB4AC9991F7DB55A155CE9396AA80D1A",
"03AB37C47419536990037B903428008878E4F395823A135C2B39E67FA850CFF41F028967ECFE399759125F59F7142B6580D91F70DE1C9E9C6B0F56754B64370A4438",
"0353365AF75F7C246089940D57D3265947A1D27576E411AE9C98702516C72DB51B02F5483E63F474BDD8EAC03F99276ED5A2ED31786F5B0F1A8706BE7367BC1D4555"
"038ECCE47C2DEBB46EBD7C04DE2580237AE4C37E565BC10253D925F25257F4F1E903BF7AEBEDAAAEA115592133BE23F991D97DD843F06875CFB678239BFAE407F668",
"0218CA838EE6DE2703318E7399F23A1EE6DE41461DB51D7A8EEB626E66FC5D64270294D9CFC121378285C93B1BE040B24F78A4D33810C1889DB8D4CA19C453F28C73",
"02FEC5F20C003199EE392537E83AEEA5EA8A1C565C71959B844990A3C079FCCD640334C5B98145FDC9444C322220F53D7A314D4962450B3BBA68916C3FFB2824740E",
"0245B149422D60D950DF04C4672A08A35F4FA3EACF476583E20A746DB97EC1E9300379BDC2C694DCE76C86548E48483CF1B88032F6EB92D978E2990A4112E7F23E01"
],
"tweaks": [
"E8F791FF9225A2AF0102AFFF4A9A723D9612A682A25EBE79802B263CDFCD83BB",
@ -34,131 +42,307 @@
"msg": "F95466D086770E689964664219266FE5ED215C92AE20BAB5C9D79ADDDDF3C0CF",
"valid_test_cases": [
{
"id_indices": [1, 2, 0],
"pubshare_indices": [1, 2, 0],
"pubnonce_indices": [1, 2, 0],
"id_indices": [
0,
1,
2
],
"pubshare_indices": [
0,
1,
2
],
"pubnonce_indices": [
0,
1,
2
],
"tweak_indices": [],
"is_xonly": [],
"aggnonce_index": 0,
"signer_index": 2,
"expected": "DEDAA44E6DB7FF1B40D8CBAA44DF3F8C80BD7CEC6A21AE22F34ED7ABC59E2AEC",
"comment": "No tweak. The expected value (partial sig) must match the signing with untweaked group public key."
"is_xonly": [],
"signer_index": 0,
"expected": "B45B8C1195CBFC1904D09BE6E58DDB9A7DFC4100A8DEC40F9E5B80347FA6E1F3",
"comment": "No tweak"
},
{
"id_indices": [1, 2, 0],
"pubshare_indices": [1, 2, 0],
"pubnonce_indices": [1, 2, 0],
"tweak_indices": [0],
"is_xonly": [true],
"id_indices": [
0,
1,
2
],
"pubshare_indices": [
0,
1,
2
],
"pubnonce_indices": [
0,
1,
2
],
"tweak_indices": [
0
],
"aggnonce_index": 0,
"signer_index": 2,
"expected": "00A84851A7D3F53B94FDFDE0BE6C6DCE570B7FF27E8B77FDF75AFF52066F42EE",
"is_xonly": [
true
],
"signer_index": 0,
"expected": "58FB09FCC09794ED1566B0101F169D82CB2E2FF08070F18ECBFC7351276B68E3",
"comment": "A single x-only tweak"
},
{
"id_indices": [1, 2, 0],
"pubshare_indices": [1, 2, 0],
"pubnonce_indices": [1, 2, 0],
"tweak_indices": [0],
"is_xonly": [false],
"id_indices": [
0,
1,
2
],
"pubshare_indices": [
0,
1,
2
],
"pubnonce_indices": [
0,
1,
2
],
"tweak_indices": [
0
],
"aggnonce_index": 0,
"signer_index": 2,
"expected": "FC2D7852AAEF8F3C229FEC7E6B496999C52857387E4274CD2F7625CD4B262D73",
"is_xonly": [
false
],
"signer_index": 0,
"expected": "830863630570E8EA492FE715ABD1C987DDAB7E2D4E6D05EBB902B5CEAF5A61F5",
"comment": "A single plain tweak"
},
{
"id_indices": [1, 2, 0],
"pubshare_indices": [1, 2, 0],
"pubnonce_indices": [1, 2, 0],
"tweak_indices": [0, 1],
"id_indices": [
0,
1,
2
],
"pubshare_indices": [
0,
1,
2
],
"pubnonce_indices": [
0,
1,
2
],
"tweak_indices": [
0,
1
],
"aggnonce_index": 0,
"is_xonly": [false, true],
"signer_index": 2,
"expected": "1634928A5951F23E77DB9D6171E89A04E55B2BC07A492CFE68B611303C96957A",
"is_xonly": [
false,
true
],
"signer_index": 0,
"expected": "BD60D37B029E5F40DF8901E2D82CD555A72F01FC9FADEFDB77CAF260C07E5D03",
"comment": "A plain tweak followed by an x-only tweak"
},
{
"id_indices": [1, 2, 0],
"pubshare_indices": [1, 2, 0],
"pubnonce_indices": [1, 2, 0],
"tweak_indices": [0, 1, 2, 3],
"id_indices": [
0,
1,
2
],
"pubshare_indices": [
0,
1,
2
],
"pubnonce_indices": [
0,
1,
2
],
"tweak_indices": [
0,
1,
2,
3
],
"aggnonce_index": 0,
"is_xonly": [true, false, true, false],
"signer_index": 2,
"expected": "4252C4EA9641F1B8C502F3B63C3D0AFEF3274CFE7C70D94AE2F2DC54FA16D216",
"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": [1, 2, 0],
"pubshare_indices": [1, 2, 0],
"pubnonce_indices": [1, 2, 0],
"tweak_indices": [0, 1, 2, 3],
"aggnonce_index": 0,
"is_xonly": [false, false, true, true],
"signer_index": 2,
"expected": "CF079FD835F00CF6A737FDC19D602AA445C95825B6A5D1C0FFB32A848427F49E",
"comment": "Four tweaks: plain, plain, x-only, x-only."
"is_xonly": [
true,
false,
true,
false
],
"signer_index": 0,
"expected": "0F4BC5D13A3BB11C038388699484B931C0205AFD1A23E8FEEC6D5D972F913F41",
"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, 2],
"pubshare_indices": [0, 1, 2],
"pubnonce_indices": [0, 1, 2],
"tweak_indices": [0, 1, 2, 3],
"id_indices": [
0,
1,
2
],
"pubshare_indices": [
0,
1,
2
],
"pubnonce_indices": [
0,
1,
2
],
"tweak_indices": [
0,
1,
2,
3
],
"aggnonce_index": 0,
"is_xonly": [false, false, true, true],
"is_xonly": [
false,
false,
true,
true
],
"signer_index": 0,
"expected": "CF079FD835F00CF6A737FDC19D602AA445C95825B6A5D1C0FFB32A848427F49E",
"comment": "Order of the signers shouldn't affect tweaking. The expected value (partial sig) must match the previous test vector."
"expected": "7CBAFEF4D47C29C6A4228F23F95A056ADE5BDA7AA1BAE31E81BF63866BDCDBE8",
"comment": "Four tweaks: plain, plain, x-only, x-only"
},
{
"id_indices": [0, 1, 2, 3],
"pubshare_indices": [0, 1, 2, 3],
"pubnonce_indices": [0, 1, 2, 3],
"tweak_indices": [0, 1, 2, 3],
"id_indices": [
0,
1,
2,
3
],
"pubshare_indices": [
0,
1,
2,
3
],
"pubnonce_indices": [
0,
1,
2,
3
],
"tweak_indices": [
0,
1,
2,
3
],
"aggnonce_index": 1,
"is_xonly": [false, false, true, true],
"is_xonly": [
false,
false,
true,
true
],
"signer_index": 0,
"expected": "22B8AE565FB2A52E07F1D6D0B5F85DD16932ADF77C0D61C473554133C22EE617",
"comment": "Number of the signers won't affect tweaking but the expected value (partial sig) will change because of interpolating value."
"expected": "B753D6F8BC25E2D5F3252975CB8FCDE5D02778B31B20EE824CB3924A9C90857B",
"comment": "Tweaking with t < number of signers < n. The expected value (partial sig) must match the previous test vector"
},
{
"id_indices": [0, 1, 2, 3, 4],
"pubshare_indices": [0, 1, 2, 3, 4],
"pubnonce_indices": [0, 1, 2, 3, 4],
"tweak_indices": [0, 1, 2, 3],
"id_indices": [
0,
1,
2,
3,
4
],
"pubshare_indices": [
0,
1,
2,
3,
4
],
"pubnonce_indices": [
0,
1,
2,
3,
4
],
"tweak_indices": [
0,
1,
2,
3
],
"aggnonce_index": 2,
"is_xonly": [false, false, true, true],
"is_xonly": [
false,
false,
true,
true
],
"signer_index": 0,
"expected": "7BCA92625F1C83D1EE6A855A198D25410BBE3867E2B61400A02D12BA2D6E2384",
"expected": "2B029B494C0DBEC46CD0F68D6B0679E87D532AD05488B63267F1DF3A1D117880",
"comment": "Tweaking with maximum possible signers"
}
],
"error_test_cases": [
{
"id_indices": [1, 2, 0],
"pubshare_indices": [1, 2, 0],
"tweak_indices": [4],
"id_indices": [
0,
1,
2
],
"pubshare_indices": [
0,
1,
2
],
"tweak_indices": [
4
],
"aggnonce_index": 0,
"is_xonly": [false],
"signer_index": 2,
"is_xonly": [
false
],
"signer_index": 0,
"error": {
"type": "value",
"type": "ValueError",
"message": "The tweak must be less than n."
},
"comment": "Tweak is invalid because it exceeds group size"
},
{
"id_indices": [1, 2, 0],
"pubshare_indices": [1, 2, 0],
"tweak_indices": [0, 1, 2, 3],
"id_indices": [
0,
1,
2
],
"pubshare_indices": [
0,
1,
2
],
"tweak_indices": [
0,
1,
2,
3
],
"aggnonce_index": 0,
"is_xonly": [false, false],
"signer_index": 2,
"is_xonly": [
true,
false
],
"signer_index": 0,
"error": {
"type": "value",
"type": "ValueError",
"message": "The tweaks and is_xonly arrays must have the same length."
},
"comment": "Tweaks count doesn't match the tweak modes count"
}
]
}
}
Loading…
Cancel
Save