Merge pull request #2210 from fjahr/fullagg

BIP459: DahLIAS fully aggregated signatures for secp256k1
This commit is contained in:
Jon Atack authored and GitHub committed 2026-11-03 12:18:06 -06:00
commit 21bfd24d6e
35 files changed
+2664

No files matched your search

+7
View File
@@ -1542,6 +1542,13 @@ users (see also: [https://en.bitcoin.it/wiki/Economic_majority economic majority
| Specification
| Draft
|-
| [[bip-0459.mediawiki|459]]
| Applications
| DahLIAS fully aggregated signatures for secp256k1
| Fabian Jahr
| Specification
| Draft
|-
| [[bip-0461.md|461]]
| Applications
| Deterministic ECDSA Signatures
+435
View File
@@ -0,0 +1,435 @@
<pre>
BIP: 459
Layer: Applications
Title: DahLIAS fully aggregated signatures for secp256k1
Authors: Fabian Jahr <fjahr@protonmail.com>
Status: Draft
Type: Specification
Assigned: 2026-07-10
License: CC0-1.0 OR MIT
Discussion: 2025-04-17: https://groups.google.com/g/bitcoindev/c/eothFkxAvK0/m/IZVGezcdDAAJ
2026-07-06: https://groups.google.com/g/bitcoindev/c/TF5mPfy58RQ/m/lvSC2FdYAAAJ
Requires: 340
</pre>
== Introduction ==
=== Abstract ===
This document describes ''full-aggregation'', a standard for Schnorr-style aggregate signatures based on the DahLIAS<ref>Jonas Nick, Tim Ruffing, Yannick Seurin: ''DahLIAS: Discrete Logarithm-Based Interactive Aggregate Signatures''. Cryptology ePrint Archive, Report 2025/692. https://eprint.iacr.org/2025/692</ref> interactive aggregate signature scheme.
Full-aggregation is an interactive process in which a group of signers produces a single aggregate signature for a collection of public key and message pairs.
The resulting aggregate signature has the same size as a single BIP 340 signature (64 bytes), regardless of the number of signers.
=== Copyright ===
This document, along with the [[bip-0459/reference.py|reference implementation]] and [[bip-0459/vectors/|test vectors]], is licensed under CC0-1.0 or the MIT License, at your option. The vendored copy of the secp256k1lab library in [[bip-0459/secp256k1lab/|bip-0459/secp256k1lab]] is licensed under the MIT License only, as stated in its [[bip-0459/secp256k1lab/COPYING|COPYING]] file.
=== Motivation ===
Full-aggregation applies whenever a verifier must check multiple signatures.
The signers jointly produce a single constant-size aggregate signature, and verifying the aggregate gives the same assurance as verifying an individual signature for each public key and message pair.
The primary purpose is to reduce the amount of data sent to the verifier.
While ''n'' BIP 340 signatures are ''64*n'' bytes, a fully aggregated signature is always 64 bytes.
Verifying the aggregate is also faster than verifying each signature separately.
One application is cross-input signature aggregation (CISA) within the Bitcoin consensus protocol, which reduces the size and verification cost of multi-input transactions.
Deploying CISA requires a consensus change that this document does not specify.
This document only specifies the signature scheme and consensus integration is left to a future proposal.
Full-aggregation is interactive.
It requires cooperation among all signers, following a two-round protocol whose first round can be preprocessed before the messages to be signed are known.
This makes full-aggregation unsuitable for scenarios where signers cannot coordinate.
However, when all input owners already cooperate to construct and sign a transaction, the interaction is not a significant burden.
Historical average transactions would see an [https://hrf.org/latest/cisa-research-paper/ average size reduction of 20.5% and a weight reduction of 7.3%].
Full-aggregation is particularly beneficial for large transactions such as CoinJoins, PayJoins and consolidations, because the savings grow with the number of inputs.
For example, a hypothetical CoinJoin of 300 inputs and 300 outputs would see a size reduction of 42.7% and a weight reduction of 15.9%.
A typical consolidation transaction of 150 inputs and one output would see a weight reduction of 27.4%.
CISA with full-aggregation can even reduce the cost of privacy since participating in CoinJoin and PayJoin transactions can become cheaper than sending individual transactions.
Unlike interactive multisignature schemes such as MuSig2 ([[bip-0327.mediawiki|BIP 327]]), in which signers cooperate to produce a single signature under a single aggregate public key for a single message, full-aggregation does not combine the signers' keys.
Each signer signs their own message under their own public key, and the aggregate signature is verified against the list of individual public key and message pairs.
== Overview ==
Implementers must make sure to understand this section thoroughly to avoid subtle mistakes that may lead to catastrophic failure.
=== General Signing Flow ===
The signing protocol involves ''u'' signers, each with their own secret key, public key, and message, and a ''coordinator'' who aggregates their contributions.
The basic order of operations to create a fully aggregated signature is as follows:
'''First broadcast round:'''
The signers start the signing session by running ''NonceGen'' to compute ''secnonce'' and ''pubnonce''.<ref>We treat the ''secnonce'' and ''pubnonce'' as grammatically singular even though they include serializations of two scalars and two elliptic curve points, respectively.</ref>
Then, the signers send their ''pubnonce'' to the coordinator.
The coordinator runs ''NonceAgg'' to compute an aggregate nonce ''aggnonce'' and sends it back to the signers, along with the ordered list of all ''(pk, m, pubnonce)'' triples.
'''Second broadcast round:'''
At this point, every signer has the required data to sign.
Every signer computes a partial signature by running ''Sign'' with their secret key, their ''secnonce'', the message they intend to sign, and the session parameters.
Then, the signers send their partial signatures to the coordinator, who runs ''SigAgg'' to obtain the final 64-byte aggregate signature.
If all signers behaved honestly, the result passes ''Verify''.
If the aggregate signature is invalid, the coordinator can run ''PartialSigVerify'' on each partial signature to identify the disruptive signer.
'''IMPORTANT''': The ''Sign'' algorithm must '''not''' be executed twice with the same ''secnonce''.
Otherwise, it is possible to extract the signer's secret key from the partial signatures output by three executions of ''Sign'' in different sessions (see [[#nonce-generation|Nonce Generation]]).
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 parameters may look different in implementations or may not exist as an explicit data structure.
However, computation of ''GetSessionValues'' and storage of the result must be protected against modification from an untrusted third party.
=== Nonce Generation ===
'''IMPORTANT''': ''NonceGen'' must have access to a high-quality random generator to draw an unbiased, uniformly random value ''rand' ''.
In contrast to BIP 340 signing, the nonce values '''must not be derived deterministically''' from the signing key and message because otherwise active adversaries can trick the victim into reusing a nonce.<ref>An attacker who participates as a co-signer can run multiple signing sessions with the victim for the same message while varying its own nonce contributions. With deterministic derivation the victim reuses its secret nonces across these sessions while the aggregate nonce and therefore the nonce coefficient ''b'' and the challenge change. Three such sessions yield a system of linear equations that reveals the victim's secret key. Deriving the nonces from additional session data does not prevent this since the other signers' nonces are unknown to the victim when its nonces are generated. This threat is not limited to sessions with malicious co-signers. With deterministic derivation, the nonce is reused whenever the same key and message are signed again in a session whose other public keys, messages, or nonces have changed. Any party who observes the resulting partial signatures, such as a coordinator, can then extract the secret key after three such sessions. Re-signing the same message in a changed session can arise naturally, for example when a transaction signed with ''SIGHASH_ANYONECANPAY'' is modified for a fee bump and signed again.</ref>
The optional arguments to ''NonceGen'' enable a defense-in-depth mechanism that may prevent secret key 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 different, avoiding accidental nonce reuse.
Therefore, it is recommended to provide the optional argument ''sk'' if available 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 session identifier or a counter.
However, the protection provided by the optional arguments should only be viewed as a last resort.
In some applications, it is beneficial to generate and send a ''pubnonce'' before the other signers, their public keys, or the messages to sign are known.
In this case, the ''NonceGen'' algorithm is run during a preprocessing phase, and the ''Sign'' algorithm can be run immediately when the messages and set of signers are 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.
In general, signers are stateful in the sense that they first generate ''secnonce'' and then need to store it until they receive the coordinator's response containing the aggregate nonce and session parameters.
However, it is possible for one of the signers to be stateless: this signer waits until it receives the ''pubnonce'' of all other signers and until session parameters are determined.
Then, the signer can run ''NonceGen'', ''NonceAgg'', and ''Sign'' in sequence and send out its ''pubnonce'' along with its partial signature.
=== Role of the Coordinator ===
Instead of every signer sending their nonce and partial signature to every other signer, it is possible to use an untrusted ''coordinator'' to reduce the communication complexity from quadratic to linear in the number of signers.
In each of the two rounds, the coordinator collects all signers' contributions (nonces or partial signatures), processes them, and sends the results back to the signers.
A malicious coordinator can force the signing session to fail to produce a valid aggregate signature but cannot negatively affect the unforgeability of the scheme, i.e., even a malicious coordinator colluding with all but one signer cannot forge a signature.
If there is no dedicated coordinator, one of the signers can act as the coordinator.
Unlike BIP 327 where the coordinator only needs to send the aggregate nonce back to signers, the coordinator in this scheme must also send each signer the individual public nonces (specifically, the ''R<sub>2</sub>'' components) of all signers.
This is because each signer must verify that their own ''R<sub>2</sub>'' value appears exactly once in the session context (see [[#sign|Sign]]).
In many applications, the signers already know the list of public keys and messages before the signing session starts and only the ''aggnonce'' and individual public nonces need to be communicated.
=== List Ordering ===
The aggregate signature depends on the order of the list of public key and message pairs.
A signature valid for a list ''L'' will not be valid for any non-trivial permutation of ''L'' (except with negligible probability).
If an application does not have a natural ordering of signers, the list should be sorted using a predetermined order before being provided to the coordinator, and the verifier must use the same order.
== Specification ==
=== Notation ===
The following conventions are used, with constants as defined for [https://www.secg.org/sec2-v2.pdf secp256k1]. We note that adapting this specification to other elliptic curves is not straightforward and can result in an insecure scheme<ref>Among other pitfalls, using the specification with a curve whose order is not close to the size of the range of the nonce derivation function is insecure.</ref>.
* 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''.
* 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 or not ''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 [https://en.wikipedia.org/wiki/Elliptic_curve#The_group_law elliptic curve group operation].
** [https://en.wikipedia.org/wiki/Elliptic_curve_point_multiplication Multiplication (⋅) of an integer and a point] 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(x)'', where ''x'' is an integer, returns the 32-byte encoding of ''x'', most significant byte first.
** The function ''xbytes(P)'', where ''P'' is a point for which ''not is_infinite(P)'', returns ''bytes(x(P))''.
** The function ''cbytes(P)'', where ''P'' is a point for which ''not is_infinite(P)'', returns the 33-byte encoding consisting of the single byte ''0x02'' if ''has_even_y(P)'', or ''0x03'' otherwise, prepended to ''bytes(x(P))''.
** The function ''len(x)'' where ''x'' is a byte array returns the length of the array.
** 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 ''has_even_y(P)'', where ''P'' is a point for which ''not is_infinite(P)'', returns ''y(P) mod 2 = 0''.
** The function ''lift_x(x)'', where ''x'' is a 256-bit unsigned integer, returns the point ''P'' for which ''x(P) = x'' and ''has_even_y(P)'', or fails if ''x ≥ p'' or no such point exists. The function ''lift_x(x)'' is equivalent to the following pseudocode:
*** Fail if ''x ≥ p''.
*** 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''.
*** Return the unique point ''P'' such that ''x(P) = x'' and ''y(P) = y'' if ''y mod 2 = 0'' or ''y(P) = p - y'' otherwise.
** The function ''cpoint(x)'', where ''x'' is a 33-byte array, sets ''P'' to the point on the curve whose X coordinate is ''int(x[1:33])''. Fail if ''x[0]'' is not ''0x02'' or ''0x03'', if ''int(x[1:33]) ≥ p'', or if no point with that X coordinate exists on the curve. If ''x[0] = 0x02'', return the point with even y; if ''x[0] = 0x03'', return the point with odd y.
** 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)''.
* Other:
** Tuples are written by listing the elements within parentheses and separated by commas. For example, ''(2, 3, 1)'' is a tuple.
In the [[bip-0459/reference.py|reference implementation]], these operations map to the following [https://github.com/secp256k1lab/secp256k1lab secp256k1lab] library methods, where ''GE'' and ''Scalar'' represent points and scalars:
{| class="wikitable"
! Notation !! secp256k1lab
|-
| ''lift_x(int(x))'' || ''GE.from_bytes_xonly(x)''
|-
| ''cpoint(x)'' || ''GE.from_bytes_compressed(x)''
|-
| ''int(x)'', failing if ''&ge; n'' || ''Scalar.from_bytes_checked(x)''
|-
| ''int(x) mod n'' || ''Scalar.from_bytes_wrapping(x)''
|-
| ''xbytes(P)'' || ''P.to_bytes_xonly()''
|-
| ''cbytes(P)'' || ''P.to_bytes_compressed()''
|-
| ''bytes(s)'' || ''s.to_bytes()''
|-
| ''has_even_y(P)'' || ''P.has_even_y()''
|-
| ''hash<sub>tag</sub>(x)'' || ''tagged_hash(tag, x)''
|}
=== Algorithms ===
The following specification of the algorithms has been written with a focus on clarity.
As a result, the specified algorithms are not always optimal in terms of computation and space.
In particular, some values are recomputed but can be cached in actual implementations (see [[#general-signing-flow|General Signing Flow]]).
Unless noted otherwise, any of the ''pk<sub>0..u-1</sub>'', ''m<sub>0..u-1</sub>'', ''pubnonce<sub>0..u-1</sub>'', and ''psig<sub>0..u-1</sub>'' arrays that an algorithm takes have the same length ''u'', with ''u ≥ 1'', and the algorithm fails otherwise.
Each message is a 32-byte array, and an algorithm fails on any message of a different length.
==== NonceGen ====
''NonceGen'' generates a secret nonce and a corresponding public nonce for a single signer.
Input:
* ''sk'': a 32-byte secret key (optional argument)
* ''extra_in'': a byte array (optional argument)
'''''NonceGen(sk, extra_in)'''''<ref>See [[#nonce-generation|Nonce Generation]] in the Overview for important security requirements regarding randomness and nonce reuse.</ref>:
* Let ''rand' '' be a 32-byte array freshly drawn uniformly at random.
* If the optional argument ''sk'' is present:
** Let ''rand'' be the byte-wise xor of ''sk'' and ''hash<sub>BIP0459/aux</sub>(rand')''.
* Else:
** Let ''rand = rand' ''.
* If the optional argument ''extra_in'' is not present:
** Let ''extra_in'' be an empty byte array.
* Let ''r<sub>1</sub> = int(hash<sub>BIP0459/nonce</sub>(rand || extra_in || 0x00)) mod n''; fail if ''r<sub>1</sub> = 0''.
* Let ''r<sub>2</sub> = int(hash<sub>BIP0459/nonce</sub>(rand || extra_in || 0x01)) mod n''; fail if ''r<sub>2</sub> = 0''.
* Let ''R<sub>1</sub> = r<sub>1</sub>⋅G''.
* Let ''R<sub>2</sub> = r<sub>2</sub>⋅G''.
* Let ''secnonce = bytes(r<sub>1</sub>) || bytes(r<sub>2</sub>)''.
* Let ''pubnonce = cbytes(R<sub>1</sub>) || cbytes(R<sub>2</sub>)''.
* Return ''(secnonce, pubnonce)''.
==== NonceAgg ====
''NonceAgg'' aggregates all signers' public nonces into a single aggregate nonce.
Input:
* ''pubnonce<sub>0..u-1</sub>'': an array of ''u'' 66-byte public nonces
'''''NonceAgg(pubnonce<sub>0..u-1</sub>)''''':
* For ''i = 0 .. u-1'':
** Let ''R<sub>1,i</sub> = cpoint(pubnonce<sub>i</sub>[0:33])''; fail if that fails.
** Let ''R<sub>2,i</sub> = cpoint(pubnonce<sub>i</sub>[33:66])''; fail if that fails.
* Let ''R<sub>1</sub> = R<sub>1,0</sub> + R<sub>1,1</sub> + ... + R<sub>1,u-1</sub>''.
* Let ''R<sub>2</sub> = R<sub>2,0</sub> + R<sub>2,1</sub> + ... + R<sub>2,u-1</sub>''.
* Fail if ''is_infinite(R<sub>1</sub>)'' or ''is_infinite(R<sub>2</sub>)''.
* Let ''aggnonce = cbytes(R<sub>1</sub>) || cbytes(R<sub>2</sub>)''.
* Return ''aggnonce''.
==== GetSessionValues ====
''GetSessionValues'' is a helper function that computes the derived values shared by ''Sign'', ''SigAgg'', and ''PartialSigVerify''.
Input:
* ''aggnonce'': a 66-byte aggregate nonce
* ''pk<sub>0..u-1</sub>'': an array of ''u'' 32-byte public keys
* ''m<sub>0..u-1</sub>'': an array of ''u'' 32-byte messages
* ''pubnonce<sub>0..u-1</sub>'': an array of ''u'' 66-byte public nonces
'''''GetSessionValues(aggnonce, pk<sub>0..u-1</sub>, m<sub>0..u-1</sub>, pubnonce<sub>0..u-1</sub>)''''':
* Let ''R<sub>1</sub> = cpoint(aggnonce[0:33])''; fail if that fails.
* Let ''R<sub>2</sub> = cpoint(aggnonce[33:66])''; fail if that fails.
* For ''i = 0 .. u-1'':
** Let ''R<sub>2,i</sub> = cpoint(pubnonce<sub>i</sub>[33:66])''; fail if that fails.
* Let ''nonce_data = cbytes(R<sub>1</sub>) || cbytes(R<sub>2</sub>)''
* For ''i = 0 .. u-1'':
** Let ''nonce_data = nonce_data || pk<sub>i</sub> || m<sub>i</sub> || cbytes(R<sub>2,i</sub>)''
* Let ''b = int(hash<sub>BIP0459/noncecoef</sub>(nonce_data)) mod n''
* Let ''R = R<sub>1</sub> + b⋅R<sub>2</sub>''
* Fail if ''is_infinite(R)''.
* Return ''(R, b)''
==== Sign ====
''Sign'' produces a partial signature for a signer.
Only the signer's own entry in the list of public key and message pairs is checked, but the resulting aggregate signature is valid for exactly this list.
Any condition the caller requires of the other entries, such as the list matching one agreed upon before the session, must therefore be checked by the caller before invoking ''Sign''.
Input:
* ''secnonce'': a 64-byte secret nonce
* ''sk'': a 32-byte secret key
* ''m'': the 32-byte message this signer intends to sign
* ''aggnonce'': a 66-byte aggregate nonce
* ''pk<sub>0..u-1</sub>'': an array of ''u'' 32-byte public keys
* ''m<sub>0..u-1</sub>'': an array of ''u'' 32-byte messages
* ''pubnonce<sub>0..u-1</sub>'': an array of ''u'' 66-byte public nonces
'''''Sign(secnonce, sk, m, aggnonce, pk<sub>0..u-1</sub>, m<sub>0..u-1</sub>, pubnonce<sub>0..u-1</sub>)''''':
* Let ''r<sub>1</sub> = int(secnonce[0:32])''; fail if ''r<sub>1</sub> = 0'' or ''r<sub>1</sub> ≥ n''.
* Let ''r<sub>2</sub> = int(secnonce[32:64])''; fail if ''r<sub>2</sub> = 0'' or ''r<sub>2</sub> ≥ n''.
* Let ''d = int(sk)''; fail if ''d = 0'' or ''d ≥ n''.
* Let ''P = d⋅G''.
* Let ''R<sub>2</sub> = r<sub>2</sub>⋅G''.
* '''Index lookup and uniqueness check''': Find the unique index ''j'' in ''0..u-1'' such that ''pubnonce<sub>j</sub>[33:66] = cbytes(R<sub>2</sub>)''. Fail if no such index exists or if more than one such index exists.<ref>The uniqueness check applies to the ''R<sub>2</sub>'' value alone. Checking instead that the combination of public key, message, and ''R<sub>2</sub>'' occurs exactly once is not sufficient. If the signer's ''R<sub>2</sub>'' appears at a second index with a different public key or message, ''Sign'' must fail, because signing such a session enables a key extraction attack (see Section 4.2 of the [https://eprint.iacr.org/2025/692 DahLIAS paper]).</ref>
* Fail if ''pk<sub>j</sub> ≠ xbytes(P)'' or ''m<sub>j</sub> ≠ m'', i.e., the public key or message at the signer's position does not match the signer's secret key or intended message.
* Let ''(R, b) = GetSessionValues(aggnonce, pk<sub>0..u-1</sub>, m<sub>0..u-1</sub>, pubnonce<sub>0..u-1</sub>)''; fail if that fails.
* Let ''e = 1'' if ''has_even_y(R)'', otherwise let ''e = n - 1''.
* Let ''d' = d'' if ''has_even_y(P)'', otherwise let ''d' = n - d''.
* Let ''L = pk<sub>0</sub> || m<sub>0</sub> || ... || pk<sub>u-1</sub> || m<sub>u-1</sub>''.
* Let ''c<sub>j</sub> = int(hash<sub>BIP0459/sig</sub>(L || xbytes(R) || pk<sub>j</sub> || m<sub>j</sub>)) mod n''.
* Let ''s<sub>j</sub> = (e⋅(r<sub>1</sub> + b⋅r<sub>2</sub>) + c<sub>j</sub>⋅d') mod n''.
* Let ''psig = bytes(s<sub>j</sub>)''.
* Return ''psig''.
==== SigAgg ====
''SigAgg'' aggregates all partial signatures into a final 64-byte aggregate signature.
Input:
* ''aggnonce'': a 66-byte aggregate nonce
* ''pk<sub>0..u-1</sub>'': an array of ''u'' 32-byte public keys
* ''m<sub>0..u-1</sub>'': an array of ''u'' 32-byte messages
* ''pubnonce<sub>0..u-1</sub>'': an array of ''u'' 66-byte public nonces
* ''psig<sub>0..u-1</sub>'': an array of ''u'' 32-byte partial signatures
'''''SigAgg(aggnonce, pk<sub>0..u-1</sub>, m<sub>0..u-1</sub>, pubnonce<sub>0..u-1</sub>, psig<sub>0..u-1</sub>)''''':
* Let ''(R, b) = GetSessionValues(aggnonce, pk<sub>0..u-1</sub>, m<sub>0..u-1</sub>, pubnonce<sub>0..u-1</sub>)''; fail if that fails.
* For ''i = 0 .. u-1'':
** Let ''s<sub>i</sub> = int(psig<sub>i</sub>)''; fail if ''s<sub>i</sub> ≥ n''.
* Let ''s = (s<sub>0</sub> + s<sub>1</sub> + ... + s<sub>u-1</sub>) mod n''.
* Let ''sig = xbytes(R) || bytes(s)''.
* Return ''sig''.
==== PartialSigVerify ====
''PartialSigVerify'' verifies a single signer's partial signature and is used to identify disruptive signers.
It recomputes the aggregate nonce from the public nonces rather than taking it as an argument, so its result cannot be influenced by the coordinator.
If the public nonces are received over authenticated channels and ''PartialSigVerify'' succeeds for the partial signatures of all ''u'' signers, then ''SigAgg'' called with ''aggnonce = NonceAgg(pubnonce<sub>0..u-1</sub>)'' produces a valid aggregate signature.
Input:
* ''psig'': a 32-byte partial signature
* ''pk<sub>0..u-1</sub>'': an array of ''u'' 32-byte public keys
* ''m<sub>0..u-1</sub>'': an array of ''u'' 32-byte messages
* ''pubnonce<sub>0..u-1</sub>'': an array of ''u'' 66-byte public nonces
* ''signer_index'': the index of the signer in the list (integer in ''0..u-1'')
'''''PartialSigVerify(psig, pk<sub>0..u-1</sub>, m<sub>0..u-1</sub>, pubnonce<sub>0..u-1</sub>, signer_index)''''':
* Let ''i = signer_index''.
* Fail if ''i < 0'' or ''i ≥ u''.
* Let ''s = int(psig)''; fail if ''s ≥ n''.
* Let ''R<sub>1,i</sub> = cpoint(pubnonce<sub>i</sub>[0:33])''; fail if that fails.
* Let ''R<sub>2,i</sub> = cpoint(pubnonce<sub>i</sub>[33:66])''; fail if that fails.
* Let ''P<sub>i</sub> = lift_x(int(pk<sub>i</sub>))''; fail if that fails.
* Let ''aggnonce = NonceAgg(pubnonce<sub>0..u-1</sub>)''; fail if that fails.
* Let ''(R, b) = GetSessionValues(aggnonce, pk<sub>0..u-1</sub>, m<sub>0..u-1</sub>, pubnonce<sub>0..u-1</sub>)''; fail if that fails.
* Let ''e = 1'' if ''has_even_y(R)'', otherwise let ''e = n - 1''.
* Let ''L = pk<sub>0</sub> || m<sub>0</sub> || ... || pk<sub>u-1</sub> || m<sub>u-1</sub>''.
* Let ''c<sub>i</sub> = int(hash<sub>BIP0459/sig</sub>(L || xbytes(R) || pk<sub>i</sub> || m<sub>i</sub>)) mod n''.
* Let ''R'<sub>i</sub> = R<sub>1,i</sub> + b⋅R<sub>2,i</sub>''.
* Fail if ''s⋅G ≠ e⋅R'<sub>i</sub> + c<sub>i</sub>⋅P<sub>i</sub>''.
* Return success iff no failure occurred before reaching this point.
==== Verify ====
''Verify'' verifies a given 64-byte aggregate signature against an ordered list of public key and message pairs.
The inputs to ''Verify'' are the only data required by a verifier.
No nonces or other session data are needed.
Input:
* ''pk<sub>0..u-1</sub>'': an array of ''u'' 32-byte public keys
* ''m<sub>0..u-1</sub>'': an array of ''u'' 32-byte messages
* ''sig'': a 64-byte aggregate signature
'''''Verify(pk<sub>0..u-1</sub>, m<sub>0..u-1</sub>, sig)'''''<ref>An [https://bitcoin.stackexchange.com/a/80702/109853 efficient algorithm for computing a sum of multiple EC multiplications] can significantly speed up verification.</ref>:
* Let ''r = sig[0:32]''.
* Let ''s = int(sig[32:64])''; fail if ''s ≥ n''.
* Let ''R = lift_x(int(r))''; fail if that fails.
* For ''i = 0 .. u-1'':
** Let ''P<sub>i</sub> = lift_x(int(pk<sub>i</sub>))''; fail if that fails.
* Let ''L = pk<sub>0</sub> || m<sub>0</sub> || ... || pk<sub>u-1</sub> || m<sub>u-1</sub>''.
* For ''i = 0 .. u-1'':
** Let ''c<sub>i</sub> = int(hash<sub>BIP0459/sig</sub>(L || r || pk<sub>i</sub> || m<sub>i</sub>)) mod n''.
* Fail if ''s⋅G ≠ R + c<sub>0</sub>⋅P<sub>0</sub> + c<sub>1</sub>⋅P<sub>1</sub> + ... + c<sub>u-1</sub>⋅P<sub>u-1</sub>''.
* Return success iff no failure occurred before reaching this point.
=== Key Tweaking ===
The scheme supports additive key tweaking, allowing signers to use tweaked key pairs without changes to the verification algorithm.
A tweak ''t'' is a 256-bit integer with ''0 ≤ t < 2<sup>256</sup>''.
In both variants below, tweaking fails if ''t ≥ n'' or if ''is_infinite(P')''.
The tweaked public key ''xbytes(P')'' appears in the public key list provided to ''Verify'', and the signer uses ''sk' '' as its secret key in ''Sign''.
'''Plain tweaking''' modifies a key pair ''(sk, P)'' with ''P = sk⋅G'' and is used for BIP 32 unhardened derivation, where ''t'' is the child offset:
* ''sk' = (sk + t) mod n''
* ''P' = P + t⋅G''
'''X-only tweaking''' is used for BIP 341 Taproot key path spending.
Because an X-only public key denotes the point with even Y, the input key is first normalized to even Y.
Let ''d = sk'' if ''has_even_y(P)'', otherwise ''d = n - sk'', so that ''d⋅G = lift_x(x(P))''.
Then:
* ''sk' = (d + t) mod n''
* ''P' = lift_x(x(P)) + t⋅G''
For BIP 341, ''t = int(hash<sub>TapTweak</sub>(xbytes(P) || merkle_root))'' where ''xbytes(P)'' is the X-only internal public key.
The resulting ''P' '' may have odd Y.
== Rationale ==
DahLIAS is the first aggregate signature scheme with constant-size signatures based directly on discrete logarithms in pairing-free groups.
* A fully aggregated signature has the same shape as a standard Schnorr signature: a pair ''(R, s)'' serialized as 64 bytes. The verification algorithm is different from BIP 340 verification and takes a ''list'' of public key and message pairs as input rather than a single pair.
* Every message is exactly 32 bytes. This is the size of a transaction sighash, the intended input for cross-input signature aggregation. Fixing the length also keeps the encoding of the list ''L'' unambiguous, since the challenge binds to the entire list rather than to a single message as in BIP 340 and BIP 327. An application that needs to sign data of a different size should pre-hash it to a 32-byte digest, for example with a tagged hash.
* Since the challenge binds to the entire list, ''L || xbytes(R)'' is a common prefix of every challenge hash input. Verifiers should hash this prefix into a single hash state once and finalize a copy of that state with ''pk<sub>i</sub> || m<sub>i</sub>'' for each ''i'', which keeps the hashing linear in the number of signers instead of quadratic.
* Each signer contributes two nonces rather than one. A single nonce per signer would leave the scheme open to the [https://eprint.iacr.org/2020/945 ROS attack] mounted across many concurrent signing sessions. Deriving the common nonce as ''R = R<sub>1</sub> + b⋅R<sub>2</sub>'', with ''b'' bound to the full session context, prevents this, following MuSig2 ([[bip-0327.mediawiki|BIP 327]]). A variant of the attack that exploits a signer signing multiple messages is in turn prevented by the ''R<sub>2</sub>'' uniqueness and message checks in ''Sign'' (see [[#security|Security]]).
* A single nonce coefficient ''b'' is applied uniformly to all signers' second nonce contributions. This keeps signing cost constant per signer (three group exponentiations) rather than requiring a multi-exponentiation of size ''u''. The trade-off is the need for a uniqueness check on the second nonce component ''R<sub>2</sub>'' during signing.
* The scheme supports additive key tweaking, enabling compatibility with BIP 32 key derivation and BIP 341 Taproot outputs.
* This document does not specify an optimized signing algorithm for a single party that knows all secret keys, e.g., a wallet spending several of its own inputs. Such a party can act as all signers and the coordinator and run the protocol locally. A more efficient method [https://groups.google.com/g/bitcoindev/c/eothFkxAvK0/m/PgRaFPovEQAJ has been outlined] in the mailing list discussion but requires additional security analysis before it can be specified.
A non-interactive alternative is half-aggregation ([[bip-0458.mediawiki|BIP 458]]), which requires no cooperation between the signers but compresses ''n'' signatures to ''32*n + 32'' bytes rather than to a constant 64 bytes.
== Security ==
'''Nonce generation and reuse.''' As detailed in [[#nonce-generation|Nonce Generation]] and [[#general-signing-flow|General Signing Flow]], ''NonceGen'' must draw ''rand' '' from a high-quality random number generator, nonces must not be derived deterministically, and a ''secnonce'' must never be used in more than one ''Sign'' invocation.
Violating any of these can allow an adversary to extract the signer's secret key.
'''Signer verification checks.''' The checks performed by ''Sign'' after recomputing ''R<sub>2</sub>'' are security-critical.
The signer locates its position by finding the unique index ''j'' whose ''R<sub>2</sub>'' matches its own.
These ''R<sub>2</sub>'' values act as ephemeral identifiers that distinguish signers even when they share a public key, and omitting the uniqueness check enables key extraction.
The signer must also confirm that ''both'' the public key and the message at index ''j'' match its own.
Verifying only the public key would let a malicious coordinator substitute a different message at the signer's position.
These checks cover only the signer's own entry, so any requirement on the other entries of the list must be verified by the caller before invoking ''Sign'' (see [[#sign|Sign]]).
'''Partial signatures.''' A partial signature is ''not'' a signature.
By choosing its nonces accordingly, an adversary can produce a partial signature that verifies for any public key.
''PartialSigVerify'' therefore serves only to identify disruptive signers and does not prove that a signer knows the corresponding secret key.
'''Untrusted coordinator.''' The coordinator can cause a signing session to abort but cannot forge a signature, even when colluding with all but one signer (see [[#role-of-the-coordinator|Role of the Coordinator]]).
'''Security model.''' The scheme is proven secure in the concurrent setting, including when signers use key tweaking, under the algebraic one-more discrete logarithm (AOMDL) assumption, the same assumption used for [[bip-0327.mediawiki|BIP 327]].<ref>The precise notion is cosigners-aware existential unforgeability (co-EUF-CMA) as defined in the DahLIAS paper. Each signer sees the entire list of public key and message pairs before finalizing its contribution, and unforgeability guarantees that no valid aggregate signature can attribute to an honest signer a message and cosigner list it never agreed to sign. This all-or-nothing guarantee over the whole list distinguishes interactive aggregate signatures from multisignatures like MuSig2, which produce a signature for a single message under a single aggregate key.</ref>
AOMDL is a falsifiable and weaker variant of the well-studied OMDL problem.
The proof models ''hash<sub>BIP0459/sig</sub>'' as a random oracle and requires only that ''hash<sub>BIP0459/noncecoef</sub>'' be collision-resistant.
The scheme is also strongly binding, meaning it is infeasible to produce a single signature valid for two distinct lists of public key and message pairs.<ref>DahLIAS strong binding additionally requires the verifier to reject any public key equal to the point at infinity. This condition is satisfied implicitly in this specification: public keys are 32-byte X-only values that are decoded with ''lift_x'', which never yields the point at infinity, so no such public key can appear in the list.</ref>
== Backward Compatibility ==
Full-aggregation specifies a new, optional interactive signing protocol for producing an aggregate signature for a list of BIP 340 public key and message pairs.
It does not change the validation of individual BIP 340 signatures and does not affect any existing Bitcoin consensus rules or protocol behavior.
Therefore, this BIP introduces no backward compatibility concerns.
== Test Vectors and Reference Code ==
A reference implementation in Python is provided in [[bip-0459/reference.py|reference.py]].
Note that the reference implementation depends on a vendored copy of the [https://github.com/secp256k1lab/secp256k1lab secp256k1lab library].
Test vectors are provided in [[bip-0459/vectors/|vectors]] and are generated with a [[bip-0459/gen_test_vectors.py|generator script]].
The reference implementation can be executed with the test vectors by running [[bip-0459/run_test_vectors.py|the run_test_vectors script]].
== Acknowledgements ==
The scheme specified in this document is DahLIAS (Discrete Logarithm-Based Interactive Aggregate Signatures) by Jonas Nick, Tim Ruffing, and Yannick Seurin.
Thanks to waxwing for his feedback on the mailing list, which, among other things, led to clarifying the uniqueness check in ''Sign'' and adding test vectors for it.
Thanks to w0xlt for reviewing and contributing additional test vectors.
== Footnotes ==
<references />
+605
View File
@@ -0,0 +1,605 @@
#!/usr/bin/env python3
"""
Generate test vectors for BIP 459 DahLIAS fully aggregated signatures
for secp256k1.
Usage:
python gen_test_vectors.py
Outputs:
- vectors/test_vectors_nonce_gen.csv (NonceGen with a fixed rand')
- vectors/test_vectors_nonce_agg.csv (NonceAgg: success and failure cases)
- vectors/test_vectors_sign.csv (end-to-end signing with fixed nonces)
- vectors/test_vectors_sign_error.csv (sessions in which Sign must fail)
- vectors/test_vectors_verify.csv (Verify: success and failure cases)
- vectors/test_vectors_partial_sig_verify.csv (PartialSigVerify cases)
- vectors/test_vectors_tweak.csv (plain and X-only key tweaking)
"""
import csv
import os
import sys
from pathlib import Path
from typing import List
sys.path.insert(0, str(Path(__file__).parent / "secp256k1lab/src"))
from secp256k1lab.secp256k1 import FE, G, GE, Scalar
from secp256k1lab.util import bytes_from_int, int_from_bytes, tagged_hash
from reference import (
NonceGenInternal,
NonceAgg,
Sign,
SigAgg,
Verify,
PartialSigVerify,
TweakSK,
TweakPK,
has_even_y,
n,
FULLAGG_TAG_SIG,
)
VECTORS_DIR = Path(__file__).resolve().parent / "vectors"
NONCE_GEN_VECTORS = VECTORS_DIR / "test_vectors_nonce_gen.csv"
NONCE_AGG_VECTORS = VECTORS_DIR / "test_vectors_nonce_agg.csv"
SIGN_VECTORS = VECTORS_DIR / "test_vectors_sign.csv"
SIGN_ERROR_VECTORS = VECTORS_DIR / "test_vectors_sign_error.csv"
VERIFY_VECTORS = VECTORS_DIR / "test_vectors_verify.csv"
PARTIAL_SIG_VERIFY_VECTORS = VECTORS_DIR / "test_vectors_partial_sig_verify.csv"
TWEAK_VECTORS = VECTORS_DIR / "test_vectors_tweak.csv"
def scalar_from_byte(b: int) -> Scalar:
"""Scalar whose 32-byte big-endian encoding is bytes([b]*32). Safe for 0x01..0xee."""
assert 1 <= b <= 0xee
return Scalar.from_bytes_checked(bytes([b] * 32))
def make_signer(i: int):
"""Deterministic (sk, pk, msg, secnonce, pubnonce). sk is normalized so pk has even y."""
sk = scalar_from_byte(i * 3 + 1)
pk = sk * G
if not has_even_y(pk):
sk = -sk
pk = sk * G
r1 = scalar_from_byte(i * 3 + 2)
r2 = scalar_from_byte(i * 3 + 3)
R1, R2 = r1 * G, r2 * G
msg = bytes([0x80 + i] * 32)
return sk, pk, msg, (r1, r2), (R1, R2)
def make_same_pk_signers():
"""Two signers sharing one key, signing different messages."""
sk = scalar_from_byte(1)
pk = sk * G
if not has_even_y(pk):
sk = -sk
pk = sk * G
msg_a = bytes([0xA0] * 32)
msg_b = bytes([0xA1] * 32)
r1_a, r2_a = scalar_from_byte(11), scalar_from_byte(12)
r1_b, r2_b = scalar_from_byte(13), scalar_from_byte(14)
return [
(sk, pk, msg_a, (r1_a, r2_a), (r1_a * G, r2_a * G)),
(sk, pk, msg_b, (r1_b, r2_b), (r1_b * G, r2_b * G)),
]
def make_tweaked_signer(i: int, tweak_byte: int, is_xonly: bool):
"""Signer with a tweaked key pair, using an odd-y base for X-only tweaks to exercise normalization."""
sk, pk, msg, secnonce, pubnonce = make_signer(i)
if is_xonly:
sk, pk = -sk, -pk
t = scalar_from_byte(tweak_byte)
return TweakSK(sk, t, is_xonly), TweakPK(pk, t, is_xonly), msg, secnonce, pubnonce
def make_duplicate_pm_signers():
"""Two signers with identical (pk, m) but distinct nonces."""
sk = scalar_from_byte(1)
pk = sk * G
if not has_even_y(pk):
sk = -sk
pk = sk * G
msg = bytes([0xC0] * 32)
r1_a, r2_a = scalar_from_byte(20), scalar_from_byte(21)
r1_b, r2_b = scalar_from_byte(22), scalar_from_byte(23)
return [
(sk, pk, msg, (r1_a, r2_a), (r1_a * G, r2_a * G)),
(sk, pk, msg, (r1_b, r2_b), (r1_b * G, r2_b * G)),
]
def serialize_pubnonce(pubnonce) -> bytes:
R1, R2 = pubnonce
return R1.to_bytes_compressed() + R2.to_bytes_compressed()
def serialize_secnonce(secnonce) -> bytes:
r1, r2 = secnonce
return r1.to_bytes() + r2.to_bytes()
def serialize_sig(sig) -> bytes:
R, s = sig
return R.to_bytes_xonly() + s.to_bytes()
def serialize_pk(pk: GE) -> bytes:
return pk.to_bytes_xonly()
def format_list(items: List[bytes], sep: str = ";") -> str:
if not items:
return ""
return sep.join(item.hex().upper() for item in items)
def sign_session(signers):
"""Run NonceAgg -> Sign (per signer) -> SigAgg with deterministic inputs."""
sks = [s[0] for s in signers]
pks = [GE.from_bytes_xonly(s[1].to_bytes_xonly()) for s in signers]
msgs = [s[2] for s in signers]
secnonces = [s[3] for s in signers]
pubnonces = [s[4] for s in signers]
aggnonce = NonceAgg(pubnonces)
psigs = [
Sign(secnonces[i], sks[i], msgs[i], aggnonce, pks, msgs, pubnonces)
for i in range(len(sks))
]
sig = SigAgg(aggnonce, pks, msgs, pubnonces, psigs)
return aggnonce, psigs, sig
def sign_unchecked(signers, msgs, r: Scalar):
"""Aggregate signature from a party holding all secret keys, skipping the message length check."""
R = r * G
if not has_even_y(R):
r = -r
R = r * G
L = b"".join(serialize_pk(s[1]) + m for s, m in zip(signers, msgs))
s = r
for (sk, pk, *_), m in zip(signers, msgs):
d = sk if has_even_y(pk) else -sk
c = Scalar.from_bytes_wrapping(tagged_hash(FULLAGG_TAG_SIG, L + R.to_bytes_xonly() + serialize_pk(pk) + m))
s = s + c * d
return R, s
def gen_nonce_gen_vectors(f):
writer = csv.writer(f)
writer.writerow((
"index", "rand_", "secret_key", "extra_in",
"expected_secnonce", "expected_pubnonce", "comment",
))
rand_prime = bytes([0x0F] * 32)
def row(idx, sk, extra_in, comment):
secnonce, pubnonce = NonceGenInternal(rand_prime, sk, extra_in)
writer.writerow((
idx, rand_prime.hex().upper(),
sk.to_bytes().hex().upper() if sk is not None else "",
extra_in.hex().upper(),
serialize_secnonce(secnonce).hex().upper(),
serialize_pubnonce(pubnonce).hex().upper(),
comment,
))
sk = scalar_from_byte(0x02)
extra_in = bytes([0x08] * 32)
row(0, sk, extra_in, "Secret key and extra_in")
row(1, sk, b"", "Secret key and no extra_in")
row(2, None, extra_in, "No secret key and extra_in")
row(3, None, b"", "No secret key and no extra_in")
def gen_nonce_agg_vectors(f):
writer = csv.writer(f)
writer.writerow((
"index", "pubnonces", "expected_result", "expected_aggnonce", "comment",
))
def nonce_agg_bytes(pubnonces):
return NonceAgg([(GE.from_bytes_compressed(p[:33]), GE.from_bytes_compressed(p[33:]))
for p in pubnonces])
def row(idx, pubnonces, comment):
aggnonce = serialize_pubnonce(nonce_agg_bytes(pubnonces))
writer.writerow((idx, format_list(pubnonces), "TRUE", aggnonce.hex().upper(), comment))
def error_row(idx, pubnonces, comment):
try:
nonce_agg_bytes(pubnonces)
except ValueError:
pass
else:
raise RuntimeError(f"NonceAgg unexpectedly succeeded: {comment}")
writer.writerow((idx, format_list(pubnonces), "FALSE", "", comment))
signers = [make_signer(i) for i in range(3)]
pubnonces = [serialize_pubnonce(s[4]) for s in signers]
(R1_0, R2_0), (R1_1, R2_1) = signers[0][4], signers[1][4]
row(0, pubnonces[:2], "Two signers")
row(1, pubnonces, "Three signers")
error_row(2, [pubnonces[0], b"\x04" + pubnonces[1][1:]],
"Signer 1's pubnonce first half has prefix 0x04")
error_row(3, [pubnonces[0][:33] + b"\x02" + bytes(32), pubnonces[1]],
"Signer 0's pubnonce second half is not on the curve")
error_row(4, [pubnonces[0][:33] + b"\x02" + bytes_from_int(FE.SIZE + 1), pubnonces[1]],
"Signer 0's pubnonce second half exceeds field size")
error_row(5, [pubnonces[0], serialize_pubnonce((-R1_0, R2_1))],
"Aggregate nonce R1 is infinity")
error_row(6, [pubnonces[0], serialize_pubnonce((R1_1, -R2_0))],
"Aggregate nonce R2 is infinity")
def gen_sign_vectors(f):
writer = csv.writer(f)
writer.writerow((
"index",
"secret_keys",
"messages",
"secnonces",
"expected_aggnonce",
"expected_partial_sigs",
"expected_sig",
"comment",
))
def write_row(idx, signers, comment):
aggnonce, psigs, sig = sign_session(signers)
writer.writerow((
idx,
format_list([s[0].to_bytes() for s in signers]),
format_list([s[2] for s in signers]),
format_list([serialize_secnonce(s[3]) for s in signers]),
serialize_pubnonce(aggnonce).hex().upper(),
format_list([p.to_bytes() for p in psigs]),
serialize_sig(sig).hex().upper(),
comment,
))
pks = [s[1] for s in signers]
msgs = [s[2] for s in signers]
pubnonces = [s[4] for s in signers]
for i in range(len(signers)):
assert PartialSigVerify(psigs[i], pks, msgs, pubnonces, i), \
f"generated psig failed self-verification: {comment}"
assert Verify(pks, msgs, sig), \
f"generated sig failed self-verification: {comment}"
write_row(0, [make_signer(0)], "Single signer")
write_row(1, [make_signer(i) for i in range(2)], "Two signers")
write_row(2, [make_signer(i) for i in range(3)], "Three signers")
write_row(3, [make_signer(i) for i in range(5)], "Five signers")
write_row(4, make_same_pk_signers(), "Same pubkey signs two different messages")
write_row(5, [make_tweaked_signer(0, 0x21, False), make_tweaked_signer(1, 0x22, True)],
"Signers with tweaked keys")
write_row(6, make_duplicate_pm_signers(), "Exact duplicate (pk, m) entries")
def gen_sign_error_vectors(f):
writer = csv.writer(f)
writer.writerow((
"index", "secret_key", "message", "secnonce", "aggnonce",
"pubkeys", "messages", "pubnonces",
"comment",
))
def write_row(idx, signer, pks, msgs, pubnonces, comment, aggnonce=None):
sk, _, msg, secnonce, _ = signer
if aggnonce is None:
aggnonce = serialize_pubnonce(NonceAgg(pubnonces))
try:
R1, R2 = GE.from_bytes_compressed(aggnonce[:33]), GE.from_bytes_compressed(aggnonce[33:])
Sign(secnonce, sk, msg, (R1, R2), pks, msgs, pubnonces)
except ValueError:
pass
else:
raise RuntimeError(f"Sign unexpectedly succeeded: {comment}")
writer.writerow((
idx,
sk.to_bytes().hex().upper(),
msg.hex().upper(),
serialize_secnonce(secnonce).hex().upper(),
aggnonce.hex().upper(),
format_list([serialize_pk(p) for p in pks]),
format_list(msgs),
format_list([serialize_pubnonce(p) for p in pubnonces]),
comment,
))
signers = [make_signer(i) for i in range(3)]
pks = [GE.from_bytes_xonly(s[1].to_bytes_xonly()) for s in signers]
msgs = [s[2] for s in signers]
pubnonces = [s[4] for s in signers]
dup_pubnonces = list(pubnonces)
dup_pubnonces[2] = (pubnonces[2][0], pubnonces[0][1])
write_row(0, signers[0], pks, msgs, dup_pubnonces,
"Signer's R2 appears at two indices")
missing_pubnonces = list(pubnonces)
missing_pubnonces[0] = (pubnonces[0][0], scalar_from_byte(0x63) * G)
write_row(1, signers[0], pks, msgs, missing_pubnonces,
"Signer's R2 appears at no index")
wrong_msgs = list(msgs)
wrong_msgs[0] = bytes([0xEE] * 32)
write_row(2, signers[0], pks, wrong_msgs, pubnonces,
"Message at signer's index was substituted")
wrong_pks = list(pks)
wrong_pks[0] = GE.from_bytes_xonly((scalar_from_byte(0x55) * G).to_bytes_xonly())
write_row(3, signers[0], wrong_pks, msgs, pubnonces,
"Public key at signer's index was substituted")
sk, pk, msg, secnonce, pubnonce = signers[0]
write_row(4, (sk, pk, msg, (Scalar(0), Scalar(0)), pubnonce), pks, msgs, pubnonces,
"Secnonce is all zeros")
write_row(5, (Scalar(0), pk, msg, secnonce, pubnonce), pks, msgs, pubnonces,
"Secret key is zero")
aggnonce = serialize_pubnonce(NonceAgg(pubnonces))
write_row(6, signers[0], pks, msgs, pubnonces,
"Aggregate nonce first half is all zeros", bytes(33) + aggnonce[33:])
write_row(7, signers[0], pks, msgs, pubnonces,
"Aggregate nonce second half is not on the curve", aggnonce[:33] + b"\x02" + bytes(32))
write_row(8, signers[0], pks, msgs, pubnonces,
"Aggregate nonce second half exceeds field size",
aggnonce[:33] + b"\x02" + bytes_from_int(FE.SIZE + 1))
def gen_verify_vectors(f):
writer = csv.writer(f)
writer.writerow((
"index", "pubkeys", "messages", "sig", "expected_result", "comment",
))
idx = 0
signers = [make_signer(i) for i in range(2)]
_, _, sig = sign_session(signers)
pks = [serialize_pk(s[1]) for s in signers]
msgs = [s[2] for s in signers]
sig_bytes = serialize_sig(sig)
writer.writerow((idx, format_list(pks), format_list(msgs),
sig_bytes.hex().upper(), "TRUE",
"Two-signer valid aggregate"))
idx += 1
signers3 = [make_signer(i) for i in range(3)]
_, _, sig3 = sign_session(signers3)
pks3 = [serialize_pk(s[1]) for s in signers3]
msgs3 = [s[2] for s in signers3]
sig3_bytes = serialize_sig(sig3)
writer.writerow((idx, format_list(pks3), format_list(msgs3),
sig3_bytes.hex().upper(), "TRUE",
"Three-signer valid aggregate"))
idx += 1
wrong_msgs = [msgs[0], bytes([0xff] * 32)]
writer.writerow((idx, format_list(pks), format_list(wrong_msgs),
sig_bytes.hex().upper(), "FALSE",
"Wrong message for second signer"))
idx += 1
writer.writerow((idx, format_list([pks[1], pks[0]]), format_list(msgs),
sig_bytes.hex().upper(), "FALSE",
"Pubkey order swapped"))
idx += 1
writer.writerow((idx, format_list([pks[0]]), format_list([msgs[0]]),
sig_bytes.hex().upper(), "FALSE",
"Dropped second signer"))
idx += 1
s_int = int_from_bytes(sig_bytes[32:64])
tampered = sig_bytes[:32] + bytes_from_int(s_int ^ 1)
writer.writerow((idx, format_list(pks), format_list(msgs),
tampered.hex().upper(), "FALSE",
"Tampered s (bit flip)"))
idx += 1
s_zero = sig_bytes[:32] + bytes(32)
writer.writerow((idx, format_list(pks), format_list(msgs),
s_zero.hex().upper(), "FALSE",
"s = 0 (in range, wrong)"))
idx += 1
s_eq_n = sig_bytes[:32] + bytes_from_int(n)
writer.writerow((idx, format_list(pks), format_list(msgs),
s_eq_n.hex().upper(), "FALSE",
"s = n (out of range)"))
idx += 1
s_max = sig_bytes[:32] + bytes([0xff] * 32)
writer.writerow((idx, format_list(pks), format_list(msgs),
s_max.hex().upper(), "FALSE",
"s = 2^256 - 1"))
idx += 1
def find_off_curve_x() -> bytes:
for i in range(1, 256):
cand = bytes([i] * 32)
try:
GE.from_bytes_xonly(cand)
except Exception:
return cand
raise RuntimeError("could not find an off-curve x")
off_curve = find_off_curve_x()
bad_R_sig = off_curve + sig_bytes[32:64]
writer.writerow((idx, format_list(pks), format_list(msgs),
bad_R_sig.hex().upper(), "FALSE",
"R x-coord not on curve"))
idx += 1
writer.writerow((idx, format_list([off_curve, pks[1]]), format_list(msgs),
sig_bytes.hex().upper(), "FALSE",
"Public key not on curve"))
idx += 1
writer.writerow((idx, format_list(pks), format_list(msgs),
sig_bytes[:-1].hex().upper(), "FALSE",
"Signature too short"))
idx += 1
writer.writerow((idx, format_list(pks), format_list(msgs),
(sig_bytes + b"\x00").hex().upper(), "FALSE",
"Signature too long"))
idx += 1
writer.writerow((idx, format_list(pks), format_list([msgs[0]]),
sig_bytes.hex().upper(), "FALSE",
"Pubkey/message count mismatch"))
idx += 1
writer.writerow((idx, "", "",
sig_bytes.hex().upper(), "FALSE",
"Empty signer list"))
idx += 1
for bad_m0, comment in ((msgs[0][:31], "Message too short"), (msgs[0] + b"\x00", "Message too long")):
bad_msgs = [bad_m0, msgs[1]]
bad_sig = serialize_sig(sign_unchecked(signers, bad_msgs, scalar_from_byte(0x70)))
writer.writerow((idx, format_list(pks), format_list(bad_msgs),
bad_sig.hex().upper(), "FALSE", comment))
idx += 1
def gen_partial_sig_verify_vectors(f):
writer = csv.writer(f)
writer.writerow((
"index", "signer_index", "psig",
"pubkeys", "messages", "pubnonces",
"expected_result", "comment",
))
signers = [make_signer(i) for i in range(2)]
aggnonce, psigs, _ = sign_session(signers)
pks_hex = format_list([serialize_pk(s[1]) for s in signers])
msgs_hex = format_list([s[2] for s in signers])
pubnonces_hex = format_list([serialize_pubnonce(s[4]) for s in signers])
def write_row(idx, signer_index, psig_bytes, expected, comment):
writer.writerow((
idx, signer_index, psig_bytes.hex().upper(),
pks_hex, msgs_hex, pubnonces_hex,
expected, comment,
))
idx = 0
write_row(idx, 0, psigs[0].to_bytes(), "TRUE", "Honest partial sig (signer 0)")
idx += 1
write_row(idx, 1, psigs[1].to_bytes(), "TRUE", "Honest partial sig (signer 1)")
idx += 1
write_row(idx, 1, psigs[0].to_bytes(), "FALSE",
"Honest psig verified at the wrong signer_index")
idx += 1
write_row(idx, 0, bytes_from_int(int(psigs[0]) ^ 1), "FALSE",
"Tampered partial sig (bit flip)")
idx += 1
write_row(idx, 0, bytes(32), "FALSE", "psig = 0 (in range, wrong)")
idx += 1
write_row(idx, 0, bytes_from_int(n), "FALSE", "psig = n (out of range)")
idx += 1
write_row(idx, 2, psigs[0].to_bytes(), "FALSE", "signer_index out of range")
idx += 1
fake_aggnonce = (aggnonce[0] + G, aggnonce[1])
psig_wrong = Sign(signers[0][3], signers[0][0], signers[0][2], fake_aggnonce,
[s[1] for s in signers], [s[2] for s in signers],
[s[4] for s in signers])
write_row(idx, 0, psig_wrong.to_bytes(), "FALSE", "psig for a different aggregate nonce")
idx += 1
R1_0, R2_0 = signers[0][4]
_, R2_1 = signers[1][4]
inf_pubnonces = format_list([serialize_pubnonce((R1_0, R2_0)),
serialize_pubnonce((-R1_0, R2_1))])
writer.writerow((idx, 0, psigs[0].to_bytes().hex().upper(),
pks_hex, msgs_hex, inf_pubnonces,
"FALSE", "Aggregate nonce R1 is infinity"))
def gen_tweak_vectors(f):
writer = csv.writer(f)
writer.writerow((
"index", "internal_secret_key", "tweak", "is_xonly", "expected_result",
"expected_secret_key", "expected_pubkey", "comment",
))
def row(idx, sk, t, is_xonly, comment):
sk_out = TweakSK(sk, t, is_xonly)
pk_out = TweakPK(sk * G, t, is_xonly)
assert sk_out * G == pk_out
writer.writerow((
idx, sk.to_bytes().hex().upper(), t.to_bytes().hex().upper(),
"TRUE" if is_xonly else "FALSE", "TRUE",
sk_out.to_bytes().hex().upper(), pk_out.to_bytes_compressed().hex().upper(),
comment,
))
def error_row(idx, sk, t, is_xonly, comment):
for tweak_fn, key in ((TweakSK, sk), (TweakPK, sk * G)):
try:
tweak_fn(key, t, is_xonly)
except ValueError:
continue
raise RuntimeError(f"{tweak_fn.__name__} unexpectedly succeeded: {comment}")
writer.writerow((
idx, sk.to_bytes().hex().upper(), t.to_bytes().hex().upper(),
"TRUE" if is_xonly else "FALSE", "FALSE", "", "",
comment,
))
sk1 = scalar_from_byte(1)
even = sk1 if has_even_y(sk1 * G) else -sk1
odd = -even
t = scalar_from_byte(0x02)
row(0, even, t, False, "Plain tweak of an even-y key")
row(1, odd, t, False, "Plain tweak of an odd-y key")
row(2, even, t, True, "X-only tweak of an even-y key")
row(3, odd, t, True, "X-only tweak of an odd-y key")
error_row(4, odd, -odd, False, "Plain tweak resulting in the point at infinity")
error_row(5, odd, odd, True, "X-only tweak resulting in the point at infinity")
writer.writerow((6, even.to_bytes().hex().upper(), bytes_from_int(n).hex().upper(),
"FALSE", "FALSE", "", "", "Tweak = n (out of range)"))
if __name__ == "__main__":
os.makedirs(VECTORS_DIR, exist_ok=True)
print(f"Generating {NONCE_GEN_VECTORS}")
with open(NONCE_GEN_VECTORS, "w", newline="", encoding="utf-8") as f:
gen_nonce_gen_vectors(f)
print(f"Generating {NONCE_AGG_VECTORS}")
with open(NONCE_AGG_VECTORS, "w", newline="", encoding="utf-8") as f:
gen_nonce_agg_vectors(f)
print(f"Generating {SIGN_VECTORS}")
with open(SIGN_VECTORS, "w", newline="", encoding="utf-8") as f:
gen_sign_vectors(f)
print(f"Generating {SIGN_ERROR_VECTORS}")
with open(SIGN_ERROR_VECTORS, "w", newline="", encoding="utf-8") as f:
gen_sign_error_vectors(f)
print(f"Generating {VERIFY_VECTORS}")
with open(VERIFY_VECTORS, "w", newline="", encoding="utf-8") as f:
gen_verify_vectors(f)
print(f"Generating {PARTIAL_SIG_VERIFY_VECTORS}")
with open(PARTIAL_SIG_VERIFY_VECTORS, "w", newline="", encoding="utf-8") as f:
gen_partial_sig_verify_vectors(f)
print(f"Generating {TWEAK_VECTORS}")
with open(TWEAK_VECTORS, "w", newline="", encoding="utf-8") as f:
gen_tweak_vectors(f)
print("Done.")
+211
View File
@@ -0,0 +1,211 @@
#!/usr/bin/env python3
"""
Reference implementation of BIP 459 DahLIAS fully aggregated signatures
for secp256k1.
WARNING: This implementation is for demonstration purposes only and is not
optimized for production use.
"""
from pathlib import Path
from typing import List, Tuple, Optional
import secrets
import sys
sys.path.insert(0, str(Path(__file__).parent / "secp256k1lab/src"))
from secp256k1lab.secp256k1 import G, GE, Scalar
from secp256k1lab.util import tagged_hash, xor_bytes
n = GE.ORDER
FULLAGG_TAG_AUX = "BIP0459/aux"
FULLAGG_TAG_NONCE = "BIP0459/nonce"
FULLAGG_TAG_NONCECOEF = "BIP0459/noncecoef"
FULLAGG_TAG_SIG = "BIP0459/sig"
#
# Helpers
#
def has_even_y(P: GE) -> bool:
return P.has_even_y()
def cbytes(P: GE) -> bytes:
return P.to_bytes_compressed()
def xbytes(P: GE) -> bytes:
return P.to_bytes_xonly()
#
# Key Tweaking
#
def TweakSK(sk: Scalar, t: Scalar, is_xonly: bool) -> Scalar:
d = sk if (not is_xonly or has_even_y(sk * G)) else -sk
sk_out = d + t
if sk_out == 0:
raise ValueError("tweaked secret key is zero")
return sk_out
def TweakPK(pk: GE, t: Scalar, is_xonly: bool) -> GE:
P = pk if (not is_xonly or has_even_y(pk)) else -pk
Q = P + t * G
if Q.infinity:
raise ValueError("tweaked public key is the point at infinity")
return Q
#
# Nonce Generation and Aggregation
#
def NonceGenInternal(rand_prime: bytes, sk: Optional[Scalar] = None,
extra_in: bytes = b'') -> Tuple[Tuple[Scalar, Scalar], Tuple[GE, GE]]:
if sk is not None:
rand = xor_bytes(sk.to_bytes(), tagged_hash(FULLAGG_TAG_AUX, rand_prime))
else:
rand = rand_prime
r1 = Scalar.from_bytes_wrapping(tagged_hash(FULLAGG_TAG_NONCE, rand + extra_in + b'\x00'))
r2 = Scalar.from_bytes_wrapping(tagged_hash(FULLAGG_TAG_NONCE, rand + extra_in + b'\x01'))
assert r1 != 0 and r2 != 0
R1, R2 = r1 * G, r2 * G
return (r1, r2), (R1, R2)
def NonceGen(sk: Optional[Scalar] = None,
extra_in: bytes = b'') -> Tuple[Tuple[Scalar, Scalar], Tuple[GE, GE]]:
rand_prime = secrets.token_bytes(32)
return NonceGenInternal(rand_prime, sk, extra_in)
def NonceAgg(pubnonces: List[Tuple[GE, GE]]) -> Tuple[GE, GE]:
u = len(pubnonces)
R1, R2 = pubnonces[0]
for i in range(1, u):
R1 = R1 + pubnonces[i][0]
R2 = R2 + pubnonces[i][1]
if R1.infinity or R2.infinity:
raise ValueError("aggregate nonce is the point at infinity")
return R1, R2
#
# Session Values
#
def GetSessionValues(aggnonce: Tuple[GE, GE], pks: List[GE], msgs: List[bytes],
pubnonces: List[Tuple[GE, GE]]) -> Tuple[GE, Scalar]:
R1, R2 = aggnonce
u = len(pks)
nonce_data = cbytes(R1) + cbytes(R2)
for i in range(u):
nonce_data += xbytes(pks[i]) + msgs[i] + cbytes(pubnonces[i][1])
b = Scalar.from_bytes_wrapping(tagged_hash(FULLAGG_TAG_NONCECOEF, nonce_data))
R = R1 + b * R2
assert not R.infinity
return R, b
#
# Signing
#
def Sign(secnonce: Tuple[Scalar, Scalar], sk: Scalar, m: bytes,
aggnonce: Tuple[GE, GE], pks: List[GE], msgs: List[bytes],
pubnonces: List[Tuple[GE, GE]]) -> Scalar:
if not (len(pks) == len(msgs) == len(pubnonces) >= 1):
raise ValueError("list lengths must match and be nonzero")
if len(m) != 32 or any(len(mi) != 32 for mi in msgs):
raise ValueError("messages must be 32 bytes")
r1, r2 = secnonce
if r1 == 0 or r2 == 0:
raise ValueError("secnonce value is zero")
if sk == 0:
raise ValueError("secret key is zero")
P = sk * G
R2_local = r2 * G
# Index lookup and uniqueness check
matches = [j for j in range(len(pubnonces)) if pubnonces[j][1] == R2_local]
if len(matches) != 1:
raise ValueError("signer's R2 must appear exactly once")
j = matches[0]
if xbytes(pks[j]) != xbytes(P) or msgs[j] != m:
raise ValueError("public key or message at signer's index does not match")
R, b = GetSessionValues(aggnonce, pks, msgs, pubnonces)
e = Scalar(1) if has_even_y(R) else -Scalar(1)
d_prime = sk if has_even_y(P) else -sk
L = b''
for i in range(len(pks)):
L += xbytes(pks[i]) + msgs[i]
c_j = Scalar.from_bytes_wrapping(tagged_hash(FULLAGG_TAG_SIG, L + xbytes(R) + xbytes(pks[j]) + msgs[j]))
s_j = e * (r1 + b * r2) + c_j * d_prime
return s_j
#
# Aggregation
#
def SigAgg(aggnonce: Tuple[GE, GE], pks: List[GE], msgs: List[bytes],
pubnonces: List[Tuple[GE, GE]], psigs: List[Scalar]) -> Tuple[GE, Scalar]:
if not (len(pks) == len(msgs) == len(pubnonces) == len(psigs) >= 1):
raise ValueError("list lengths must match and be nonzero")
if any(len(mi) != 32 for mi in msgs):
raise ValueError("messages must be 32 bytes")
R, _ = GetSessionValues(aggnonce, pks, msgs, pubnonces)
s = Scalar.sum(*psigs)
return R, s
#
# Partial Signature Verification
#
def PartialSigVerify(psig: Scalar, pks: List[GE], msgs: List[bytes],
pubnonces: List[Tuple[GE, GE]], signer_index: int) -> bool:
if not (len(pks) == len(msgs) == len(pubnonces) >= 1) or any(len(mi) != 32 for mi in msgs):
return False
if not (0 <= signer_index < len(pks)):
return False
i = signer_index
R1_i, R2_i = pubnonces[i]
P_i = GE.from_bytes_xonly(xbytes(pks[i]))
R, b = GetSessionValues(NonceAgg(pubnonces), pks, msgs, pubnonces)
e = Scalar(1) if has_even_y(R) else -Scalar(1)
L = b''
for k in range(len(pks)):
L += xbytes(pks[k]) + msgs[k]
c_i = Scalar.from_bytes_wrapping(tagged_hash(FULLAGG_TAG_SIG, L + xbytes(R) + xbytes(pks[i]) + msgs[i]))
R_eff_i = R1_i + b * R2_i
return psig * G == e * R_eff_i + c_i * P_i
#
# Aggregate Signature Verification
#
def Verify(pks: List[GE], msgs: List[bytes], sig: Tuple[GE, Scalar]) -> bool:
R_point, s = sig
if not (len(pks) == len(msgs) >= 1) or any(len(mi) != 32 for mi in msgs):
return False
u = len(pks)
R = GE.from_bytes_xonly(xbytes(R_point))
Ps = [GE.from_bytes_xonly(xbytes(pks[i])) for i in range(u)]
L = b''
for i in range(u):
L += xbytes(pks[i]) + msgs[i]
rhs = R
for i in range(u):
c_i = Scalar.from_bytes_wrapping(tagged_hash(FULLAGG_TAG_SIG, L + xbytes(R) + xbytes(pks[i]) + msgs[i]))
rhs = rhs + c_i * Ps[i]
return s * G == rhs
+314
View File
@@ -0,0 +1,314 @@
#!/usr/bin/env python3
"""
Run test vectors for BIP 459 DahLIAS fully aggregated signatures
for secp256k1.
Usage:
python run_test_vectors.py
Expects CSV files generated by gen_test_vectors.py in vectors/ directory.
"""
import csv
import os
import sys
from pathlib import Path
from typing import List, Tuple
sys.path.insert(0, str(Path(__file__).parent / "secp256k1lab/src"))
from secp256k1lab.secp256k1 import G, GE, Scalar
from reference import NonceGenInternal, NonceAgg, Sign, SigAgg, Verify, PartialSigVerify, TweakSK, TweakPK
VECTORS_DIR = Path(__file__).resolve().parent / "vectors"
NONCE_GEN_VECTORS = VECTORS_DIR / "test_vectors_nonce_gen.csv"
NONCE_AGG_VECTORS = VECTORS_DIR / "test_vectors_nonce_agg.csv"
SIGN_VECTORS = VECTORS_DIR / "test_vectors_sign.csv"
SIGN_ERROR_VECTORS = VECTORS_DIR / "test_vectors_sign_error.csv"
VERIFY_VECTORS = VECTORS_DIR / "test_vectors_verify.csv"
PARTIAL_SIG_VERIFY_VECTORS = VECTORS_DIR / "test_vectors_partial_sig_verify.csv"
TWEAK_VECTORS = VECTORS_DIR / "test_vectors_tweak.csv"
def parse_hex_list(hex_str: str) -> List[bytes]:
if not hex_str:
return []
return [bytes.fromhex(h) for h in hex_str.split(";")]
def parse_pubnonce(b: bytes) -> Tuple[GE, GE]:
if len(b) != 66:
raise ValueError("pubnonce must be 66 bytes")
return GE.from_bytes_compressed(b[:33]), GE.from_bytes_compressed(b[33:])
def parse_secnonce(b: bytes) -> Tuple[Scalar, Scalar]:
if len(b) != 64:
raise ValueError("secnonce must be 64 bytes")
return Scalar.from_bytes_checked(b[:32]), Scalar.from_bytes_checked(b[32:])
def parse_sig(b: bytes) -> Tuple[GE, Scalar]:
"""Parse R (32-byte x-only) || s (32-byte big-endian).
Lifts R to even y and raises ValueError on length mismatch, off-curve R, or
s >= n.
"""
if len(b) != 64:
raise ValueError("signature must be 64 bytes")
R = GE.from_bytes_xonly(b[:32])
s = Scalar.from_bytes_checked(b[32:])
return R, s
def run_nonce_gen_tests() -> List[str]:
failures = []
with open(NONCE_GEN_VECTORS, newline="", encoding="utf-8") as f:
reader = csv.reader(f)
next(reader)
for row in reader:
(index, rand_str, sk_str, extra_in_str,
expected_secnonce_str, expected_pubnonce_str, comment) = row
sk = Scalar.from_bytes_checked(bytes.fromhex(sk_str)) if sk_str else None
(r1, r2), (R1, R2) = NonceGenInternal(bytes.fromhex(rand_str), sk, bytes.fromhex(extra_in_str))
secnonce = r1.to_bytes() + r2.to_bytes()
pubnonce = R1.to_bytes_compressed() + R2.to_bytes_compressed()
if (secnonce.hex().upper() != expected_secnonce_str
or pubnonce.hex().upper() != expected_pubnonce_str):
failures.append(f"NonceGen #{index}: {comment}")
return failures
def run_nonce_agg_tests() -> List[str]:
failures = []
with open(NONCE_AGG_VECTORS, newline="", encoding="utf-8") as f:
reader = csv.reader(f)
next(reader)
for row in reader:
index, pubnonces_str, expected_result, expected_aggnonce_str, comment = row
expected = (expected_result == "TRUE")
try:
pubnonces = [parse_pubnonce(b) for b in parse_hex_list(pubnonces_str)]
R1, R2 = NonceAgg(pubnonces)
aggnonce = R1.to_bytes_compressed() + R2.to_bytes_compressed()
except ValueError:
aggnonce = None
if expected:
ok = aggnonce is not None and aggnonce.hex().upper() == expected_aggnonce_str
else:
ok = aggnonce is None
if not ok:
failures.append(f"NonceAgg #{index}: {comment}")
return failures
def run_sign_tests() -> List[str]:
failures = []
with open(SIGN_VECTORS, newline="", encoding="utf-8") as f:
reader = csv.reader(f)
next(reader)
for row in reader:
(index, sks_str, msgs_str, secnonces_str,
expected_aggnonce_str, expected_psigs_str,
expected_sig_str, comment) = row
try:
sks = [Scalar.from_bytes_checked(b) for b in parse_hex_list(sks_str)]
msgs = parse_hex_list(msgs_str)
secnonces = [parse_secnonce(b) for b in parse_hex_list(secnonces_str)]
expected_aggnonce = bytes.fromhex(expected_aggnonce_str)
expected_psigs = parse_hex_list(expected_psigs_str)
expected_sig = bytes.fromhex(expected_sig_str)
pks = [GE.from_bytes_xonly((sk * G).to_bytes_xonly()) for sk in sks]
pubnonces = [(r1 * G, r2 * G) for (r1, r2) in secnonces]
aggnonce = NonceAgg(pubnonces)
aggnonce_bytes = (aggnonce[0].to_bytes_compressed()
+ aggnonce[1].to_bytes_compressed())
if aggnonce_bytes != expected_aggnonce:
failures.append(f"Sign #{index}: {comment} (aggnonce mismatch)")
continue
psigs = [
Sign(secnonces[i], sks[i], msgs[i], aggnonce, pks, msgs, pubnonces)
for i in range(len(sks))
]
derived_psigs = [p.to_bytes() for p in psigs]
if derived_psigs != expected_psigs:
failures.append(f"Sign #{index}: {comment} (partial sig mismatch)")
continue
for i in range(len(sks)):
if not PartialSigVerify(psigs[i], pks, msgs, pubnonces, i):
failures.append(f"Sign #{index}: {comment} (PartialSigVerify failed for signer {i})")
break
else:
R, s = SigAgg(aggnonce, pks, msgs, pubnonces, psigs)
actual_sig = R.to_bytes_xonly() + s.to_bytes()
if actual_sig != expected_sig:
failures.append(f"Sign #{index}: {comment} (aggsig mismatch)")
elif not Verify(pks, msgs, (R, s)):
failures.append(f"Sign #{index}: {comment} (Verify failed)")
except ValueError as e:
failures.append(f"Sign #{index}: {comment} (raised {e!r})")
return failures
def run_sign_error_tests() -> List[str]:
failures = []
with open(SIGN_ERROR_VECTORS, newline="", encoding="utf-8") as f:
reader = csv.reader(f)
next(reader)
for row in reader:
(index, sk_str, msg_str, secnonce_str, aggnonce_str,
pks_str, msgs_str, pubnonces_str, comment) = row
sk = Scalar.from_bytes_checked(bytes.fromhex(sk_str))
m = bytes.fromhex(msg_str)
secnonce = parse_secnonce(bytes.fromhex(secnonce_str))
pks = [GE.from_bytes_xonly(b) for b in parse_hex_list(pks_str)]
msgs = parse_hex_list(msgs_str)
pubnonces = [parse_pubnonce(b) for b in parse_hex_list(pubnonces_str)]
try:
aggnonce = parse_pubnonce(bytes.fromhex(aggnonce_str))
Sign(secnonce, sk, m, aggnonce, pks, msgs, pubnonces)
except ValueError:
continue
failures.append(f"SignError #{index}: {comment} (Sign did not fail)")
return failures
def run_partial_sig_verify_tests() -> List[str]:
failures = []
with open(PARTIAL_SIG_VERIFY_VECTORS, newline="", encoding="utf-8") as f:
reader = csv.reader(f)
next(reader)
for row in reader:
(index, signer_index_str, psig_str,
pks_str, msgs_str, pubnonces_str,
expected_result, comment) = row
expected = (expected_result == "TRUE")
try:
signer_index = int(signer_index_str)
psig = Scalar.from_bytes_checked(bytes.fromhex(psig_str))
pks = [GE.from_bytes_xonly(b) for b in parse_hex_list(pks_str)]
msgs = parse_hex_list(msgs_str)
pubnonces = [parse_pubnonce(b) for b in parse_hex_list(pubnonces_str)]
result = PartialSigVerify(psig, pks, msgs, pubnonces, signer_index)
except ValueError:
result = False
if result != expected:
failures.append(f"PartialSigVerify #{index}: {comment}")
return failures
def run_verify_tests() -> List[str]:
failures = []
with open(VERIFY_VECTORS, newline="", encoding="utf-8") as f:
reader = csv.reader(f)
next(reader)
for row in reader:
index, pks_str, msgs_str, sig_str, expected_result, comment = row
expected = (expected_result == "TRUE")
try:
pks = [GE.from_bytes_xonly(b) for b in parse_hex_list(pks_str)]
msgs = parse_hex_list(msgs_str)
R, s = parse_sig(bytes.fromhex(sig_str))
result = Verify(pks, msgs, (R, s))
except ValueError:
result = False
if result != expected:
failures.append(f"Verify #{index}: {comment}")
return failures
def run_tweak_tests() -> List[str]:
failures = []
with open(TWEAK_VECTORS, newline="", encoding="utf-8") as f:
reader = csv.reader(f)
next(reader)
for row in reader:
(index, sk_str, t_str, is_xonly_str, expected_result,
exp_sk_str, exp_pk_str, comment) = row
sk = Scalar.from_bytes_checked(bytes.fromhex(sk_str))
is_xonly = (is_xonly_str == "TRUE")
expected = (expected_result == "TRUE")
results = []
for tweak_fn, key in ((TweakSK, sk), (TweakPK, sk * G)):
try:
t = Scalar.from_bytes_checked(bytes.fromhex(t_str))
results.append(tweak_fn(key, t, is_xonly))
except ValueError:
results.append(None)
sk_out, pk_out = results
if expected:
ok = (sk_out is not None and pk_out is not None
and sk_out.to_bytes().hex().upper() == exp_sk_str
and pk_out.to_bytes_compressed().hex().upper() == exp_pk_str)
else:
ok = sk_out is None and pk_out is None
if not ok:
failures.append(f"Tweak #{index}: {comment}")
return failures
def main():
for filename in [NONCE_GEN_VECTORS, NONCE_AGG_VECTORS, SIGN_VECTORS, SIGN_ERROR_VECTORS,
VERIFY_VECTORS, PARTIAL_SIG_VERIFY_VECTORS, TWEAK_VECTORS]:
if not os.path.exists(filename):
print(f"ERROR: Missing {filename}. Run gen_test_vectors.py first.")
sys.exit(1)
failures = []
failures.extend(run_nonce_gen_tests())
failures.extend(run_nonce_agg_tests())
failures.extend(run_sign_tests())
failures.extend(run_sign_error_tests())
failures.extend(run_partial_sig_verify_tests())
failures.extend(run_verify_tests())
failures.extend(run_tweak_tests())
if failures:
print("FAILED:")
for f in failures:
print(f" {f}")
sys.exit(1)
else:
print("All tests have passed.")
sys.exit(0)
if __name__ == "__main__":
main()
+34
View File
@@ -0,0 +1,34 @@
name: Tests
on: [push, pull_request]
jobs:
ruff:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install the latest version of uv
uses: astral-sh/setup-uv@v5
- run: uvx ruff check .
mypy:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.11", "3.12", "3.13", "3.14"]
steps:
- uses: actions/checkout@v4
- name: Install the latest version of uv, setup Python ${{ matrix.python-version }}
uses: astral-sh/setup-uv@v5
with:
python-version: ${{ matrix.python-version }}
- run: uvx mypy .
unittest:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.11", "3.12", "3.13", "3.14"]
steps:
- uses: actions/checkout@v4
- name: Setup Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- run: python3 -m unittest
+10
View File
@@ -0,0 +1,10 @@
# Python-generated files
__pycache__/
*.py[oc]
build/
dist/
wheels/
*.egg-info
# Virtual environments
.venv
+1
View File
@@ -0,0 +1 @@
3.11
+25
View File
@@ -0,0 +1,25 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
#### Added
- Added new methods `Scalar.from_int_nonzero_checked` and `Scalar.from_bytes_nonzero_checked`
that ensure a constructed scalar is in the range `0 < s < N` (i.e. is non-zero and within the
group order) and throw a `ValueError` otherwise. This is e.g. useful for ensuring that newly
generated secret keys or nonces are valid without having to do the non-zero check manually.
The already existing methods `Scalar.from_int_checked` and `Scalar.from_bytes_checked` error
on overflow, but not on zero, i.e. they only ensure `0 <= s < N`.
- Added a new method `GE.from_bytes_compressed_with_infinity` to parse a compressed
public key (33 bytes) to a group element, where the all-zeros bytestring maps to the
point at infinity. This is the counterpart to the already existing serialization
method `GE.to_bytes_compressed_with_infinity`.
## [1.0.0] - 2025-03-31
Initial release.
+23
View File
@@ -0,0 +1,23 @@
The MIT License (MIT)
Copyright (c) 2009-2024 The Bitcoin Core developers
Copyright (c) 2009-2024 Bitcoin Developers
Copyright (c) 2025- The secp256k1lab Developers
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
+13
View File
@@ -0,0 +1,13 @@
secp256k1lab
============
![Dependencies: None](https://img.shields.io/badge/dependencies-none-success)
An INSECURE implementation of the secp256k1 elliptic curve and related cryptographic schemes written in Python, intended for prototyping, experimentation and education.
Features:
* Low-level secp256k1 field and group arithmetic.
* Schnorr signing/verification and key generation according to [BIP-340](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki).
* ECDH key exchange.
WARNING: The code in this library is slow and trivially vulnerable to side channel attacks.
+34
View File
@@ -0,0 +1,34 @@
[project]
name = "secp256k1lab"
version = "1.0.0"
description = "An INSECURE implementation of the secp256k1 elliptic curve and related cryptographic schemes, intended for prototyping, experimentation and education"
readme = "README.md"
authors = [
{ name = "Pieter Wuille", email = "pieter@wuille.net" },
{ name = "Tim Ruffing", email = "me@real-or-random.org" },
{ name = "Jonas Nick", email = "jonasd.nick@gmail.com" },
{ name = "Sebastian Falbesoner", email = "sebastian.falbesoner@gmail.com" }
]
maintainers = [
{ name = "Tim Ruffing", email = "me@real-or-random.org" },
{ name = "Jonas Nick", email = "jonasd.nick@gmail.com" },
{ name = "Sebastian Falbesoner", email = "sebastian.falbesoner@gmail.com" }
]
requires-python = ">=3.11"
license = "MIT"
license-files = ["COPYING"]
keywords = ["secp256k1", "elliptic curves", "cryptography", "Bitcoin"]
classifiers = [
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"Intended Audience :: Education",
"Intended Audience :: Science/Research",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python",
"Topic :: Security :: Cryptography",
]
dependencies = []
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
Whitespace-only changes.
@@ -0,0 +1,73 @@
# The following functions are based on the BIP 340 reference implementation:
# https://github.com/bitcoin/bips/blob/master/bip-0340/reference.py
from .secp256k1 import FE, GE, G
from .util import int_from_bytes, bytes_from_int, xor_bytes, tagged_hash
def pubkey_gen(seckey: bytes) -> bytes:
d0 = int_from_bytes(seckey)
if not (1 <= d0 <= GE.ORDER - 1):
raise ValueError("The secret key must be an integer in the range 1..n-1.")
P = d0 * G
assert not P.infinity
return P.to_bytes_xonly()
def schnorr_sign(
msg: bytes, seckey: bytes, aux_rand: bytes, tag_prefix: str = "BIP0340"
) -> bytes:
d0 = int_from_bytes(seckey)
if not (1 <= d0 <= GE.ORDER - 1):
raise ValueError("The secret key must be an integer in the range 1..n-1.")
if len(aux_rand) != 32:
raise ValueError("aux_rand must be 32 bytes instead of %i." % len(aux_rand))
P = d0 * G
assert not P.infinity
d = d0 if P.has_even_y() else GE.ORDER - d0
t = xor_bytes(bytes_from_int(d), tagged_hash(tag_prefix + "/aux", aux_rand))
k0 = (
int_from_bytes(tagged_hash(tag_prefix + "/nonce", t + P.to_bytes_xonly() + msg))
% GE.ORDER
)
if k0 == 0:
raise RuntimeError("Failure. This happens only with negligible probability.")
R = k0 * G
assert not R.infinity
k = k0 if R.has_even_y() else GE.ORDER - k0
e = (
int_from_bytes(
tagged_hash(
tag_prefix + "/challenge", R.to_bytes_xonly() + P.to_bytes_xonly() + msg
)
)
% GE.ORDER
)
sig = R.to_bytes_xonly() + bytes_from_int((k + e * d) % GE.ORDER)
assert schnorr_verify(msg, P.to_bytes_xonly(), sig, tag_prefix=tag_prefix)
return sig
def schnorr_verify(
msg: bytes, pubkey: bytes, sig: bytes, tag_prefix: str = "BIP0340"
) -> bool:
if len(pubkey) != 32:
raise ValueError("The public key must be a 32-byte array.")
if len(sig) != 64:
raise ValueError("The signature must be a 64-byte array.")
try:
P = GE.from_bytes_xonly(pubkey)
except ValueError:
return False
r = int_from_bytes(sig[0:32])
s = int_from_bytes(sig[32:64])
if (r >= FE.SIZE) or (s >= GE.ORDER):
return False
e = (
int_from_bytes(tagged_hash(tag_prefix + "/challenge", sig[0:32] + pubkey + msg))
% GE.ORDER
)
R = s * G - e * P
if R.infinity or (not R.has_even_y()) or (R.x != r):
return False
return True
@@ -0,0 +1,16 @@
import hashlib
from .secp256k1 import GE, Scalar
def ecdh_compressed_in_raw_out(seckey: bytes, pubkey: bytes) -> GE:
"""TODO"""
shared_secret = Scalar.from_bytes_checked(seckey) * GE.from_bytes_compressed(pubkey)
assert not shared_secret.infinity # prime-order group
return shared_secret
def ecdh_libsecp256k1(seckey: bytes, pubkey: bytes) -> bytes:
"""TODO"""
shared_secret = ecdh_compressed_in_raw_out(seckey, pubkey)
return hashlib.sha256(shared_secret.to_bytes_compressed()).digest()
@@ -0,0 +1,15 @@
from .secp256k1 import GE, G
from .util import int_from_bytes
# The following function is based on the BIP 327 reference implementation
# https://github.com/bitcoin/bips/blob/master/bip-0327/reference.py
# Return the plain public key corresponding to a given secret key
def pubkey_gen_plain(seckey: bytes) -> bytes:
d0 = int_from_bytes(seckey)
if not (1 <= d0 <= GE.ORDER - 1):
raise ValueError("The secret key must be an integer in the range 1..n-1.")
P = d0 * G
assert not P.infinity
return P.to_bytes_compressed()
Whitespace-only changes.
@@ -0,0 +1,483 @@
# Copyright (c) 2022-2023 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test-only implementation of low-level secp256k1 field and group arithmetic
It is designed for ease of understanding, not performance.
WARNING: This code is slow and trivially vulnerable to side channel attacks. Do not use for
anything but tests.
Exports:
* FE: class for secp256k1 field elements
* GE: class for secp256k1 group elements
* G: the secp256k1 generator point
"""
from __future__ import annotations
from typing import Self
# TODO Docstrings of methods still say "field element"
class APrimeFE:
"""Objects of this class represent elements of a prime field.
They are represented internally in numerator / denominator form, in order to delay inversions.
"""
# The size of the field (also its modulus and characteristic).
SIZE: int
def __init__(self, a: int | Self = 0, b: int | Self = 1) -> None:
"""Initialize a field element a/b; both a and b can be ints or field elements."""
if isinstance(a, type(self)):
num = a._num
den = a._den
else:
assert isinstance(a, int)
num = a % self.SIZE
den = 1
if isinstance(b, type(self)):
den = (den * b._num) % self.SIZE
num = (num * b._den) % self.SIZE
else:
assert isinstance(b, int)
den = (den * b) % self.SIZE
assert den != 0
if num == 0:
den = 1
self._num: int = num
self._den: int = den
def __add__(self, a: int | Self) -> Self:
"""Compute the sum of two field elements (second may be int)."""
if isinstance(a, type(self)):
return type(self)(self._num * a._den + self._den * a._num, self._den * a._den)
if isinstance(a, int):
return type(self)(self._num + self._den * a, self._den)
return NotImplemented
def __radd__(self, a: int) -> Self:
"""Compute the sum of an integer and a field element."""
return type(self)(a) + self
@classmethod
def sum(cls, *es: Self) -> Self:
"""Compute the sum of field elements.
sum(a, b, c, ...) is identical to (0 + a + b + c + ...)."""
return sum(es, start=cls(0))
def __sub__(self, a: int | Self) -> Self:
"""Compute the difference of two field elements (second may be int)."""
if isinstance(a, type(self)):
return type(self)(self._num * a._den - self._den * a._num, self._den * a._den)
if isinstance(a, int):
return type(self)(self._num - self._den * a, self._den)
return NotImplemented
def __rsub__(self, a: int) -> Self:
"""Compute the difference of an integer and a field element."""
return type(self)(a) - self
def __mul__(self, a: int | Self) -> Self:
"""Compute the product of two field elements (second may be int)."""
if isinstance(a, type(self)):
return type(self)(self._num * a._num, self._den * a._den)
if isinstance(a, int):
return type(self)(self._num * a, self._den)
return NotImplemented
def __rmul__(self, a: int) -> Self:
"""Compute the product of an integer with a field element."""
return type(self)(a) * self
def __truediv__(self, a: int | Self) -> Self:
"""Compute the ratio of two field elements (second may be int)."""
if isinstance(a, type(self)) or isinstance(a, int):
return type(self)(self, a)
return NotImplemented
def __pow__(self, a: int) -> Self:
"""Raise a field element to an integer power."""
return type(self)(pow(self._num, a, self.SIZE), pow(self._den, a, self.SIZE))
def __neg__(self) -> Self:
"""Negate a field element."""
return type(self)(-self._num, self._den)
def __int__(self) -> int:
"""Convert a field element to an integer in range 0..SIZE-1. The result is cached."""
if self._den != 1:
self._num = (self._num * pow(self._den, -1, self.SIZE)) % self.SIZE
self._den = 1
return self._num
def sqrt(self) -> Self | None:
"""Compute the square root of a field element if it exists (None otherwise)."""
raise NotImplementedError
def is_square(self) -> bool:
"""Determine if this field element has a square root."""
# A more efficient algorithm is possible here (Jacobi symbol).
return self.sqrt() is not None
def is_even(self) -> bool:
"""Determine whether this field element, represented as integer in 0..SIZE-1, is even."""
return int(self) & 1 == 0
def __eq__(self, a: object) -> bool:
"""Check whether two field elements are equal (second may be an int)."""
if isinstance(a, type(self)):
return (self._num * a._den - self._den * a._num) % self.SIZE == 0
elif isinstance(a, int):
return (self._num - self._den * a) % self.SIZE == 0
return False # for other types
def to_bytes(self) -> bytes:
"""Convert a field element to a 32-byte array (BE byte order)."""
return int(self).to_bytes(32, 'big')
@classmethod
def from_int_checked(cls, v: int) -> Self:
"""Convert an integer to a field element (no overflow allowed)."""
if v >= cls.SIZE:
raise ValueError
return cls(v)
@classmethod
def from_int_wrapping(cls, v: int) -> Self:
"""Convert an integer to a field element (reduced modulo SIZE)."""
return cls(v % cls.SIZE)
@classmethod
def from_bytes_checked(cls, b: bytes) -> Self:
"""Convert a 32-byte array to a field element (BE byte order, no overflow allowed)."""
v = int.from_bytes(b, 'big')
return cls.from_int_checked(v)
@classmethod
def from_bytes_wrapping(cls, b: bytes) -> Self:
"""Convert a 32-byte array to a field element (BE byte order, reduced modulo SIZE)."""
v = int.from_bytes(b, 'big')
return cls.from_int_wrapping(v)
def __str__(self) -> str:
"""Convert this field element to a 64 character hex string."""
return f"{int(self):064x}"
def __repr__(self) -> str:
"""Get a string representation of this field element."""
return f"{type(self).__qualname__}(0x{int(self):x})"
class FE(APrimeFE):
SIZE = 2**256 - 2**32 - 977
def sqrt(self) -> Self | None:
# Due to the fact that our modulus p is of the form (p % 4) == 3, the Tonelli-Shanks
# algorithm (https://en.wikipedia.org/wiki/Tonelli-Shanks_algorithm) is simply
# raising the argument to the power (p + 1) / 4.
# To see why: (p-1) % 2 = 0, so 2 divides the order of the multiplicative group,
# and thus only half of the non-zero field elements are squares. An element a is
# a (nonzero) square when Euler's criterion, a^((p-1)/2) = 1 (mod p), holds. We're
# looking for x such that x^2 = a (mod p). Given a^((p-1)/2) = 1, that is equivalent
# to x^2 = a^(1 + (p-1)/2) mod p. As (1 + (p-1)/2) is even, this is equivalent to
# x = a^((1 + (p-1)/2)/2) mod p, or x = a^((p+1)/4) mod p.
v = int(self)
s = pow(v, (self.SIZE + 1) // 4, self.SIZE)
if s**2 % self.SIZE == v:
return type(self)(s)
return None
class Scalar(APrimeFE):
"""TODO Docstring"""
SIZE = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141
@classmethod
def from_int_nonzero_checked(cls, v: int) -> Self:
"""Convert an integer to a scalar (no zero or overflow allowed)."""
if not (0 < v < cls.SIZE):
raise ValueError
return cls(v)
@classmethod
def from_bytes_nonzero_checked(cls, b: bytes) -> Self:
"""Convert a 32-byte array to a scalar (BE byte order, no zero or overflow allowed)."""
v = int.from_bytes(b, 'big')
return cls.from_int_nonzero_checked(v)
class GE:
"""Objects of this class represent secp256k1 group elements (curve points or infinity)
GE objects are immutable.
Normal points on the curve have fields:
* x: the x coordinate (a field element)
* y: the y coordinate (a field element, satisfying y^2 = x^3 + 7)
* infinity: False
The point at infinity has field:
* infinity: True
"""
# TODO The following two class attributes should probably be just getters as
# classmethods to enforce immutability. Unfortunately Python makes it hard
# to create "classproperties". `G` could then also be just a classmethod.
# Order of the group (number of points on the curve, plus 1 for infinity)
ORDER = Scalar.SIZE
# Number of valid distinct x coordinates on the curve.
ORDER_HALF = ORDER // 2
@property
def infinity(self) -> bool:
"""Whether the group element is the point at infinity."""
return self._infinity
@property
def x(self) -> FE:
"""The x coordinate (a field element) of a non-infinite group element."""
assert not self.infinity
return self._x
@property
def y(self) -> FE:
"""The y coordinate (a field element) of a non-infinite group element."""
assert not self.infinity
return self._y
def __init__(self, x: int | FE | None = None, y: int | FE | None = None) -> None:
"""Initialize a group element with specified x and y coordinates, or infinity."""
if x is None:
# Initialize as infinity.
assert y is None
self._infinity = True
else:
# Initialize as point on the curve (and check that it is).
assert x is not None
assert y is not None
fx = FE(x)
fy = FE(y)
assert fy**2 == fx**3 + 7
self._infinity = False
self._x = fx
self._y = fy
def __add__(self, a: GE) -> GE:
"""Add two group elements together."""
# Deal with infinity: a + infinity == infinity + a == a.
if self.infinity:
return a
if a.infinity:
return self
if self.x == a.x:
if self.y != a.y:
# A point added to its own negation is infinity.
assert self.y + a.y == 0
return GE()
else:
# For identical inputs, use the tangent (doubling formula).
lam = (3 * self.x**2) / (2 * self.y)
else:
# For distinct inputs, use the line through both points (adding formula).
lam = (self.y - a.y) / (self.x - a.x)
# Determine point opposite to the intersection of that line with the curve.
x = lam**2 - (self.x + a.x)
y = lam * (self.x - x) - self.y
return GE(x, y)
@staticmethod
def sum(*ps: GE) -> GE:
"""Compute the sum of group elements.
GE.sum(a, b, c, ...) is identical to (GE() + a + b + c + ...)."""
return sum(ps, start=GE())
@staticmethod
def batch_mul(*aps: tuple[Scalar, GE]) -> GE:
"""Compute a (batch) scalar group element multiplication.
GE.batch_mul((a1, p1), (a2, p2), (a3, p3)) is identical to a1*p1 + a2*p2 + a3*p3,
but more efficient."""
# Reduce all the scalars modulo order first (so we can deal with negatives etc).
naps = [(int(a), p) for a, p in aps]
# Start with point at infinity.
r = GE()
# Iterate over all bit positions, from high to low.
for i in range(255, -1, -1):
# Double what we have so far.
r = r + r
# Add then add the points for which the corresponding scalar bit is set.
for (a, p) in naps:
if (a >> i) & 1:
r += p
return r
def __rmul__(self, a: int | Scalar) -> GE:
"""Multiply an integer or scalar with a group element."""
if self == G:
return FAST_G.mul(Scalar(a))
return GE.batch_mul((Scalar(a), self))
def __neg__(self) -> GE:
"""Compute the negation of a group element."""
if self.infinity:
return self
return GE(self.x, -self.y)
def __sub__(self, a: GE) -> GE:
"""Subtract a group element from another."""
return self + (-a)
def __eq__(self, a: object) -> bool:
"""Check if two group elements are equal."""
if not isinstance(a, type(self)):
return False
return (self - a).infinity
def has_even_y(self) -> bool:
"""Determine whether a non-infinity group element has an even y coordinate."""
assert not self.infinity
return self.y.is_even()
def to_bytes_compressed(self) -> bytes:
"""Convert a non-infinite group element to 33-byte compressed encoding."""
assert not self.infinity
return bytes([3 - self.y.is_even()]) + self.x.to_bytes()
def to_bytes_compressed_with_infinity(self) -> bytes:
"""Convert a group element to 33-byte compressed encoding, mapping infinity to zeros."""
if self.infinity:
return 33 * b"\x00"
return self.to_bytes_compressed()
def to_bytes_uncompressed(self) -> bytes:
"""Convert a non-infinite group element to 65-byte uncompressed encoding."""
assert not self.infinity
return b'\x04' + self.x.to_bytes() + self.y.to_bytes()
def to_bytes_xonly(self) -> bytes:
"""Convert (the x coordinate of) a non-infinite group element to 32-byte xonly encoding."""
assert not self.infinity
return self.x.to_bytes()
@staticmethod
def lift_x(x: int | FE) -> GE:
"""Return group element with specified field element as x coordinate (and even y)."""
y = (FE(x)**3 + 7).sqrt()
if y is None:
raise ValueError
if not y.is_even():
y = -y
return GE(x, y)
@staticmethod
def from_bytes_compressed(b: bytes) -> GE:
"""Convert a compressed to a group element."""
assert len(b) == 33
if b[0] != 2 and b[0] != 3:
raise ValueError
x = FE.from_bytes_checked(b[1:])
r = GE.lift_x(x)
if b[0] == 3:
r = -r
return r
@staticmethod
def from_bytes_compressed_with_infinity(b: bytes) -> GE:
"""Convert a compressed to a group element, mapping zeros to infinity."""
if b == 33 * b"\x00":
return GE()
else:
return GE.from_bytes_compressed(b)
@staticmethod
def from_bytes_uncompressed(b: bytes) -> GE:
"""Convert an uncompressed to a group element."""
assert len(b) == 65
if b[0] != 4:
raise ValueError
x = FE.from_bytes_checked(b[1:33])
y = FE.from_bytes_checked(b[33:])
if y**2 != x**3 + 7:
raise ValueError
return GE(x, y)
@staticmethod
def from_bytes(b: bytes) -> GE:
"""Convert a compressed or uncompressed encoding to a group element."""
assert len(b) in (33, 65)
if len(b) == 33:
return GE.from_bytes_compressed(b)
else:
return GE.from_bytes_uncompressed(b)
@staticmethod
def from_bytes_xonly(b: bytes) -> GE:
"""Convert a point given in xonly encoding to a group element."""
assert len(b) == 32
x = FE.from_bytes_checked(b)
r = GE.lift_x(x)
return r
@staticmethod
def is_valid_x(x: int | FE) -> bool:
"""Determine whether the provided field element is a valid X coordinate."""
return (FE(x)**3 + 7).is_square()
def __str__(self) -> str:
"""Convert this group element to a string."""
if self.infinity:
return "(inf)"
return f"({self.x},{self.y})"
def __repr__(self) -> str:
"""Get a string representation for this group element."""
if self.infinity:
return "GE()"
return f"GE(0x{int(self.x):x},0x{int(self.y):x})"
def __hash__(self) -> int:
"""Compute a non-cryptographic hash of the group element."""
if self.infinity:
return 0 # 0 is not a valid x coordinate
return int(self.x)
# The secp256k1 generator point
G = GE.lift_x(0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798)
class FastGEMul:
"""Table for fast multiplication with a constant group element.
Speed up scalar multiplication with a fixed point P by using a precomputed lookup table with
its powers of 2:
table = [P, 2*P, 4*P, (2^3)*P, (2^4)*P, ..., (2^255)*P]
During multiplication, the points corresponding to each bit set in the scalar are added up,
i.e. on average ~128 point additions take place.
"""
def __init__(self, p: GE) -> None:
self.table: list[GE] = [p] # table[i] = (2^i) * p
for _ in range(255):
p = p + p
self.table.append(p)
def mul(self, a: Scalar | int) -> GE:
result = GE()
a_ = int(a)
for bit in range(a_.bit_length()):
if a_ & (1 << bit):
result += self.table[bit]
return result
# Precomputed table with multiples of G for fast multiplication
FAST_G = FastGEMul(G)
@@ -0,0 +1,24 @@
import hashlib
# This implementation can be sped up by storing the midstate after hashing
# tag_hash instead of rehashing it all the time.
def tagged_hash(tag: str, msg: bytes) -> bytes:
tag_hash = hashlib.sha256(tag.encode()).digest()
return hashlib.sha256(tag_hash + tag_hash + msg).digest()
def bytes_from_int(x: int) -> bytes:
return x.to_bytes(32, byteorder="big")
def xor_bytes(b0: bytes, b1: bytes) -> bytes:
return bytes(x ^ y for (x, y) in zip(b0, b1))
def int_from_bytes(b: bytes) -> int:
return int.from_bytes(b, byteorder="big")
def hash_sha256(b: bytes) -> bytes:
return hashlib.sha256(b).digest()
+5
View File
@@ -0,0 +1,5 @@
from pathlib import Path
import sys
# Ensure secp256k1lab is found and can be imported directly
sys.path.insert(0, str(Path(__file__).parent / "../src/"))
+51
View File
@@ -0,0 +1,51 @@
import csv
from pathlib import Path
from random import randbytes
import unittest
from secp256k1lab.bip340 import pubkey_gen, schnorr_sign, schnorr_verify
class BIP340Tests(unittest.TestCase):
"""Test schnorr signatures (BIP 340)."""
def test_correctness(self):
seckey = randbytes(32)
pubkey_xonly = pubkey_gen(seckey)
aux_rand = randbytes(32)
message = b'this is some arbitrary message'
signature = schnorr_sign(message, seckey, aux_rand)
success = schnorr_verify(message, pubkey_xonly, signature)
self.assertTrue(success)
def test_vectors(self):
# Test against vectors from the BIPs repository
# [https://github.com/bitcoin/bips/blob/master/bip-0340/test-vectors.csv]
vectors_file = Path(__file__).parent / "vectors" / "bip340.csv"
with open(vectors_file, encoding='utf8') as csvfile:
reader = csv.DictReader(csvfile)
for row in reader:
with self.subTest(i=int(row['index'])):
self.subtest_vectors_case(row)
def subtest_vectors_case(self, row):
seckey = bytes.fromhex(row['secret key'])
pubkey_xonly = bytes.fromhex(row['public key'])
aux_rand = bytes.fromhex(row['aux_rand'])
msg = bytes.fromhex(row['message'])
sig = bytes.fromhex(row['signature'])
result_str = row['verification result']
comment = row['comment']
result = result_str == 'TRUE'
assert result or result_str == 'FALSE'
if seckey != b'':
pubkey_xonly_actual = pubkey_gen(seckey)
self.assertEqual(pubkey_xonly.hex(), pubkey_xonly_actual.hex(), f"BIP340 test vector ({comment}): pubkey mismatch")
sig_actual = schnorr_sign(msg, seckey, aux_rand)
self.assertEqual(sig.hex(), sig_actual.hex(), f"BIP340 test vector ({comment}): sig mismatch")
result_actual = schnorr_verify(msg, pubkey_xonly, sig)
if result:
self.assertEqual(result, result_actual, f"BIP340 test vector ({comment}): verification failed unexpectedly")
else:
self.assertEqual(result, result_actual, f"BIP340 test vector ({comment}): verification succeeded unexpectedly")
+18
View File
@@ -0,0 +1,18 @@
from random import randbytes
import unittest
from secp256k1lab.ecdh import ecdh_libsecp256k1
from secp256k1lab.keys import pubkey_gen_plain
class ECDHTests(unittest.TestCase):
"""Test ECDH module."""
def test_correctness(self):
seckey_alice = randbytes(32)
pubkey_alice = pubkey_gen_plain(seckey_alice)
seckey_bob = randbytes(32)
pubkey_bob = pubkey_gen_plain(seckey_bob)
shared_secret1 = ecdh_libsecp256k1(seckey_alice, pubkey_bob)
shared_secret2 = ecdh_libsecp256k1(seckey_bob, pubkey_alice)
self.assertEqual(shared_secret1, shared_secret2)
@@ -0,0 +1,180 @@
"""Test low-level secp256k1 field and group arithmetic classes."""
from random import randint
import unittest
from secp256k1lab.secp256k1 import FE, G, GE, Scalar
class PrimeFieldTests(unittest.TestCase):
def test_fe_constructors(self):
P = FE.SIZE
random_fe_valid = randint(0, P-1)
random_fe_overflowing = randint(P, 2**256-1)
# wrapping constructors
for init_value in [0, P-1, P, P+1, random_fe_valid, random_fe_overflowing]:
fe1 = FE(init_value)
fe2 = FE.from_int_wrapping(init_value)
fe3 = FE.from_bytes_wrapping(init_value.to_bytes(32, 'big'))
reduced_value = init_value % P
self.assertEqual(int(fe1), reduced_value)
self.assertEqual(int(fe1), int(fe2))
self.assertEqual(int(fe2), int(fe3))
# checking constructors (should throw on overflow)
for valid_value in [0, P-1, random_fe_valid]:
fe1 = FE.from_int_checked(valid_value)
fe2 = FE.from_bytes_checked(valid_value.to_bytes(32, 'big'))
self.assertEqual(int(fe1), valid_value)
self.assertEqual(int(fe1), int(fe2))
for overflow_value in [P, P+1, random_fe_overflowing]:
with self.assertRaises(ValueError):
_ = FE.from_int_checked(overflow_value)
with self.assertRaises(ValueError):
_ = FE.from_bytes_checked(overflow_value.to_bytes(32, 'big'))
def test_scalar_constructors(self):
N = Scalar.SIZE
random_scalar_valid = randint(0, N-1)
random_scalar_overflowing = randint(N, 2**256-1)
# wrapping constructors
for init_value in [0, N-1, N, N+1, random_scalar_valid, random_scalar_overflowing]:
s1 = Scalar(init_value)
s2 = Scalar.from_int_wrapping(init_value)
s3 = Scalar.from_bytes_wrapping(init_value.to_bytes(32, 'big'))
reduced_value = init_value % N
self.assertEqual(int(s1), reduced_value)
self.assertEqual(int(s1), int(s2))
self.assertEqual(int(s2), int(s3))
# checking constructors (should throw on overflow)
for valid_value in [0, N-1, random_scalar_valid]:
s1 = Scalar.from_int_checked(valid_value)
s2 = Scalar.from_bytes_checked(valid_value.to_bytes(32, 'big'))
self.assertEqual(int(s1), valid_value)
self.assertEqual(int(s1), int(s2))
for overflow_value in [N, N+1, random_scalar_overflowing]:
with self.assertRaises(ValueError):
_ = Scalar.from_int_checked(overflow_value)
with self.assertRaises(ValueError):
_ = Scalar.from_bytes_checked(overflow_value.to_bytes(32, 'big'))
# non-zero checking constructors (should throw on zero or overflow, only for Scalar)
random_nonzero_scalar_valid = randint(1, N-1)
for valid_value in [1, N-1, random_nonzero_scalar_valid]:
s1 = Scalar.from_int_nonzero_checked(valid_value)
s2 = Scalar.from_bytes_nonzero_checked(valid_value.to_bytes(32, 'big'))
self.assertEqual(int(s1), valid_value)
self.assertEqual(int(s1), int(s2))
for invalid_value in [0, N, random_scalar_overflowing]:
with self.assertRaises(ValueError):
_ = Scalar.from_int_nonzero_checked(invalid_value)
with self.assertRaises(ValueError):
_ = Scalar.from_bytes_nonzero_checked(invalid_value.to_bytes(32, 'big'))
class GeSerializationTests(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.point_at_infinity = GE()
cls.group_elements_on_curve = [
# generator point
G,
# Bitcoin genesis block public key
GE(0x678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb6,
0x49f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f),
]
# generate a few random points, to likely cover both even/odd y polarity
cls.group_elements_on_curve.extend([randint(1, Scalar.SIZE-1) * G for _ in range(8)])
# generate x coordinates that don't have a valid point on the curve
# (note that ~50% of all x coordinates are valid, so finding one needs two loop iterations on average)
cls.x_coords_not_on_curve = []
while len(cls.x_coords_not_on_curve) < 8:
x = randint(0, FE.SIZE-1)
if not GE.is_valid_x(x):
cls.x_coords_not_on_curve.append(x)
cls.group_elements = [cls.point_at_infinity] + cls.group_elements_on_curve
def test_infinity_raises(self):
with self.assertRaises(AssertionError):
_ = self.point_at_infinity.to_bytes_uncompressed()
with self.assertRaises(AssertionError):
_ = self.point_at_infinity.to_bytes_compressed()
with self.assertRaises(AssertionError):
_ = self.point_at_infinity.to_bytes_xonly()
def test_not_on_curve_raises(self):
# for compressed and x-only GE deserialization, test with invalid x coordinate
for x in self.x_coords_not_on_curve:
x_bytes = x.to_bytes(32, 'big')
with self.assertRaises(ValueError):
_ = GE.from_bytes_compressed(b'\x02' + x_bytes)
with self.assertRaises(ValueError):
_ = GE.from_bytes_compressed(b'\x03' + x_bytes)
with self.assertRaises(ValueError):
_ = GE.from_bytes_compressed_with_infinity(b'\x02' + x_bytes)
with self.assertRaises(ValueError):
_ = GE.from_bytes_compressed_with_infinity(b'\x03' + x_bytes)
with self.assertRaises(ValueError):
_ = GE.from_bytes_xonly(x_bytes)
# for uncompressed GE serialization, test by invalidating either coordinate
for ge in self.group_elements_on_curve:
valid_x = ge.x
valid_y = ge.y
invalid_x = ge.x + 1
invalid_y = ge.y + 1
# valid cases (if point (x,y) is on the curve, then point(x,-y) is on the curve as well)
_ = GE.from_bytes_uncompressed(b'\x04' + valid_x.to_bytes() + valid_y.to_bytes())
_ = GE.from_bytes_uncompressed(b'\x04' + valid_x.to_bytes() + (-valid_y).to_bytes())
# invalid cases (curve equation y**2 = x**3 + 7 doesn't hold)
self.assertNotEqual(invalid_y**2, valid_x**3 + 7)
with self.assertRaises(ValueError):
_ = GE.from_bytes_uncompressed(b'\x04' + valid_x.to_bytes() + invalid_y.to_bytes())
self.assertNotEqual(valid_y**2, invalid_x**3 + 7)
with self.assertRaises(ValueError):
_ = GE.from_bytes_uncompressed(b'\x04' + invalid_x.to_bytes() + valid_y.to_bytes())
def test_affine(self):
# GE serialization and parsing round-trip (variants that only support serializing points on the curve)
for ge_orig in self.group_elements_on_curve:
# uncompressed serialization: 65 bytes, starts with 0x04
ge_ser = ge_orig.to_bytes_uncompressed()
self.assertEqual(len(ge_ser), 65)
self.assertEqual(ge_ser[0], 0x04)
ge_deser = GE.from_bytes_uncompressed(ge_ser)
self.assertEqual(ge_deser, ge_orig)
# compressed serialization: 33 bytes, starts with 0x02 (if y is even) or 0x03 (if y is odd)
ge_ser = ge_orig.to_bytes_compressed()
self.assertEqual(len(ge_ser), 33)
self.assertEqual(ge_ser[0], 0x02 if ge_orig.has_even_y() else 0x03)
ge_deser = GE.from_bytes_compressed(ge_ser)
self.assertEqual(ge_deser, ge_orig)
# x-only serialization: 32 bytes
ge_ser = ge_orig.to_bytes_xonly()
self.assertEqual(len(ge_ser), 32)
ge_deser = GE.from_bytes_xonly(ge_ser)
if not ge_orig.has_even_y(): # x-only implies even y, so flip if necessary
ge_deser = -ge_deser
self.assertEqual(ge_deser, ge_orig)
def test_affine_with_infinity(self):
# GE serialization and parsing round-trip (variants that also support serializing the point at infinity)
for ge_orig in self.group_elements:
# compressed serialization: 33 bytes, all-zeros for point at infinity
ge_ser = ge_orig.to_bytes_compressed_with_infinity()
self.assertEqual(len(ge_ser), 33)
if ge_orig.infinity:
self.assertEqual(ge_ser, b'\x00'*33)
else:
self.assertEqual(ge_ser[0], 0x02 if ge_orig.has_even_y() else 0x03)
ge_deser = GE.from_bytes_compressed_with_infinity(ge_ser)
self.assertEqual(ge_deser, ge_orig)
@@ -0,0 +1,20 @@
index,secret key,public key,aux_rand,message,signature,verification result,comment
0,0000000000000000000000000000000000000000000000000000000000000003,F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9,0000000000000000000000000000000000000000000000000000000000000000,0000000000000000000000000000000000000000000000000000000000000000,E907831F80848D1069A5371B402410364BDF1C5F8307B0084C55F1CE2DCA821525F66A4A85EA8B71E482A74F382D2CE5EBEEE8FDB2172F477DF4900D310536C0,TRUE,
1,B7E151628AED2A6ABF7158809CF4F3C762E7160F38B4DA56A784D9045190CFEF,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,0000000000000000000000000000000000000000000000000000000000000001,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,6896BD60EEAE296DB48A229FF71DFE071BDE413E6D43F917DC8DCF8C78DE33418906D11AC976ABCCB20B091292BFF4EA897EFCB639EA871CFA95F6DE339E4B0A,TRUE,
2,C90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B14E5C9,DD308AFEC5777E13121FA72B9CC1B7CC0139715309B086C960E18FD969774EB8,C87AA53824B4D7AE2EB035A2B5BBBCCC080E76CDC6D1692C4B0B62D798E6D906,7E2D58D8B3BCDF1ABADEC7829054F90DDA9805AAB56C77333024B9D0A508B75C,5831AAEED7B44BB74E5EAB94BA9D4294C49BCF2A60728D8B4C200F50DD313C1BAB745879A5AD954A72C45A91C3A51D3C7ADEA98D82F8481E0E1E03674A6F3FB7,TRUE,
3,0B432B2677937381AEF05BB02A66ECD012773062CF3FA2549E44F58ED2401710,25D1DFF95105F5253C4022F628A996AD3A0D95FBF21D468A1B33F8C160D8F517,FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF,FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF,7EB0509757E246F19449885651611CB965ECC1A187DD51B64FDA1EDC9637D5EC97582B9CB13DB3933705B32BA982AF5AF25FD78881EBB32771FC5922EFC66EA3,TRUE,test fails if msg is reduced modulo p or n
4,,D69C3509BB99E412E68B0FE8544E72837DFA30746D8BE2AA65975F29D22DC7B9,,4DF3C3F68FCC83B27E9D42C90431A72499F17875C81A599B566C9889B9696703,00000000000000000000003B78CE563F89A0ED9414F5AA28AD0D96D6795F9C6376AFB1548AF603B3EB45C9F8207DEE1060CB71C04E80F593060B07D28308D7F4,TRUE,
5,,EEFDEA4CDB677750A420FEE807EACF21EB9898AE79B9768766E4FAA04A2D4A34,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E17776969E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B,FALSE,public key not on the curve
6,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,FFF97BD5755EEEA420453A14355235D382F6472F8568A18B2F057A14602975563CC27944640AC607CD107AE10923D9EF7A73C643E166BE5EBEAFA34B1AC553E2,FALSE,has_even_y(R) is false
7,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,1FA62E331EDBC21C394792D2AB1100A7B432B013DF3F6FF4F99FCB33E0E1515F28890B3EDB6E7189B630448B515CE4F8622A954CFE545735AAEA5134FCCDB2BD,FALSE,negated message
8,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E177769961764B3AA9B2FFCB6EF947B6887A226E8D7C93E00C5ED0C1834FF0D0C2E6DA6,FALSE,negated s value
9,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,0000000000000000000000000000000000000000000000000000000000000000123DDA8328AF9C23A94C1FEECFD123BA4FB73476F0D594DCB65C6425BD186051,FALSE,sG - eP is infinite. Test fails in single verification if has_even_y(inf) is defined as true and x(inf) as 0
10,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,00000000000000000000000000000000000000000000000000000000000000017615FBAF5AE28864013C099742DEADB4DBA87F11AC6754F93780D5A1837CF197,FALSE,sG - eP is infinite. Test fails in single verification if has_even_y(inf) is defined as true and x(inf) as 1
11,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,4A298DACAE57395A15D0795DDBFD1DCB564DA82B0F269BC70A74F8220429BA1D69E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B,FALSE,sig[0:32] is not an X coordinate on the curve
12,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F69E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B,FALSE,sig[0:32] is equal to field size
13,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E177769FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141,FALSE,sig[32:64] is equal to curve order
14,,FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC30,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E17776969E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B,FALSE,public key is not a valid X coordinate because it exceeds the field size
15,0340034003400340034003400340034003400340034003400340034003400340,778CAA53B4393AC467774D09497A87224BF9FAB6F6E68B23086497324D6FD117,0000000000000000000000000000000000000000000000000000000000000000,,71535DB165ECD9FBBC046E5FFAEA61186BB6AD436732FCCC25291A55895464CF6069CE26BF03466228F19A3A62DB8A649F2D560FAC652827D1AF0574E427AB63,TRUE,message of size 0 (added 2022-12)
16,0340034003400340034003400340034003400340034003400340034003400340,778CAA53B4393AC467774D09497A87224BF9FAB6F6E68B23086497324D6FD117,0000000000000000000000000000000000000000000000000000000000000000,11,08A20A0AFEF64124649232E0693C583AB1B9934AE63B4C3511F3AE1134C6A303EA3173BFEA6683BD101FA5AA5DBC1996FE7CACFC5A577D33EC14564CEC2BACBF,TRUE,message of size 1 (added 2022-12)
17,0340034003400340034003400340034003400340034003400340034003400340,778CAA53B4393AC467774D09497A87224BF9FAB6F6E68B23086497324D6FD117,0000000000000000000000000000000000000000000000000000000000000000,0102030405060708090A0B0C0D0E0F1011,5130F39A4059B43BC7CAC09A19ECE52B5D8699D1A71E3C52DA9AFDB6B50AC370C4A482B77BF960F8681540E25B6771ECE1E5A37FD80E5A51897C5566A97EA5A5,TRUE,message of size 17 (added 2022-12)
18,0340034003400340034003400340034003400340034003400340034003400340,778CAA53B4393AC467774D09497A87224BF9FAB6F6E68B23086497324D6FD117,0000000000000000000000000000000000000000000000000000000000000000,99999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999,403B12B0D8555A344175EA7EC746566303321E5DBFA8BE6F091635163ECA79A8585ED3E3170807E7C03B720FC54C7B23897FCBA0E9D0B4A06894CFD249F22367,TRUE,message of size 100 (added 2022-12)
1 index secret key public key aux_rand message signature verification result comment
2 0 0000000000000000000000000000000000000000000000000000000000000003 F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9 0000000000000000000000000000000000000000000000000000000000000000 0000000000000000000000000000000000000000000000000000000000000000 E907831F80848D1069A5371B402410364BDF1C5F8307B0084C55F1CE2DCA821525F66A4A85EA8B71E482A74F382D2CE5EBEEE8FDB2172F477DF4900D310536C0 TRUE
3 1 B7E151628AED2A6ABF7158809CF4F3C762E7160F38B4DA56A784D9045190CFEF DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659 0000000000000000000000000000000000000000000000000000000000000001 243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89 6896BD60EEAE296DB48A229FF71DFE071BDE413E6D43F917DC8DCF8C78DE33418906D11AC976ABCCB20B091292BFF4EA897EFCB639EA871CFA95F6DE339E4B0A TRUE
4 2 C90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B14E5C9 DD308AFEC5777E13121FA72B9CC1B7CC0139715309B086C960E18FD969774EB8 C87AA53824B4D7AE2EB035A2B5BBBCCC080E76CDC6D1692C4B0B62D798E6D906 7E2D58D8B3BCDF1ABADEC7829054F90DDA9805AAB56C77333024B9D0A508B75C 5831AAEED7B44BB74E5EAB94BA9D4294C49BCF2A60728D8B4C200F50DD313C1BAB745879A5AD954A72C45A91C3A51D3C7ADEA98D82F8481E0E1E03674A6F3FB7 TRUE
5 3 0B432B2677937381AEF05BB02A66ECD012773062CF3FA2549E44F58ED2401710 25D1DFF95105F5253C4022F628A996AD3A0D95FBF21D468A1B33F8C160D8F517 FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF 7EB0509757E246F19449885651611CB965ECC1A187DD51B64FDA1EDC9637D5EC97582B9CB13DB3933705B32BA982AF5AF25FD78881EBB32771FC5922EFC66EA3 TRUE test fails if msg is reduced modulo p or n
6 4 D69C3509BB99E412E68B0FE8544E72837DFA30746D8BE2AA65975F29D22DC7B9 4DF3C3F68FCC83B27E9D42C90431A72499F17875C81A599B566C9889B9696703 00000000000000000000003B78CE563F89A0ED9414F5AA28AD0D96D6795F9C6376AFB1548AF603B3EB45C9F8207DEE1060CB71C04E80F593060B07D28308D7F4 TRUE
7 5 EEFDEA4CDB677750A420FEE807EACF21EB9898AE79B9768766E4FAA04A2D4A34 243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89 6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E17776969E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B FALSE public key not on the curve
8 6 DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659 243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89 FFF97BD5755EEEA420453A14355235D382F6472F8568A18B2F057A14602975563CC27944640AC607CD107AE10923D9EF7A73C643E166BE5EBEAFA34B1AC553E2 FALSE has_even_y(R) is false
9 7 DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659 243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89 1FA62E331EDBC21C394792D2AB1100A7B432B013DF3F6FF4F99FCB33E0E1515F28890B3EDB6E7189B630448B515CE4F8622A954CFE545735AAEA5134FCCDB2BD FALSE negated message
10 8 DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659 243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89 6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E177769961764B3AA9B2FFCB6EF947B6887A226E8D7C93E00C5ED0C1834FF0D0C2E6DA6 FALSE negated s value
11 9 DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659 243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89 0000000000000000000000000000000000000000000000000000000000000000123DDA8328AF9C23A94C1FEECFD123BA4FB73476F0D594DCB65C6425BD186051 FALSE sG - eP is infinite. Test fails in single verification if has_even_y(inf) is defined as true and x(inf) as 0
12 10 DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659 243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89 00000000000000000000000000000000000000000000000000000000000000017615FBAF5AE28864013C099742DEADB4DBA87F11AC6754F93780D5A1837CF197 FALSE sG - eP is infinite. Test fails in single verification if has_even_y(inf) is defined as true and x(inf) as 1
13 11 DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659 243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89 4A298DACAE57395A15D0795DDBFD1DCB564DA82B0F269BC70A74F8220429BA1D69E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B FALSE sig[0:32] is not an X coordinate on the curve
14 12 DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659 243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89 FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F69E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B FALSE sig[0:32] is equal to field size
15 13 DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659 243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89 6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E177769FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 FALSE sig[32:64] is equal to curve order
16 14 FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC30 243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89 6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E17776969E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B FALSE public key is not a valid X coordinate because it exceeds the field size
17 15 0340034003400340034003400340034003400340034003400340034003400340 778CAA53B4393AC467774D09497A87224BF9FAB6F6E68B23086497324D6FD117 0000000000000000000000000000000000000000000000000000000000000000 71535DB165ECD9FBBC046E5FFAEA61186BB6AD436732FCCC25291A55895464CF6069CE26BF03466228F19A3A62DB8A649F2D560FAC652827D1AF0574E427AB63 TRUE message of size 0 (added 2022-12)
18 16 0340034003400340034003400340034003400340034003400340034003400340 778CAA53B4393AC467774D09497A87224BF9FAB6F6E68B23086497324D6FD117 0000000000000000000000000000000000000000000000000000000000000000 11 08A20A0AFEF64124649232E0693C583AB1B9934AE63B4C3511F3AE1134C6A303EA3173BFEA6683BD101FA5AA5DBC1996FE7CACFC5A577D33EC14564CEC2BACBF TRUE message of size 1 (added 2022-12)
19 17 0340034003400340034003400340034003400340034003400340034003400340 778CAA53B4393AC467774D09497A87224BF9FAB6F6E68B23086497324D6FD117 0000000000000000000000000000000000000000000000000000000000000000 0102030405060708090A0B0C0D0E0F1011 5130F39A4059B43BC7CAC09A19ECE52B5D8699D1A71E3C52DA9AFDB6B50AC370C4A482B77BF960F8681540E25B6771ECE1E5A37FD80E5A51897C5566A97EA5A5 TRUE message of size 17 (added 2022-12)
20 18 0340034003400340034003400340034003400340034003400340034003400340 778CAA53B4393AC467774D09497A87224BF9FAB6F6E68B23086497324D6FD117 0000000000000000000000000000000000000000000000000000000000000000 99999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 403B12B0D8555A344175EA7EC746566303321E5DBFA8BE6F091635163ECA79A8585ED3E3170807E7C03B720FC54C7B23897FCBA0E9D0B4A06894CFD249F22367 TRUE message of size 100 (added 2022-12)
@@ -0,0 +1,8 @@
index,pubnonces,expected_result,expected_aggnonce,comment
0,024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D076602531FE6068134503D2723133227C867AC8FA6C83C537E9A44C3C5BDBDCB1FE337;0362C0A046DACCE86DDD0343C6D3C7C79C2208BA0D9C9CF24A6D046D21D21F90F703F006A18D5653C4EDF5391FF23A61F03FF83D237E880EE61187FA9F379A028E0A,TRUE,02989C0B76CB563971FDC9BEF31EC06C3560F3249D6EE9E5D83C57625596E05F6F0256B328B30C8BF5839E24058747879408BDB36241DC9C2E7C619FAA12B2920967,Two signers
1,024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D076602531FE6068134503D2723133227C867AC8FA6C83C537E9A44C3C5BDBDCB1FE337;0362C0A046DACCE86DDD0343C6D3C7C79C2208BA0D9C9CF24A6D046D21D21F90F703F006A18D5653C4EDF5391FF23A61F03FF83D237E880EE61187FA9F379A028E0A;03F991F944D1E1954A7FC8B9BF62E0D78F015F4C07762D505E20E6C45260A3661B0256B328B30C8BF5839E24058747879408BDB36241DC9C2E7C619FAA12B2920967,TRUE,021A7A569E91DBF60581509C7FC946D1003B60C7DEE85299538DB6353538D59574036360E856310CE5D294E8BE33FC807077DC56AC80D95D9CD4DDBD21325EFF73F7,Three signers
2,024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D076602531FE6068134503D2723133227C867AC8FA6C83C537E9A44C3C5BDBDCB1FE337;0462C0A046DACCE86DDD0343C6D3C7C79C2208BA0D9C9CF24A6D046D21D21F90F703F006A18D5653C4EDF5391FF23A61F03FF83D237E880EE61187FA9F379A028E0A,FALSE,,Signer 1's pubnonce first half has prefix 0x04
3,024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D0766020000000000000000000000000000000000000000000000000000000000000000;0362C0A046DACCE86DDD0343C6D3C7C79C2208BA0D9C9CF24A6D046D21D21F90F703F006A18D5653C4EDF5391FF23A61F03FF83D237E880EE61187FA9F379A028E0A,FALSE,,Signer 0's pubnonce second half is not on the curve
4,024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D076602FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC30;0362C0A046DACCE86DDD0343C6D3C7C79C2208BA0D9C9CF24A6D046D21D21F90F703F006A18D5653C4EDF5391FF23A61F03FF83D237E880EE61187FA9F379A028E0A,FALSE,,Signer 0's pubnonce second half exceeds field size
5,024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D076602531FE6068134503D2723133227C867AC8FA6C83C537E9A44C3C5BDBDCB1FE337;034D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D076603F006A18D5653C4EDF5391FF23A61F03FF83D237E880EE61187FA9F379A028E0A,FALSE,,Aggregate nonce R1 is infinity
6,024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D076602531FE6068134503D2723133227C867AC8FA6C83C537E9A44C3C5BDBDCB1FE337;0362C0A046DACCE86DDD0343C6D3C7C79C2208BA0D9C9CF24A6D046D21D21F90F703531FE6068134503D2723133227C867AC8FA6C83C537E9A44C3C5BDBDCB1FE337,FALSE,,Aggregate nonce R2 is infinity
1 index pubnonces expected_result expected_aggnonce comment
2 0 024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D076602531FE6068134503D2723133227C867AC8FA6C83C537E9A44C3C5BDBDCB1FE337;0362C0A046DACCE86DDD0343C6D3C7C79C2208BA0D9C9CF24A6D046D21D21F90F703F006A18D5653C4EDF5391FF23A61F03FF83D237E880EE61187FA9F379A028E0A TRUE 02989C0B76CB563971FDC9BEF31EC06C3560F3249D6EE9E5D83C57625596E05F6F0256B328B30C8BF5839E24058747879408BDB36241DC9C2E7C619FAA12B2920967 Two signers
3 1 024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D076602531FE6068134503D2723133227C867AC8FA6C83C537E9A44C3C5BDBDCB1FE337;0362C0A046DACCE86DDD0343C6D3C7C79C2208BA0D9C9CF24A6D046D21D21F90F703F006A18D5653C4EDF5391FF23A61F03FF83D237E880EE61187FA9F379A028E0A;03F991F944D1E1954A7FC8B9BF62E0D78F015F4C07762D505E20E6C45260A3661B0256B328B30C8BF5839E24058747879408BDB36241DC9C2E7C619FAA12B2920967 TRUE 021A7A569E91DBF60581509C7FC946D1003B60C7DEE85299538DB6353538D59574036360E856310CE5D294E8BE33FC807077DC56AC80D95D9CD4DDBD21325EFF73F7 Three signers
4 2 024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D076602531FE6068134503D2723133227C867AC8FA6C83C537E9A44C3C5BDBDCB1FE337;0462C0A046DACCE86DDD0343C6D3C7C79C2208BA0D9C9CF24A6D046D21D21F90F703F006A18D5653C4EDF5391FF23A61F03FF83D237E880EE61187FA9F379A028E0A FALSE Signer 1's pubnonce first half has prefix 0x04
5 3 024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D0766020000000000000000000000000000000000000000000000000000000000000000;0362C0A046DACCE86DDD0343C6D3C7C79C2208BA0D9C9CF24A6D046D21D21F90F703F006A18D5653C4EDF5391FF23A61F03FF83D237E880EE61187FA9F379A028E0A FALSE Signer 0's pubnonce second half is not on the curve
6 4 024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D076602FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC30;0362C0A046DACCE86DDD0343C6D3C7C79C2208BA0D9C9CF24A6D046D21D21F90F703F006A18D5653C4EDF5391FF23A61F03FF83D237E880EE61187FA9F379A028E0A FALSE Signer 0's pubnonce second half exceeds field size
7 5 024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D076602531FE6068134503D2723133227C867AC8FA6C83C537E9A44C3C5BDBDCB1FE337;034D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D076603F006A18D5653C4EDF5391FF23A61F03FF83D237E880EE61187FA9F379A028E0A FALSE Aggregate nonce R1 is infinity
8 6 024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D076602531FE6068134503D2723133227C867AC8FA6C83C537E9A44C3C5BDBDCB1FE337;0362C0A046DACCE86DDD0343C6D3C7C79C2208BA0D9C9CF24A6D046D21D21F90F703531FE6068134503D2723133227C867AC8FA6C83C537E9A44C3C5BDBDCB1FE337 FALSE Aggregate nonce R2 is infinity
@@ -0,0 +1,5 @@
index,rand_,secret_key,extra_in,expected_secnonce,expected_pubnonce,comment
0,0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F,0202020202020202020202020202020202020202020202020202020202020202,0808080808080808080808080808080808080808080808080808080808080808,496C8F2EBAEAB2FD7189A9F217BE77ABA036B87CD947EDEFA001CF8D0BF452D55CB6CF68A9A528BBCB71DB15B150806ABB7E92E724FCEE78AEB5B9024EB34CE4,031E006EEDAE3D3820BE8C8EFCD4CE67715EE9B2AB2218CD6330AA1F025AB79FA803538EF9EDCC35E77A69C7C4720D3E6BE495CBA5E3D8ADAFAE7E0F5FFCABC6AC8A,Secret key and extra_in
1,0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F,0202020202020202020202020202020202020202020202020202020202020202,,94EB69603F7D12C1C511F187C8AC92B4DC61ED8E76CF42ACEFD9904C1720F46F7249D0E01A33C71BA9E14AE1CBC749C103A16D3E5DD7B172B4397E5E16180866,02E1DB4796E1D36F6CDD734CCAECB9D35530BAA46CBB0C33EBB689E6B5E6550B9F033FEC0D4E18F85C00F484DE0CFD867ED0F4258227F8739DC0F6033C920E8EDC48,Secret key and no extra_in
2,0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F,,0808080808080808080808080808080808080808080808080808080808080808,78BA95FD557F793BD321CC690E0C695E72B330201BFACC489CF0D42485C850AE2A5C817B42B11ADF4B96B2C94BCEBD9072426A2AC632C9B7BDF810CE5283A556,03C1829FEABAADD69DC5EEAE3778AF61AF82B56AE8D7F1CA980D95DAA6677FD75F038E523C53F4FDBF0F4AB9D0FBD615DE191D7ABD764B03355DCE7331D1F39A9E94,No secret key and extra_in
3,0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F,,,01E41FFC41183A6C0CC981D66F98CB0E460AC059F4F2B9AFA5EA9FA34636098B090315C8AFB605FFB1791365F90B8602E79D4E0D32FD6916F61A7F82ECA20132,027E7AAC81E61DF4F2BAEA16F5544592D2E556B7BFB034B7811064E3DAC46883F70382EDACAD18AF9ADF8395B4F3D1198F3FD5A7E9365177000D4769B92F6ECFFBEA,No secret key and no extra_in
1 index rand_ secret_key extra_in expected_secnonce expected_pubnonce comment
2 0 0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F 0202020202020202020202020202020202020202020202020202020202020202 0808080808080808080808080808080808080808080808080808080808080808 496C8F2EBAEAB2FD7189A9F217BE77ABA036B87CD947EDEFA001CF8D0BF452D55CB6CF68A9A528BBCB71DB15B150806ABB7E92E724FCEE78AEB5B9024EB34CE4 031E006EEDAE3D3820BE8C8EFCD4CE67715EE9B2AB2218CD6330AA1F025AB79FA803538EF9EDCC35E77A69C7C4720D3E6BE495CBA5E3D8ADAFAE7E0F5FFCABC6AC8A Secret key and extra_in
3 1 0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F 0202020202020202020202020202020202020202020202020202020202020202 94EB69603F7D12C1C511F187C8AC92B4DC61ED8E76CF42ACEFD9904C1720F46F7249D0E01A33C71BA9E14AE1CBC749C103A16D3E5DD7B172B4397E5E16180866 02E1DB4796E1D36F6CDD734CCAECB9D35530BAA46CBB0C33EBB689E6B5E6550B9F033FEC0D4E18F85C00F484DE0CFD867ED0F4258227F8739DC0F6033C920E8EDC48 Secret key and no extra_in
4 2 0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F 0808080808080808080808080808080808080808080808080808080808080808 78BA95FD557F793BD321CC690E0C695E72B330201BFACC489CF0D42485C850AE2A5C817B42B11ADF4B96B2C94BCEBD9072426A2AC632C9B7BDF810CE5283A556 03C1829FEABAADD69DC5EEAE3778AF61AF82B56AE8D7F1CA980D95DAA6677FD75F038E523C53F4FDBF0F4AB9D0FBD615DE191D7ABD764B03355DCE7331D1F39A9E94 No secret key and extra_in
5 3 0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F 01E41FFC41183A6C0CC981D66F98CB0E460AC059F4F2B9AFA5EA9FA34636098B090315C8AFB605FFB1791365F90B8602E79D4E0D32FD6916F61A7F82ECA20132 027E7AAC81E61DF4F2BAEA16F5544592D2E556B7BFB034B7811064E3DAC46883F70382EDACAD18AF9ADF8395B4F3D1198F3FD5A7E9365177000D4769B92F6ECFFBEA No secret key and no extra_in
@@ -0,0 +1,10 @@
index,signer_index,psig,pubkeys,messages,pubnonces,expected_result,comment
0,0,AC7EA4711D03C9C5F0D0CDD761B81FC27D18187655A75D37E4EE8879545756AA,1B84C5567B126440995D3ED5AABA0565D71E1834604819FF9C17F5E9D5DD078F;462779AD4AAD39514614751A71085F2F10E1C7A593E4E030EFB5B8721CE55B0B,8080808080808080808080808080808080808080808080808080808080808080;8181818181818181818181818181818181818181818181818181818181818181,024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D076602531FE6068134503D2723133227C867AC8FA6C83C537E9A44C3C5BDBDCB1FE337;0362C0A046DACCE86DDD0343C6D3C7C79C2208BA0D9C9CF24A6D046D21D21F90F703F006A18D5653C4EDF5391FF23A61F03FF83D237E880EE61187FA9F379A028E0A,TRUE,Honest partial sig (signer 0)
1,1,B83BBCAA5F0D6E0DEBEE0000E41B21D2E9304D0387660F96D3664735B0B954A3,1B84C5567B126440995D3ED5AABA0565D71E1834604819FF9C17F5E9D5DD078F;462779AD4AAD39514614751A71085F2F10E1C7A593E4E030EFB5B8721CE55B0B,8080808080808080808080808080808080808080808080808080808080808080;8181818181818181818181818181818181818181818181818181818181818181,024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D076602531FE6068134503D2723133227C867AC8FA6C83C537E9A44C3C5BDBDCB1FE337;0362C0A046DACCE86DDD0343C6D3C7C79C2208BA0D9C9CF24A6D046D21D21F90F703F006A18D5653C4EDF5391FF23A61F03FF83D237E880EE61187FA9F379A028E0A,TRUE,Honest partial sig (signer 1)
2,1,AC7EA4711D03C9C5F0D0CDD761B81FC27D18187655A75D37E4EE8879545756AA,1B84C5567B126440995D3ED5AABA0565D71E1834604819FF9C17F5E9D5DD078F;462779AD4AAD39514614751A71085F2F10E1C7A593E4E030EFB5B8721CE55B0B,8080808080808080808080808080808080808080808080808080808080808080;8181818181818181818181818181818181818181818181818181818181818181,024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D076602531FE6068134503D2723133227C867AC8FA6C83C537E9A44C3C5BDBDCB1FE337;0362C0A046DACCE86DDD0343C6D3C7C79C2208BA0D9C9CF24A6D046D21D21F90F703F006A18D5653C4EDF5391FF23A61F03FF83D237E880EE61187FA9F379A028E0A,FALSE,Honest psig verified at the wrong signer_index
3,0,AC7EA4711D03C9C5F0D0CDD761B81FC27D18187655A75D37E4EE8879545756AB,1B84C5567B126440995D3ED5AABA0565D71E1834604819FF9C17F5E9D5DD078F;462779AD4AAD39514614751A71085F2F10E1C7A593E4E030EFB5B8721CE55B0B,8080808080808080808080808080808080808080808080808080808080808080;8181818181818181818181818181818181818181818181818181818181818181,024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D076602531FE6068134503D2723133227C867AC8FA6C83C537E9A44C3C5BDBDCB1FE337;0362C0A046DACCE86DDD0343C6D3C7C79C2208BA0D9C9CF24A6D046D21D21F90F703F006A18D5653C4EDF5391FF23A61F03FF83D237E880EE61187FA9F379A028E0A,FALSE,Tampered partial sig (bit flip)
4,0,0000000000000000000000000000000000000000000000000000000000000000,1B84C5567B126440995D3ED5AABA0565D71E1834604819FF9C17F5E9D5DD078F;462779AD4AAD39514614751A71085F2F10E1C7A593E4E030EFB5B8721CE55B0B,8080808080808080808080808080808080808080808080808080808080808080;8181818181818181818181818181818181818181818181818181818181818181,024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D076602531FE6068134503D2723133227C867AC8FA6C83C537E9A44C3C5BDBDCB1FE337;0362C0A046DACCE86DDD0343C6D3C7C79C2208BA0D9C9CF24A6D046D21D21F90F703F006A18D5653C4EDF5391FF23A61F03FF83D237E880EE61187FA9F379A028E0A,FALSE,"psig = 0 (in range, wrong)"
5,0,FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141,1B84C5567B126440995D3ED5AABA0565D71E1834604819FF9C17F5E9D5DD078F;462779AD4AAD39514614751A71085F2F10E1C7A593E4E030EFB5B8721CE55B0B,8080808080808080808080808080808080808080808080808080808080808080;8181818181818181818181818181818181818181818181818181818181818181,024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D076602531FE6068134503D2723133227C867AC8FA6C83C537E9A44C3C5BDBDCB1FE337;0362C0A046DACCE86DDD0343C6D3C7C79C2208BA0D9C9CF24A6D046D21D21F90F703F006A18D5653C4EDF5391FF23A61F03FF83D237E880EE61187FA9F379A028E0A,FALSE,psig = n (out of range)
6,2,AC7EA4711D03C9C5F0D0CDD761B81FC27D18187655A75D37E4EE8879545756AA,1B84C5567B126440995D3ED5AABA0565D71E1834604819FF9C17F5E9D5DD078F;462779AD4AAD39514614751A71085F2F10E1C7A593E4E030EFB5B8721CE55B0B,8080808080808080808080808080808080808080808080808080808080808080;8181818181818181818181818181818181818181818181818181818181818181,024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D076602531FE6068134503D2723133227C867AC8FA6C83C537E9A44C3C5BDBDCB1FE337;0362C0A046DACCE86DDD0343C6D3C7C79C2208BA0D9C9CF24A6D046D21D21F90F703F006A18D5653C4EDF5391FF23A61F03FF83D237E880EE61187FA9F379A028E0A,FALSE,signer_index out of range
7,0,2873FA643DD95EDDDCE81DE64A0C7A8C8166E11E5C687261AAC94F9044EBC9E6,1B84C5567B126440995D3ED5AABA0565D71E1834604819FF9C17F5E9D5DD078F;462779AD4AAD39514614751A71085F2F10E1C7A593E4E030EFB5B8721CE55B0B,8080808080808080808080808080808080808080808080808080808080808080;8181818181818181818181818181818181818181818181818181818181818181,024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D076602531FE6068134503D2723133227C867AC8FA6C83C537E9A44C3C5BDBDCB1FE337;0362C0A046DACCE86DDD0343C6D3C7C79C2208BA0D9C9CF24A6D046D21D21F90F703F006A18D5653C4EDF5391FF23A61F03FF83D237E880EE61187FA9F379A028E0A,FALSE,psig for a different aggregate nonce
8,0,AC7EA4711D03C9C5F0D0CDD761B81FC27D18187655A75D37E4EE8879545756AA,1B84C5567B126440995D3ED5AABA0565D71E1834604819FF9C17F5E9D5DD078F;462779AD4AAD39514614751A71085F2F10E1C7A593E4E030EFB5B8721CE55B0B,8080808080808080808080808080808080808080808080808080808080808080;8181818181818181818181818181818181818181818181818181818181818181,024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D076602531FE6068134503D2723133227C867AC8FA6C83C537E9A44C3C5BDBDCB1FE337;034D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D076603F006A18D5653C4EDF5391FF23A61F03FF83D237E880EE61187FA9F379A028E0A,FALSE,Aggregate nonce R1 is infinity
1 index signer_index psig pubkeys messages pubnonces expected_result comment
2 0 0 AC7EA4711D03C9C5F0D0CDD761B81FC27D18187655A75D37E4EE8879545756AA 1B84C5567B126440995D3ED5AABA0565D71E1834604819FF9C17F5E9D5DD078F;462779AD4AAD39514614751A71085F2F10E1C7A593E4E030EFB5B8721CE55B0B 8080808080808080808080808080808080808080808080808080808080808080;8181818181818181818181818181818181818181818181818181818181818181 024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D076602531FE6068134503D2723133227C867AC8FA6C83C537E9A44C3C5BDBDCB1FE337;0362C0A046DACCE86DDD0343C6D3C7C79C2208BA0D9C9CF24A6D046D21D21F90F703F006A18D5653C4EDF5391FF23A61F03FF83D237E880EE61187FA9F379A028E0A TRUE Honest partial sig (signer 0)
3 1 1 B83BBCAA5F0D6E0DEBEE0000E41B21D2E9304D0387660F96D3664735B0B954A3 1B84C5567B126440995D3ED5AABA0565D71E1834604819FF9C17F5E9D5DD078F;462779AD4AAD39514614751A71085F2F10E1C7A593E4E030EFB5B8721CE55B0B 8080808080808080808080808080808080808080808080808080808080808080;8181818181818181818181818181818181818181818181818181818181818181 024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D076602531FE6068134503D2723133227C867AC8FA6C83C537E9A44C3C5BDBDCB1FE337;0362C0A046DACCE86DDD0343C6D3C7C79C2208BA0D9C9CF24A6D046D21D21F90F703F006A18D5653C4EDF5391FF23A61F03FF83D237E880EE61187FA9F379A028E0A TRUE Honest partial sig (signer 1)
4 2 1 AC7EA4711D03C9C5F0D0CDD761B81FC27D18187655A75D37E4EE8879545756AA 1B84C5567B126440995D3ED5AABA0565D71E1834604819FF9C17F5E9D5DD078F;462779AD4AAD39514614751A71085F2F10E1C7A593E4E030EFB5B8721CE55B0B 8080808080808080808080808080808080808080808080808080808080808080;8181818181818181818181818181818181818181818181818181818181818181 024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D076602531FE6068134503D2723133227C867AC8FA6C83C537E9A44C3C5BDBDCB1FE337;0362C0A046DACCE86DDD0343C6D3C7C79C2208BA0D9C9CF24A6D046D21D21F90F703F006A18D5653C4EDF5391FF23A61F03FF83D237E880EE61187FA9F379A028E0A FALSE Honest psig verified at the wrong signer_index
5 3 0 AC7EA4711D03C9C5F0D0CDD761B81FC27D18187655A75D37E4EE8879545756AB 1B84C5567B126440995D3ED5AABA0565D71E1834604819FF9C17F5E9D5DD078F;462779AD4AAD39514614751A71085F2F10E1C7A593E4E030EFB5B8721CE55B0B 8080808080808080808080808080808080808080808080808080808080808080;8181818181818181818181818181818181818181818181818181818181818181 024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D076602531FE6068134503D2723133227C867AC8FA6C83C537E9A44C3C5BDBDCB1FE337;0362C0A046DACCE86DDD0343C6D3C7C79C2208BA0D9C9CF24A6D046D21D21F90F703F006A18D5653C4EDF5391FF23A61F03FF83D237E880EE61187FA9F379A028E0A FALSE Tampered partial sig (bit flip)
6 4 0 0000000000000000000000000000000000000000000000000000000000000000 1B84C5567B126440995D3ED5AABA0565D71E1834604819FF9C17F5E9D5DD078F;462779AD4AAD39514614751A71085F2F10E1C7A593E4E030EFB5B8721CE55B0B 8080808080808080808080808080808080808080808080808080808080808080;8181818181818181818181818181818181818181818181818181818181818181 024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D076602531FE6068134503D2723133227C867AC8FA6C83C537E9A44C3C5BDBDCB1FE337;0362C0A046DACCE86DDD0343C6D3C7C79C2208BA0D9C9CF24A6D046D21D21F90F703F006A18D5653C4EDF5391FF23A61F03FF83D237E880EE61187FA9F379A028E0A FALSE psig = 0 (in range, wrong)
7 5 0 FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 1B84C5567B126440995D3ED5AABA0565D71E1834604819FF9C17F5E9D5DD078F;462779AD4AAD39514614751A71085F2F10E1C7A593E4E030EFB5B8721CE55B0B 8080808080808080808080808080808080808080808080808080808080808080;8181818181818181818181818181818181818181818181818181818181818181 024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D076602531FE6068134503D2723133227C867AC8FA6C83C537E9A44C3C5BDBDCB1FE337;0362C0A046DACCE86DDD0343C6D3C7C79C2208BA0D9C9CF24A6D046D21D21F90F703F006A18D5653C4EDF5391FF23A61F03FF83D237E880EE61187FA9F379A028E0A FALSE psig = n (out of range)
8 6 2 AC7EA4711D03C9C5F0D0CDD761B81FC27D18187655A75D37E4EE8879545756AA 1B84C5567B126440995D3ED5AABA0565D71E1834604819FF9C17F5E9D5DD078F;462779AD4AAD39514614751A71085F2F10E1C7A593E4E030EFB5B8721CE55B0B 8080808080808080808080808080808080808080808080808080808080808080;8181818181818181818181818181818181818181818181818181818181818181 024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D076602531FE6068134503D2723133227C867AC8FA6C83C537E9A44C3C5BDBDCB1FE337;0362C0A046DACCE86DDD0343C6D3C7C79C2208BA0D9C9CF24A6D046D21D21F90F703F006A18D5653C4EDF5391FF23A61F03FF83D237E880EE61187FA9F379A028E0A FALSE signer_index out of range
9 7 0 2873FA643DD95EDDDCE81DE64A0C7A8C8166E11E5C687261AAC94F9044EBC9E6 1B84C5567B126440995D3ED5AABA0565D71E1834604819FF9C17F5E9D5DD078F;462779AD4AAD39514614751A71085F2F10E1C7A593E4E030EFB5B8721CE55B0B 8080808080808080808080808080808080808080808080808080808080808080;8181818181818181818181818181818181818181818181818181818181818181 024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D076602531FE6068134503D2723133227C867AC8FA6C83C537E9A44C3C5BDBDCB1FE337;0362C0A046DACCE86DDD0343C6D3C7C79C2208BA0D9C9CF24A6D046D21D21F90F703F006A18D5653C4EDF5391FF23A61F03FF83D237E880EE61187FA9F379A028E0A FALSE psig for a different aggregate nonce
10 8 0 AC7EA4711D03C9C5F0D0CDD761B81FC27D18187655A75D37E4EE8879545756AA 1B84C5567B126440995D3ED5AABA0565D71E1834604819FF9C17F5E9D5DD078F;462779AD4AAD39514614751A71085F2F10E1C7A593E4E030EFB5B8721CE55B0B 8080808080808080808080808080808080808080808080808080808080808080;8181818181818181818181818181818181818181818181818181818181818181 024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D076602531FE6068134503D2723133227C867AC8FA6C83C537E9A44C3C5BDBDCB1FE337;034D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D076603F006A18D5653C4EDF5391FF23A61F03FF83D237E880EE61187FA9F379A028E0A FALSE Aggregate nonce R1 is infinity
+8
View File
@@ -0,0 +1,8 @@
index,secret_keys,messages,secnonces,expected_aggnonce,expected_partial_sigs,expected_sig,comment
0,FEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFDB9ADDBE5AE479F3ABED15D8BCF354040,8080808080808080808080808080808080808080808080808080808080808080,02020202020202020202020202020202020202020202020202020202020202020303030303030303030303030303030303030303030303030303030303030303,024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D076602531FE6068134503D2723133227C867AC8FA6C83C537E9A44C3C5BDBDCB1FE337,52127919CC414D892F6FD0FE262E3811F188A02C14867A7A1D4B69BCC771FC61,C8F3414CAF7ED655F450E7AA8B0164FFA38882DAD001CED9E82AFA11806DFD3B52127919CC414D892F6FD0FE262E3811F188A02C14867A7A1D4B69BCC771FC61,Single signer
1,FEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFDB9ADDBE5AE479F3ABED15D8BCF354040;FBFBFBFBFBFBFBFBFBFBFBFBFBFBFBFAB6AAD8E2AB449C37BBCE5A88CC323D3D,8080808080808080808080808080808080808080808080808080808080808080;8181818181818181818181818181818181818181818181818181818181818181,02020202020202020202020202020202020202020202020202020202020202020303030303030303030303030303030303030303030303030303030303030303;05050505050505050505050505050505050505050505050505050505050505050606060606060606060606060606060606060606060606060606060606060606,02989C0B76CB563971FDC9BEF31EC06C3560F3249D6EE9E5D83C57625596E05F6F0256B328B30C8BF5839E24058747879408BDB36241DC9C2E7C619FAA12B2920967,AC7EA4711D03C9C5F0D0CDD761B81FC27D18187655A75D37E4EE8879545756AA;B83BBCAA5F0D6E0DEBEE0000E41B21D2E9304D0387660F96D3664735B0B954A3,5485290A0E23A1912901F3CD0F2F8884CF8528B089F86ACFE5184670ECB8F06D64BA611B7C1137D3DCBECDD845D34196AB9988932DC4CC92F882712234DA6A0C,Two signers
2,FEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFDB9ADDBE5AE479F3ABED15D8BCF354040;FBFBFBFBFBFBFBFBFBFBFBFBFBFBFBFAB6AAD8E2AB449C37BBCE5A88CC323D3D;0707070707070707070707070707070707070707070707070707070707070707,8080808080808080808080808080808080808080808080808080808080808080;8181818181818181818181818181818181818181818181818181818181818181;8282828282828282828282828282828282828282828282828282828282828282,02020202020202020202020202020202020202020202020202020202020202020303030303030303030303030303030303030303030303030303030303030303;05050505050505050505050505050505050505050505050505050505050505050606060606060606060606060606060606060606060606060606060606060606;08080808080808080808080808080808080808080808080808080808080808080909090909090909090909090909090909090909090909090909090909090909,021A7A569E91DBF60581509C7FC946D1003B60C7DEE85299538DB6353538D59574036360E856310CE5D294E8BE33FC807077DC56AC80D95D9CD4DDBD21325EFF73F7,9567B1D92D32A863462A5373E775095C64F36F6546CDE60A972550EC9D27E963;3780F8549BB5C8704A3A75F3B6A9E93EF3D844B80C3F8BC730F0658B4F729B08;E42ABB02908EEB24A91CB5530551A45D9501026E16EEDBC1B15FB38088030F54,2F02469D98943B63C85904CA5F34AD0525B650A79B55D4C5730FBE1AAC7C004FB113653059775BF839817EBAA37096FA331DD9A4BAB3AD57B9A30B6BA467527E,Three signers
3,FEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFDB9ADDBE5AE479F3ABED15D8BCF354040;FBFBFBFBFBFBFBFBFBFBFBFBFBFBFBFAB6AAD8E2AB449C37BBCE5A88CC323D3D;0707070707070707070707070707070707070707070707070707070707070707;F5F5F5F5F5F5F5F5F5F5F5F5F5F5F5F4B0A4D2DCA53E9631B5C85482C62C3737;0D0D0D0D0D0D0D0D0D0D0D0D0D0D0D0D0D0D0D0D0D0D0D0D0D0D0D0D0D0D0D0D,8080808080808080808080808080808080808080808080808080808080808080;8181818181818181818181818181818181818181818181818181818181818181;8282828282828282828282828282828282828282828282828282828282828282;8383838383838383838383838383838383838383838383838383838383838383;8484848484848484848484848484848484848484848484848484848484848484,02020202020202020202020202020202020202020202020202020202020202020303030303030303030303030303030303030303030303030303030303030303;05050505050505050505050505050505050505050505050505050505050505050606060606060606060606060606060606060606060606060606060606060606;08080808080808080808080808080808080808080808080808080808080808080909090909090909090909090909090909090909090909090909090909090909;0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C;0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F,02BA72A6E8BA53E8B971AD0C9823968AEF4D78CE8AF255AB43DFF83003C902FB8D03948B53DA97FDF674C0877315ACBCC8761AA3B9A582B439982FBAFAC99F97210F,40BD244B3077B384CC4FB475EA4B75F00534F8C8AC9495DF5D501AB8EBEFE95C;7413D7A5D78AF26516FE5CD9D7D769DA04B4934162CF212761F8C6BF145F33A6;EC4BA1816B30306CE38A02FFE0D742DB131069DFF013C1CCC95DEC12816E0FAE;1A3D61F95D6C6FC23871AA5795E0F7DC8B2C4AB7A70D7AFAA7D3C51FB0F646E2;CBEF31FEF7E60491D7E986CF11E8F6739FD215A8098BAD81E03F29ADDAFA9DAD,8E1F9F4D2716EBA94D4DCA8A484F08B121EC7CB56331C056E7CA68955AB55D018749316AC8854AAAD73345764AC410F7D29A9C7C517F60D89114FF3E6D418EBD,Five signers
4,FEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFDB9ADDBE5AE479F3ABED15D8BCF354040;FEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFDB9ADDBE5AE479F3ABED15D8BCF354040,A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0;A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1,0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C;0D0D0D0D0D0D0D0D0D0D0D0D0D0D0D0D0D0D0D0D0D0D0D0D0D0D0D0D0D0D0D0D0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E,0329CE758BB2D21DEF214CEA069ADDBD16896EC77924BF0EC9219E00DCF077605E02DCEC806DEB8DA0774EC5A5D0F5097FFDE1F70A9F40D44363BD3DC2DC2590CA5D,F9E5D159B41C9BAC7B61B034E88DE52F6E5035700312ACCCAC9518B123F172E8;F1EC4EB02363C0A81FF82BD9500A990BFA59E3AE38102D59FF41B1C451B67D4E,6EF86E78B8043F5F2683D1A4018F57527D1992B2A8090770ECD375B45310FEE4EBD22009D7805C549B59DC0E38987E3CADFB3C378BDA39EAEC046BE8A571AEF5,Same pubkey signs two different messages
5,2020202020202020202020202020202020202020202020202020202020202020;1E1E1E1E1E1E1E1E1E1E1E1E1E1E1E1E1E1E1E1E1E1E1E1E1E1E1E1E1E1E1E1E,8080808080808080808080808080808080808080808080808080808080808080;8181818181818181818181818181818181818181818181818181818181818181,02020202020202020202020202020202020202020202020202020202020202020303030303030303030303030303030303030303030303030303030303030303;05050505050505050505050505050505050505050505050505050505050505050606060606060606060606060606060606060606060606060606060606060606,02989C0B76CB563971FDC9BEF31EC06C3560F3249D6EE9E5D83C57625596E05F6F0256B328B30C8BF5839E24058747879408BDB36241DC9C2E7C619FAA12B2920967,20DCE3CE1C51AE4F82A282B06989EC59206BC6E5EE42507FC7E4A49118182977;1D1F36CADF491EA12E121FA6520BCE32B2BAFE31AEFD0E840B1725A10F9FE76B,C01306054091CC81F54D5A49DD591706CB36BF621C984E9F7FEBA26E0C544E553DFC1A98FB9ACCF0B0B4A256BB95BA8BD326C5179D3F5F03D2FBCA3227B810E2,Signers with tweaked keys
6,FEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFDB9ADDBE5AE479F3ABED15D8BCF354040;FEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFDB9ADDBE5AE479F3ABED15D8BCF354040,C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0;C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0,14141414141414141414141414141414141414141414141414141414141414141515151515151515151515151515151515151515151515151515151515151515;16161616161616161616161616161616161616161616161616161616161616161717171717171717171717171717171717171717171717171717171717171717,035BE5E9478209674A96E60F1F037F6176540FD001FA1D64694770C56A7709C42C028F5304E2373E56EE7D774CB89E9F1AFECF0EE7E3E3757F189908F069DAA36C60,1AF1A3721CA970F3A158EB32176A71B69C8886AF98C7EF9F50E14D8E11B1A2A9;C0B6BBB2725E30F338DB5C032959AE8D766B0E8389077A4F217DF88FB190260C,9669004E9B03D7C34670914CD09024A65DAA635497C42BE33F21C56CD740BBDEDBA85F248F07A1E6DA34473540C4204412F3953321CF69EE725F461DC341C8B5,"Exact duplicate (pk, m) entries"
1 index secret_keys messages secnonces expected_aggnonce expected_partial_sigs expected_sig comment
2 0 FEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFDB9ADDBE5AE479F3ABED15D8BCF354040 8080808080808080808080808080808080808080808080808080808080808080 02020202020202020202020202020202020202020202020202020202020202020303030303030303030303030303030303030303030303030303030303030303 024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D076602531FE6068134503D2723133227C867AC8FA6C83C537E9A44C3C5BDBDCB1FE337 52127919CC414D892F6FD0FE262E3811F188A02C14867A7A1D4B69BCC771FC61 C8F3414CAF7ED655F450E7AA8B0164FFA38882DAD001CED9E82AFA11806DFD3B52127919CC414D892F6FD0FE262E3811F188A02C14867A7A1D4B69BCC771FC61 Single signer
3 1 FEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFDB9ADDBE5AE479F3ABED15D8BCF354040;FBFBFBFBFBFBFBFBFBFBFBFBFBFBFBFAB6AAD8E2AB449C37BBCE5A88CC323D3D 8080808080808080808080808080808080808080808080808080808080808080;8181818181818181818181818181818181818181818181818181818181818181 02020202020202020202020202020202020202020202020202020202020202020303030303030303030303030303030303030303030303030303030303030303;05050505050505050505050505050505050505050505050505050505050505050606060606060606060606060606060606060606060606060606060606060606 02989C0B76CB563971FDC9BEF31EC06C3560F3249D6EE9E5D83C57625596E05F6F0256B328B30C8BF5839E24058747879408BDB36241DC9C2E7C619FAA12B2920967 AC7EA4711D03C9C5F0D0CDD761B81FC27D18187655A75D37E4EE8879545756AA;B83BBCAA5F0D6E0DEBEE0000E41B21D2E9304D0387660F96D3664735B0B954A3 5485290A0E23A1912901F3CD0F2F8884CF8528B089F86ACFE5184670ECB8F06D64BA611B7C1137D3DCBECDD845D34196AB9988932DC4CC92F882712234DA6A0C Two signers
4 2 FEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFDB9ADDBE5AE479F3ABED15D8BCF354040;FBFBFBFBFBFBFBFBFBFBFBFBFBFBFBFAB6AAD8E2AB449C37BBCE5A88CC323D3D;0707070707070707070707070707070707070707070707070707070707070707 8080808080808080808080808080808080808080808080808080808080808080;8181818181818181818181818181818181818181818181818181818181818181;8282828282828282828282828282828282828282828282828282828282828282 02020202020202020202020202020202020202020202020202020202020202020303030303030303030303030303030303030303030303030303030303030303;05050505050505050505050505050505050505050505050505050505050505050606060606060606060606060606060606060606060606060606060606060606;08080808080808080808080808080808080808080808080808080808080808080909090909090909090909090909090909090909090909090909090909090909 021A7A569E91DBF60581509C7FC946D1003B60C7DEE85299538DB6353538D59574036360E856310CE5D294E8BE33FC807077DC56AC80D95D9CD4DDBD21325EFF73F7 9567B1D92D32A863462A5373E775095C64F36F6546CDE60A972550EC9D27E963;3780F8549BB5C8704A3A75F3B6A9E93EF3D844B80C3F8BC730F0658B4F729B08;E42ABB02908EEB24A91CB5530551A45D9501026E16EEDBC1B15FB38088030F54 2F02469D98943B63C85904CA5F34AD0525B650A79B55D4C5730FBE1AAC7C004FB113653059775BF839817EBAA37096FA331DD9A4BAB3AD57B9A30B6BA467527E Three signers
5 3 FEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFDB9ADDBE5AE479F3ABED15D8BCF354040;FBFBFBFBFBFBFBFBFBFBFBFBFBFBFBFAB6AAD8E2AB449C37BBCE5A88CC323D3D;0707070707070707070707070707070707070707070707070707070707070707;F5F5F5F5F5F5F5F5F5F5F5F5F5F5F5F4B0A4D2DCA53E9631B5C85482C62C3737;0D0D0D0D0D0D0D0D0D0D0D0D0D0D0D0D0D0D0D0D0D0D0D0D0D0D0D0D0D0D0D0D 8080808080808080808080808080808080808080808080808080808080808080;8181818181818181818181818181818181818181818181818181818181818181;8282828282828282828282828282828282828282828282828282828282828282;8383838383838383838383838383838383838383838383838383838383838383;8484848484848484848484848484848484848484848484848484848484848484 02020202020202020202020202020202020202020202020202020202020202020303030303030303030303030303030303030303030303030303030303030303;05050505050505050505050505050505050505050505050505050505050505050606060606060606060606060606060606060606060606060606060606060606;08080808080808080808080808080808080808080808080808080808080808080909090909090909090909090909090909090909090909090909090909090909;0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C;0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F 02BA72A6E8BA53E8B971AD0C9823968AEF4D78CE8AF255AB43DFF83003C902FB8D03948B53DA97FDF674C0877315ACBCC8761AA3B9A582B439982FBAFAC99F97210F 40BD244B3077B384CC4FB475EA4B75F00534F8C8AC9495DF5D501AB8EBEFE95C;7413D7A5D78AF26516FE5CD9D7D769DA04B4934162CF212761F8C6BF145F33A6;EC4BA1816B30306CE38A02FFE0D742DB131069DFF013C1CCC95DEC12816E0FAE;1A3D61F95D6C6FC23871AA5795E0F7DC8B2C4AB7A70D7AFAA7D3C51FB0F646E2;CBEF31FEF7E60491D7E986CF11E8F6739FD215A8098BAD81E03F29ADDAFA9DAD 8E1F9F4D2716EBA94D4DCA8A484F08B121EC7CB56331C056E7CA68955AB55D018749316AC8854AAAD73345764AC410F7D29A9C7C517F60D89114FF3E6D418EBD Five signers
6 4 FEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFDB9ADDBE5AE479F3ABED15D8BCF354040;FEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFDB9ADDBE5AE479F3ABED15D8BCF354040 A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0;A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1 0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C;0D0D0D0D0D0D0D0D0D0D0D0D0D0D0D0D0D0D0D0D0D0D0D0D0D0D0D0D0D0D0D0D0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E 0329CE758BB2D21DEF214CEA069ADDBD16896EC77924BF0EC9219E00DCF077605E02DCEC806DEB8DA0774EC5A5D0F5097FFDE1F70A9F40D44363BD3DC2DC2590CA5D F9E5D159B41C9BAC7B61B034E88DE52F6E5035700312ACCCAC9518B123F172E8;F1EC4EB02363C0A81FF82BD9500A990BFA59E3AE38102D59FF41B1C451B67D4E 6EF86E78B8043F5F2683D1A4018F57527D1992B2A8090770ECD375B45310FEE4EBD22009D7805C549B59DC0E38987E3CADFB3C378BDA39EAEC046BE8A571AEF5 Same pubkey signs two different messages
7 5 2020202020202020202020202020202020202020202020202020202020202020;1E1E1E1E1E1E1E1E1E1E1E1E1E1E1E1E1E1E1E1E1E1E1E1E1E1E1E1E1E1E1E1E 8080808080808080808080808080808080808080808080808080808080808080;8181818181818181818181818181818181818181818181818181818181818181 02020202020202020202020202020202020202020202020202020202020202020303030303030303030303030303030303030303030303030303030303030303;05050505050505050505050505050505050505050505050505050505050505050606060606060606060606060606060606060606060606060606060606060606 02989C0B76CB563971FDC9BEF31EC06C3560F3249D6EE9E5D83C57625596E05F6F0256B328B30C8BF5839E24058747879408BDB36241DC9C2E7C619FAA12B2920967 20DCE3CE1C51AE4F82A282B06989EC59206BC6E5EE42507FC7E4A49118182977;1D1F36CADF491EA12E121FA6520BCE32B2BAFE31AEFD0E840B1725A10F9FE76B C01306054091CC81F54D5A49DD591706CB36BF621C984E9F7FEBA26E0C544E553DFC1A98FB9ACCF0B0B4A256BB95BA8BD326C5179D3F5F03D2FBCA3227B810E2 Signers with tweaked keys
8 6 FEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFDB9ADDBE5AE479F3ABED15D8BCF354040;FEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFDB9ADDBE5AE479F3ABED15D8BCF354040 C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0;C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0 14141414141414141414141414141414141414141414141414141414141414141515151515151515151515151515151515151515151515151515151515151515;16161616161616161616161616161616161616161616161616161616161616161717171717171717171717171717171717171717171717171717171717171717 035BE5E9478209674A96E60F1F037F6176540FD001FA1D64694770C56A7709C42C028F5304E2373E56EE7D774CB89E9F1AFECF0EE7E3E3757F189908F069DAA36C60 1AF1A3721CA970F3A158EB32176A71B69C8886AF98C7EF9F50E14D8E11B1A2A9;C0B6BBB2725E30F338DB5C032959AE8D766B0E8389077A4F217DF88FB190260C 9669004E9B03D7C34670914CD09024A65DAA635497C42BE33F21C56CD740BBDEDBA85F248F07A1E6DA34473540C4204412F3953321CF69EE725F461DC341C8B5 Exact duplicate (pk, m) entries
@@ -0,0 +1,10 @@
index,secret_key,message,secnonce,aggnonce,pubkeys,messages,pubnonces,comment
0,FEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFDB9ADDBE5AE479F3ABED15D8BCF354040,8080808080808080808080808080808080808080808080808080808080808080,02020202020202020202020202020202020202020202020202020202020202020303030303030303030303030303030303030303030303030303030303030303,021A7A569E91DBF60581509C7FC946D1003B60C7DEE85299538DB6353538D59574030F0FB9A244AD31A369EE02B7ABFBBB0BFA3812B9A39ED93346D03D67D412D177,1B84C5567B126440995D3ED5AABA0565D71E1834604819FF9C17F5E9D5DD078F;462779AD4AAD39514614751A71085F2F10E1C7A593E4E030EFB5B8721CE55B0B;989C0B76CB563971FDC9BEF31EC06C3560F3249D6EE9E5D83C57625596E05F6F,8080808080808080808080808080808080808080808080808080808080808080;8181818181818181818181818181818181818181818181818181818181818181;8282828282828282828282828282828282828282828282828282828282828282,024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D076602531FE6068134503D2723133227C867AC8FA6C83C537E9A44C3C5BDBDCB1FE337;0362C0A046DACCE86DDD0343C6D3C7C79C2208BA0D9C9CF24A6D046D21D21F90F703F006A18D5653C4EDF5391FF23A61F03FF83D237E880EE61187FA9F379A028E0A;03F991F944D1E1954A7FC8B9BF62E0D78F015F4C07762D505E20E6C45260A3661B02531FE6068134503D2723133227C867AC8FA6C83C537E9A44C3C5BDBDCB1FE337,Signer's R2 appears at two indices
1,FEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFDB9ADDBE5AE479F3ABED15D8BCF354040,8080808080808080808080808080808080808080808080808080808080808080,02020202020202020202020202020202020202020202020202020202020202020303030303030303030303030303030303030303030303030303030303030303,021A7A569E91DBF60581509C7FC946D1003B60C7DEE85299538DB6353538D5957403B83130DE0D1386592FE7B9F407F5F1AE8F1DB91D772E484B3D81DF0FA2E88F24,1B84C5567B126440995D3ED5AABA0565D71E1834604819FF9C17F5E9D5DD078F;462779AD4AAD39514614751A71085F2F10E1C7A593E4E030EFB5B8721CE55B0B;989C0B76CB563971FDC9BEF31EC06C3560F3249D6EE9E5D83C57625596E05F6F,8080808080808080808080808080808080808080808080808080808080808080;8181818181818181818181818181818181818181818181818181818181818181;8282828282828282828282828282828282828282828282828282828282828282,024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D0766031195A8046DCBB8E17034BCA630065E7A0982E4E36F6F7E5A8D4554E4846FCD99;0362C0A046DACCE86DDD0343C6D3C7C79C2208BA0D9C9CF24A6D046D21D21F90F703F006A18D5653C4EDF5391FF23A61F03FF83D237E880EE61187FA9F379A028E0A;03F991F944D1E1954A7FC8B9BF62E0D78F015F4C07762D505E20E6C45260A3661B0256B328B30C8BF5839E24058747879408BDB36241DC9C2E7C619FAA12B2920967,Signer's R2 appears at no index
2,FEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFDB9ADDBE5AE479F3ABED15D8BCF354040,8080808080808080808080808080808080808080808080808080808080808080,02020202020202020202020202020202020202020202020202020202020202020303030303030303030303030303030303030303030303030303030303030303,021A7A569E91DBF60581509C7FC946D1003B60C7DEE85299538DB6353538D59574036360E856310CE5D294E8BE33FC807077DC56AC80D95D9CD4DDBD21325EFF73F7,1B84C5567B126440995D3ED5AABA0565D71E1834604819FF9C17F5E9D5DD078F;462779AD4AAD39514614751A71085F2F10E1C7A593E4E030EFB5B8721CE55B0B;989C0B76CB563971FDC9BEF31EC06C3560F3249D6EE9E5D83C57625596E05F6F,EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE;8181818181818181818181818181818181818181818181818181818181818181;8282828282828282828282828282828282828282828282828282828282828282,024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D076602531FE6068134503D2723133227C867AC8FA6C83C537E9A44C3C5BDBDCB1FE337;0362C0A046DACCE86DDD0343C6D3C7C79C2208BA0D9C9CF24A6D046D21D21F90F703F006A18D5653C4EDF5391FF23A61F03FF83D237E880EE61187FA9F379A028E0A;03F991F944D1E1954A7FC8B9BF62E0D78F015F4C07762D505E20E6C45260A3661B0256B328B30C8BF5839E24058747879408BDB36241DC9C2E7C619FAA12B2920967,Message at signer's index was substituted
3,FEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFDB9ADDBE5AE479F3ABED15D8BCF354040,8080808080808080808080808080808080808080808080808080808080808080,02020202020202020202020202020202020202020202020202020202020202020303030303030303030303030303030303030303030303030303030303030303,021A7A569E91DBF60581509C7FC946D1003B60C7DEE85299538DB6353538D59574036360E856310CE5D294E8BE33FC807077DC56AC80D95D9CD4DDBD21325EFF73F7,9AC20335EB38768D2052BE1DBBC3C8F6178407458E51E6B4AD22F1D91758895B;462779AD4AAD39514614751A71085F2F10E1C7A593E4E030EFB5B8721CE55B0B;989C0B76CB563971FDC9BEF31EC06C3560F3249D6EE9E5D83C57625596E05F6F,8080808080808080808080808080808080808080808080808080808080808080;8181818181818181818181818181818181818181818181818181818181818181;8282828282828282828282828282828282828282828282828282828282828282,024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D076602531FE6068134503D2723133227C867AC8FA6C83C537E9A44C3C5BDBDCB1FE337;0362C0A046DACCE86DDD0343C6D3C7C79C2208BA0D9C9CF24A6D046D21D21F90F703F006A18D5653C4EDF5391FF23A61F03FF83D237E880EE61187FA9F379A028E0A;03F991F944D1E1954A7FC8B9BF62E0D78F015F4C07762D505E20E6C45260A3661B0256B328B30C8BF5839E24058747879408BDB36241DC9C2E7C619FAA12B2920967,Public key at signer's index was substituted
4,FEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFDB9ADDBE5AE479F3ABED15D8BCF354040,8080808080808080808080808080808080808080808080808080808080808080,00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000,021A7A569E91DBF60581509C7FC946D1003B60C7DEE85299538DB6353538D59574036360E856310CE5D294E8BE33FC807077DC56AC80D95D9CD4DDBD21325EFF73F7,1B84C5567B126440995D3ED5AABA0565D71E1834604819FF9C17F5E9D5DD078F;462779AD4AAD39514614751A71085F2F10E1C7A593E4E030EFB5B8721CE55B0B;989C0B76CB563971FDC9BEF31EC06C3560F3249D6EE9E5D83C57625596E05F6F,8080808080808080808080808080808080808080808080808080808080808080;8181818181818181818181818181818181818181818181818181818181818181;8282828282828282828282828282828282828282828282828282828282828282,024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D076602531FE6068134503D2723133227C867AC8FA6C83C537E9A44C3C5BDBDCB1FE337;0362C0A046DACCE86DDD0343C6D3C7C79C2208BA0D9C9CF24A6D046D21D21F90F703F006A18D5653C4EDF5391FF23A61F03FF83D237E880EE61187FA9F379A028E0A;03F991F944D1E1954A7FC8B9BF62E0D78F015F4C07762D505E20E6C45260A3661B0256B328B30C8BF5839E24058747879408BDB36241DC9C2E7C619FAA12B2920967,Secnonce is all zeros
5,0000000000000000000000000000000000000000000000000000000000000000,8080808080808080808080808080808080808080808080808080808080808080,02020202020202020202020202020202020202020202020202020202020202020303030303030303030303030303030303030303030303030303030303030303,021A7A569E91DBF60581509C7FC946D1003B60C7DEE85299538DB6353538D59574036360E856310CE5D294E8BE33FC807077DC56AC80D95D9CD4DDBD21325EFF73F7,1B84C5567B126440995D3ED5AABA0565D71E1834604819FF9C17F5E9D5DD078F;462779AD4AAD39514614751A71085F2F10E1C7A593E4E030EFB5B8721CE55B0B;989C0B76CB563971FDC9BEF31EC06C3560F3249D6EE9E5D83C57625596E05F6F,8080808080808080808080808080808080808080808080808080808080808080;8181818181818181818181818181818181818181818181818181818181818181;8282828282828282828282828282828282828282828282828282828282828282,024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D076602531FE6068134503D2723133227C867AC8FA6C83C537E9A44C3C5BDBDCB1FE337;0362C0A046DACCE86DDD0343C6D3C7C79C2208BA0D9C9CF24A6D046D21D21F90F703F006A18D5653C4EDF5391FF23A61F03FF83D237E880EE61187FA9F379A028E0A;03F991F944D1E1954A7FC8B9BF62E0D78F015F4C07762D505E20E6C45260A3661B0256B328B30C8BF5839E24058747879408BDB36241DC9C2E7C619FAA12B2920967,Secret key is zero
6,FEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFDB9ADDBE5AE479F3ABED15D8BCF354040,8080808080808080808080808080808080808080808080808080808080808080,02020202020202020202020202020202020202020202020202020202020202020303030303030303030303030303030303030303030303030303030303030303,000000000000000000000000000000000000000000000000000000000000000000036360E856310CE5D294E8BE33FC807077DC56AC80D95D9CD4DDBD21325EFF73F7,1B84C5567B126440995D3ED5AABA0565D71E1834604819FF9C17F5E9D5DD078F;462779AD4AAD39514614751A71085F2F10E1C7A593E4E030EFB5B8721CE55B0B;989C0B76CB563971FDC9BEF31EC06C3560F3249D6EE9E5D83C57625596E05F6F,8080808080808080808080808080808080808080808080808080808080808080;8181818181818181818181818181818181818181818181818181818181818181;8282828282828282828282828282828282828282828282828282828282828282,024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D076602531FE6068134503D2723133227C867AC8FA6C83C537E9A44C3C5BDBDCB1FE337;0362C0A046DACCE86DDD0343C6D3C7C79C2208BA0D9C9CF24A6D046D21D21F90F703F006A18D5653C4EDF5391FF23A61F03FF83D237E880EE61187FA9F379A028E0A;03F991F944D1E1954A7FC8B9BF62E0D78F015F4C07762D505E20E6C45260A3661B0256B328B30C8BF5839E24058747879408BDB36241DC9C2E7C619FAA12B2920967,Aggregate nonce first half is all zeros
7,FEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFDB9ADDBE5AE479F3ABED15D8BCF354040,8080808080808080808080808080808080808080808080808080808080808080,02020202020202020202020202020202020202020202020202020202020202020303030303030303030303030303030303030303030303030303030303030303,021A7A569E91DBF60581509C7FC946D1003B60C7DEE85299538DB6353538D59574020000000000000000000000000000000000000000000000000000000000000000,1B84C5567B126440995D3ED5AABA0565D71E1834604819FF9C17F5E9D5DD078F;462779AD4AAD39514614751A71085F2F10E1C7A593E4E030EFB5B8721CE55B0B;989C0B76CB563971FDC9BEF31EC06C3560F3249D6EE9E5D83C57625596E05F6F,8080808080808080808080808080808080808080808080808080808080808080;8181818181818181818181818181818181818181818181818181818181818181;8282828282828282828282828282828282828282828282828282828282828282,024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D076602531FE6068134503D2723133227C867AC8FA6C83C537E9A44C3C5BDBDCB1FE337;0362C0A046DACCE86DDD0343C6D3C7C79C2208BA0D9C9CF24A6D046D21D21F90F703F006A18D5653C4EDF5391FF23A61F03FF83D237E880EE61187FA9F379A028E0A;03F991F944D1E1954A7FC8B9BF62E0D78F015F4C07762D505E20E6C45260A3661B0256B328B30C8BF5839E24058747879408BDB36241DC9C2E7C619FAA12B2920967,Aggregate nonce second half is not on the curve
8,FEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFDB9ADDBE5AE479F3ABED15D8BCF354040,8080808080808080808080808080808080808080808080808080808080808080,02020202020202020202020202020202020202020202020202020202020202020303030303030303030303030303030303030303030303030303030303030303,021A7A569E91DBF60581509C7FC946D1003B60C7DEE85299538DB6353538D5957402FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC30,1B84C5567B126440995D3ED5AABA0565D71E1834604819FF9C17F5E9D5DD078F;462779AD4AAD39514614751A71085F2F10E1C7A593E4E030EFB5B8721CE55B0B;989C0B76CB563971FDC9BEF31EC06C3560F3249D6EE9E5D83C57625596E05F6F,8080808080808080808080808080808080808080808080808080808080808080;8181818181818181818181818181818181818181818181818181818181818181;8282828282828282828282828282828282828282828282828282828282828282,024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D076602531FE6068134503D2723133227C867AC8FA6C83C537E9A44C3C5BDBDCB1FE337;0362C0A046DACCE86DDD0343C6D3C7C79C2208BA0D9C9CF24A6D046D21D21F90F703F006A18D5653C4EDF5391FF23A61F03FF83D237E880EE61187FA9F379A028E0A;03F991F944D1E1954A7FC8B9BF62E0D78F015F4C07762D505E20E6C45260A3661B0256B328B30C8BF5839E24058747879408BDB36241DC9C2E7C619FAA12B2920967,Aggregate nonce second half exceeds field size
1 index secret_key message secnonce aggnonce pubkeys messages pubnonces comment
2 0 FEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFDB9ADDBE5AE479F3ABED15D8BCF354040 8080808080808080808080808080808080808080808080808080808080808080 02020202020202020202020202020202020202020202020202020202020202020303030303030303030303030303030303030303030303030303030303030303 021A7A569E91DBF60581509C7FC946D1003B60C7DEE85299538DB6353538D59574030F0FB9A244AD31A369EE02B7ABFBBB0BFA3812B9A39ED93346D03D67D412D177 1B84C5567B126440995D3ED5AABA0565D71E1834604819FF9C17F5E9D5DD078F;462779AD4AAD39514614751A71085F2F10E1C7A593E4E030EFB5B8721CE55B0B;989C0B76CB563971FDC9BEF31EC06C3560F3249D6EE9E5D83C57625596E05F6F 8080808080808080808080808080808080808080808080808080808080808080;8181818181818181818181818181818181818181818181818181818181818181;8282828282828282828282828282828282828282828282828282828282828282 024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D076602531FE6068134503D2723133227C867AC8FA6C83C537E9A44C3C5BDBDCB1FE337;0362C0A046DACCE86DDD0343C6D3C7C79C2208BA0D9C9CF24A6D046D21D21F90F703F006A18D5653C4EDF5391FF23A61F03FF83D237E880EE61187FA9F379A028E0A;03F991F944D1E1954A7FC8B9BF62E0D78F015F4C07762D505E20E6C45260A3661B02531FE6068134503D2723133227C867AC8FA6C83C537E9A44C3C5BDBDCB1FE337 Signer's R2 appears at two indices
3 1 FEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFDB9ADDBE5AE479F3ABED15D8BCF354040 8080808080808080808080808080808080808080808080808080808080808080 02020202020202020202020202020202020202020202020202020202020202020303030303030303030303030303030303030303030303030303030303030303 021A7A569E91DBF60581509C7FC946D1003B60C7DEE85299538DB6353538D5957403B83130DE0D1386592FE7B9F407F5F1AE8F1DB91D772E484B3D81DF0FA2E88F24 1B84C5567B126440995D3ED5AABA0565D71E1834604819FF9C17F5E9D5DD078F;462779AD4AAD39514614751A71085F2F10E1C7A593E4E030EFB5B8721CE55B0B;989C0B76CB563971FDC9BEF31EC06C3560F3249D6EE9E5D83C57625596E05F6F 8080808080808080808080808080808080808080808080808080808080808080;8181818181818181818181818181818181818181818181818181818181818181;8282828282828282828282828282828282828282828282828282828282828282 024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D0766031195A8046DCBB8E17034BCA630065E7A0982E4E36F6F7E5A8D4554E4846FCD99;0362C0A046DACCE86DDD0343C6D3C7C79C2208BA0D9C9CF24A6D046D21D21F90F703F006A18D5653C4EDF5391FF23A61F03FF83D237E880EE61187FA9F379A028E0A;03F991F944D1E1954A7FC8B9BF62E0D78F015F4C07762D505E20E6C45260A3661B0256B328B30C8BF5839E24058747879408BDB36241DC9C2E7C619FAA12B2920967 Signer's R2 appears at no index
4 2 FEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFDB9ADDBE5AE479F3ABED15D8BCF354040 8080808080808080808080808080808080808080808080808080808080808080 02020202020202020202020202020202020202020202020202020202020202020303030303030303030303030303030303030303030303030303030303030303 021A7A569E91DBF60581509C7FC946D1003B60C7DEE85299538DB6353538D59574036360E856310CE5D294E8BE33FC807077DC56AC80D95D9CD4DDBD21325EFF73F7 1B84C5567B126440995D3ED5AABA0565D71E1834604819FF9C17F5E9D5DD078F;462779AD4AAD39514614751A71085F2F10E1C7A593E4E030EFB5B8721CE55B0B;989C0B76CB563971FDC9BEF31EC06C3560F3249D6EE9E5D83C57625596E05F6F EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE;8181818181818181818181818181818181818181818181818181818181818181;8282828282828282828282828282828282828282828282828282828282828282 024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D076602531FE6068134503D2723133227C867AC8FA6C83C537E9A44C3C5BDBDCB1FE337;0362C0A046DACCE86DDD0343C6D3C7C79C2208BA0D9C9CF24A6D046D21D21F90F703F006A18D5653C4EDF5391FF23A61F03FF83D237E880EE61187FA9F379A028E0A;03F991F944D1E1954A7FC8B9BF62E0D78F015F4C07762D505E20E6C45260A3661B0256B328B30C8BF5839E24058747879408BDB36241DC9C2E7C619FAA12B2920967 Message at signer's index was substituted
5 3 FEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFDB9ADDBE5AE479F3ABED15D8BCF354040 8080808080808080808080808080808080808080808080808080808080808080 02020202020202020202020202020202020202020202020202020202020202020303030303030303030303030303030303030303030303030303030303030303 021A7A569E91DBF60581509C7FC946D1003B60C7DEE85299538DB6353538D59574036360E856310CE5D294E8BE33FC807077DC56AC80D95D9CD4DDBD21325EFF73F7 9AC20335EB38768D2052BE1DBBC3C8F6178407458E51E6B4AD22F1D91758895B;462779AD4AAD39514614751A71085F2F10E1C7A593E4E030EFB5B8721CE55B0B;989C0B76CB563971FDC9BEF31EC06C3560F3249D6EE9E5D83C57625596E05F6F 8080808080808080808080808080808080808080808080808080808080808080;8181818181818181818181818181818181818181818181818181818181818181;8282828282828282828282828282828282828282828282828282828282828282 024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D076602531FE6068134503D2723133227C867AC8FA6C83C537E9A44C3C5BDBDCB1FE337;0362C0A046DACCE86DDD0343C6D3C7C79C2208BA0D9C9CF24A6D046D21D21F90F703F006A18D5653C4EDF5391FF23A61F03FF83D237E880EE61187FA9F379A028E0A;03F991F944D1E1954A7FC8B9BF62E0D78F015F4C07762D505E20E6C45260A3661B0256B328B30C8BF5839E24058747879408BDB36241DC9C2E7C619FAA12B2920967 Public key at signer's index was substituted
6 4 FEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFDB9ADDBE5AE479F3ABED15D8BCF354040 8080808080808080808080808080808080808080808080808080808080808080 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 021A7A569E91DBF60581509C7FC946D1003B60C7DEE85299538DB6353538D59574036360E856310CE5D294E8BE33FC807077DC56AC80D95D9CD4DDBD21325EFF73F7 1B84C5567B126440995D3ED5AABA0565D71E1834604819FF9C17F5E9D5DD078F;462779AD4AAD39514614751A71085F2F10E1C7A593E4E030EFB5B8721CE55B0B;989C0B76CB563971FDC9BEF31EC06C3560F3249D6EE9E5D83C57625596E05F6F 8080808080808080808080808080808080808080808080808080808080808080;8181818181818181818181818181818181818181818181818181818181818181;8282828282828282828282828282828282828282828282828282828282828282 024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D076602531FE6068134503D2723133227C867AC8FA6C83C537E9A44C3C5BDBDCB1FE337;0362C0A046DACCE86DDD0343C6D3C7C79C2208BA0D9C9CF24A6D046D21D21F90F703F006A18D5653C4EDF5391FF23A61F03FF83D237E880EE61187FA9F379A028E0A;03F991F944D1E1954A7FC8B9BF62E0D78F015F4C07762D505E20E6C45260A3661B0256B328B30C8BF5839E24058747879408BDB36241DC9C2E7C619FAA12B2920967 Secnonce is all zeros
7 5 0000000000000000000000000000000000000000000000000000000000000000 8080808080808080808080808080808080808080808080808080808080808080 02020202020202020202020202020202020202020202020202020202020202020303030303030303030303030303030303030303030303030303030303030303 021A7A569E91DBF60581509C7FC946D1003B60C7DEE85299538DB6353538D59574036360E856310CE5D294E8BE33FC807077DC56AC80D95D9CD4DDBD21325EFF73F7 1B84C5567B126440995D3ED5AABA0565D71E1834604819FF9C17F5E9D5DD078F;462779AD4AAD39514614751A71085F2F10E1C7A593E4E030EFB5B8721CE55B0B;989C0B76CB563971FDC9BEF31EC06C3560F3249D6EE9E5D83C57625596E05F6F 8080808080808080808080808080808080808080808080808080808080808080;8181818181818181818181818181818181818181818181818181818181818181;8282828282828282828282828282828282828282828282828282828282828282 024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D076602531FE6068134503D2723133227C867AC8FA6C83C537E9A44C3C5BDBDCB1FE337;0362C0A046DACCE86DDD0343C6D3C7C79C2208BA0D9C9CF24A6D046D21D21F90F703F006A18D5653C4EDF5391FF23A61F03FF83D237E880EE61187FA9F379A028E0A;03F991F944D1E1954A7FC8B9BF62E0D78F015F4C07762D505E20E6C45260A3661B0256B328B30C8BF5839E24058747879408BDB36241DC9C2E7C619FAA12B2920967 Secret key is zero
8 6 FEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFDB9ADDBE5AE479F3ABED15D8BCF354040 8080808080808080808080808080808080808080808080808080808080808080 02020202020202020202020202020202020202020202020202020202020202020303030303030303030303030303030303030303030303030303030303030303 000000000000000000000000000000000000000000000000000000000000000000036360E856310CE5D294E8BE33FC807077DC56AC80D95D9CD4DDBD21325EFF73F7 1B84C5567B126440995D3ED5AABA0565D71E1834604819FF9C17F5E9D5DD078F;462779AD4AAD39514614751A71085F2F10E1C7A593E4E030EFB5B8721CE55B0B;989C0B76CB563971FDC9BEF31EC06C3560F3249D6EE9E5D83C57625596E05F6F 8080808080808080808080808080808080808080808080808080808080808080;8181818181818181818181818181818181818181818181818181818181818181;8282828282828282828282828282828282828282828282828282828282828282 024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D076602531FE6068134503D2723133227C867AC8FA6C83C537E9A44C3C5BDBDCB1FE337;0362C0A046DACCE86DDD0343C6D3C7C79C2208BA0D9C9CF24A6D046D21D21F90F703F006A18D5653C4EDF5391FF23A61F03FF83D237E880EE61187FA9F379A028E0A;03F991F944D1E1954A7FC8B9BF62E0D78F015F4C07762D505E20E6C45260A3661B0256B328B30C8BF5839E24058747879408BDB36241DC9C2E7C619FAA12B2920967 Aggregate nonce first half is all zeros
9 7 FEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFDB9ADDBE5AE479F3ABED15D8BCF354040 8080808080808080808080808080808080808080808080808080808080808080 02020202020202020202020202020202020202020202020202020202020202020303030303030303030303030303030303030303030303030303030303030303 021A7A569E91DBF60581509C7FC946D1003B60C7DEE85299538DB6353538D59574020000000000000000000000000000000000000000000000000000000000000000 1B84C5567B126440995D3ED5AABA0565D71E1834604819FF9C17F5E9D5DD078F;462779AD4AAD39514614751A71085F2F10E1C7A593E4E030EFB5B8721CE55B0B;989C0B76CB563971FDC9BEF31EC06C3560F3249D6EE9E5D83C57625596E05F6F 8080808080808080808080808080808080808080808080808080808080808080;8181818181818181818181818181818181818181818181818181818181818181;8282828282828282828282828282828282828282828282828282828282828282 024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D076602531FE6068134503D2723133227C867AC8FA6C83C537E9A44C3C5BDBDCB1FE337;0362C0A046DACCE86DDD0343C6D3C7C79C2208BA0D9C9CF24A6D046D21D21F90F703F006A18D5653C4EDF5391FF23A61F03FF83D237E880EE61187FA9F379A028E0A;03F991F944D1E1954A7FC8B9BF62E0D78F015F4C07762D505E20E6C45260A3661B0256B328B30C8BF5839E24058747879408BDB36241DC9C2E7C619FAA12B2920967 Aggregate nonce second half is not on the curve
10 8 FEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFDB9ADDBE5AE479F3ABED15D8BCF354040 8080808080808080808080808080808080808080808080808080808080808080 02020202020202020202020202020202020202020202020202020202020202020303030303030303030303030303030303030303030303030303030303030303 021A7A569E91DBF60581509C7FC946D1003B60C7DEE85299538DB6353538D5957402FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC30 1B84C5567B126440995D3ED5AABA0565D71E1834604819FF9C17F5E9D5DD078F;462779AD4AAD39514614751A71085F2F10E1C7A593E4E030EFB5B8721CE55B0B;989C0B76CB563971FDC9BEF31EC06C3560F3249D6EE9E5D83C57625596E05F6F 8080808080808080808080808080808080808080808080808080808080808080;8181818181818181818181818181818181818181818181818181818181818181;8282828282828282828282828282828282828282828282828282828282828282 024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D076602531FE6068134503D2723133227C867AC8FA6C83C537E9A44C3C5BDBDCB1FE337;0362C0A046DACCE86DDD0343C6D3C7C79C2208BA0D9C9CF24A6D046D21D21F90F703F006A18D5653C4EDF5391FF23A61F03FF83D237E880EE61187FA9F379A028E0A;03F991F944D1E1954A7FC8B9BF62E0D78F015F4C07762D505E20E6C45260A3661B0256B328B30C8BF5839E24058747879408BDB36241DC9C2E7C619FAA12B2920967 Aggregate nonce second half exceeds field size
+8
View File
@@ -0,0 +1,8 @@
index,internal_secret_key,tweak,is_xonly,expected_result,expected_secret_key,expected_pubkey,comment
0,FEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFDB9ADDBE5AE479F3ABED15D8BCF354040,0202020202020202020202020202020202020202020202020202020202020202,FALSE,TRUE,0101010101010101010101010101010101010101010101010101010101010101,031B84C5567B126440995D3ED5AABA0565D71E1834604819FF9C17F5E9D5DD078F,Plain tweak of an even-y key
1,0101010101010101010101010101010101010101010101010101010101010101,0202020202020202020202020202020202020202020202020202020202020202,FALSE,TRUE,0303030303030303030303030303030303030303030303030303030303030303,02531FE6068134503D2723133227C867AC8FA6C83C537E9A44C3C5BDBDCB1FE337,Plain tweak of an odd-y key
2,FEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFDB9ADDBE5AE479F3ABED15D8BCF354040,0202020202020202020202020202020202020202020202020202020202020202,TRUE,TRUE,0101010101010101010101010101010101010101010101010101010101010101,031B84C5567B126440995D3ED5AABA0565D71E1834604819FF9C17F5E9D5DD078F,X-only tweak of an even-y key
3,0101010101010101010101010101010101010101010101010101010101010101,0202020202020202020202020202020202020202020202020202020202020202,TRUE,TRUE,0101010101010101010101010101010101010101010101010101010101010101,031B84C5567B126440995D3ED5AABA0565D71E1834604819FF9C17F5E9D5DD078F,X-only tweak of an odd-y key
4,0101010101010101010101010101010101010101010101010101010101010101,FEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFDB9ADDBE5AE479F3ABED15D8BCF354040,FALSE,FALSE,,,Plain tweak resulting in the point at infinity
5,0101010101010101010101010101010101010101010101010101010101010101,0101010101010101010101010101010101010101010101010101010101010101,TRUE,FALSE,,,X-only tweak resulting in the point at infinity
6,FEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFDB9ADDBE5AE479F3ABED15D8BCF354040,FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141,FALSE,FALSE,,,Tweak = n (out of range)
1 index internal_secret_key tweak is_xonly expected_result expected_secret_key expected_pubkey comment
2 0 FEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFDB9ADDBE5AE479F3ABED15D8BCF354040 0202020202020202020202020202020202020202020202020202020202020202 FALSE TRUE 0101010101010101010101010101010101010101010101010101010101010101 031B84C5567B126440995D3ED5AABA0565D71E1834604819FF9C17F5E9D5DD078F Plain tweak of an even-y key
3 1 0101010101010101010101010101010101010101010101010101010101010101 0202020202020202020202020202020202020202020202020202020202020202 FALSE TRUE 0303030303030303030303030303030303030303030303030303030303030303 02531FE6068134503D2723133227C867AC8FA6C83C537E9A44C3C5BDBDCB1FE337 Plain tweak of an odd-y key
4 2 FEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFDB9ADDBE5AE479F3ABED15D8BCF354040 0202020202020202020202020202020202020202020202020202020202020202 TRUE TRUE 0101010101010101010101010101010101010101010101010101010101010101 031B84C5567B126440995D3ED5AABA0565D71E1834604819FF9C17F5E9D5DD078F X-only tweak of an even-y key
5 3 0101010101010101010101010101010101010101010101010101010101010101 0202020202020202020202020202020202020202020202020202020202020202 TRUE TRUE 0101010101010101010101010101010101010101010101010101010101010101 031B84C5567B126440995D3ED5AABA0565D71E1834604819FF9C17F5E9D5DD078F X-only tweak of an odd-y key
6 4 0101010101010101010101010101010101010101010101010101010101010101 FEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFDB9ADDBE5AE479F3ABED15D8BCF354040 FALSE FALSE Plain tweak resulting in the point at infinity
7 5 0101010101010101010101010101010101010101010101010101010101010101 0101010101010101010101010101010101010101010101010101010101010101 TRUE FALSE X-only tweak resulting in the point at infinity
8 6 FEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFDB9ADDBE5AE479F3ABED15D8BCF354040 FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 FALSE FALSE Tweak = n (out of range)
+18
View File
@@ -0,0 +1,18 @@
index,pubkeys,messages,sig,expected_result,comment
0,1B84C5567B126440995D3ED5AABA0565D71E1834604819FF9C17F5E9D5DD078F;462779AD4AAD39514614751A71085F2F10E1C7A593E4E030EFB5B8721CE55B0B,8080808080808080808080808080808080808080808080808080808080808080;8181818181818181818181818181818181818181818181818181818181818181,5485290A0E23A1912901F3CD0F2F8884CF8528B089F86ACFE5184670ECB8F06D64BA611B7C1137D3DCBECDD845D34196AB9988932DC4CC92F882712234DA6A0C,TRUE,Two-signer valid aggregate
1,1B84C5567B126440995D3ED5AABA0565D71E1834604819FF9C17F5E9D5DD078F;462779AD4AAD39514614751A71085F2F10E1C7A593E4E030EFB5B8721CE55B0B;989C0B76CB563971FDC9BEF31EC06C3560F3249D6EE9E5D83C57625596E05F6F,8080808080808080808080808080808080808080808080808080808080808080;8181818181818181818181818181818181818181818181818181818181818181;8282828282828282828282828282828282828282828282828282828282828282,2F02469D98943B63C85904CA5F34AD0525B650A79B55D4C5730FBE1AAC7C004FB113653059775BF839817EBAA37096FA331DD9A4BAB3AD57B9A30B6BA467527E,TRUE,Three-signer valid aggregate
2,1B84C5567B126440995D3ED5AABA0565D71E1834604819FF9C17F5E9D5DD078F;462779AD4AAD39514614751A71085F2F10E1C7A593E4E030EFB5B8721CE55B0B,8080808080808080808080808080808080808080808080808080808080808080;FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF,5485290A0E23A1912901F3CD0F2F8884CF8528B089F86ACFE5184670ECB8F06D64BA611B7C1137D3DCBECDD845D34196AB9988932DC4CC92F882712234DA6A0C,FALSE,Wrong message for second signer
3,462779AD4AAD39514614751A71085F2F10E1C7A593E4E030EFB5B8721CE55B0B;1B84C5567B126440995D3ED5AABA0565D71E1834604819FF9C17F5E9D5DD078F,8080808080808080808080808080808080808080808080808080808080808080;8181818181818181818181818181818181818181818181818181818181818181,5485290A0E23A1912901F3CD0F2F8884CF8528B089F86ACFE5184670ECB8F06D64BA611B7C1137D3DCBECDD845D34196AB9988932DC4CC92F882712234DA6A0C,FALSE,Pubkey order swapped
4,1B84C5567B126440995D3ED5AABA0565D71E1834604819FF9C17F5E9D5DD078F,8080808080808080808080808080808080808080808080808080808080808080,5485290A0E23A1912901F3CD0F2F8884CF8528B089F86ACFE5184670ECB8F06D64BA611B7C1137D3DCBECDD845D34196AB9988932DC4CC92F882712234DA6A0C,FALSE,Dropped second signer
5,1B84C5567B126440995D3ED5AABA0565D71E1834604819FF9C17F5E9D5DD078F;462779AD4AAD39514614751A71085F2F10E1C7A593E4E030EFB5B8721CE55B0B,8080808080808080808080808080808080808080808080808080808080808080;8181818181818181818181818181818181818181818181818181818181818181,5485290A0E23A1912901F3CD0F2F8884CF8528B089F86ACFE5184670ECB8F06D64BA611B7C1137D3DCBECDD845D34196AB9988932DC4CC92F882712234DA6A0D,FALSE,Tampered s (bit flip)
6,1B84C5567B126440995D3ED5AABA0565D71E1834604819FF9C17F5E9D5DD078F;462779AD4AAD39514614751A71085F2F10E1C7A593E4E030EFB5B8721CE55B0B,8080808080808080808080808080808080808080808080808080808080808080;8181818181818181818181818181818181818181818181818181818181818181,5485290A0E23A1912901F3CD0F2F8884CF8528B089F86ACFE5184670ECB8F06D0000000000000000000000000000000000000000000000000000000000000000,FALSE,"s = 0 (in range, wrong)"
7,1B84C5567B126440995D3ED5AABA0565D71E1834604819FF9C17F5E9D5DD078F;462779AD4AAD39514614751A71085F2F10E1C7A593E4E030EFB5B8721CE55B0B,8080808080808080808080808080808080808080808080808080808080808080;8181818181818181818181818181818181818181818181818181818181818181,5485290A0E23A1912901F3CD0F2F8884CF8528B089F86ACFE5184670ECB8F06DFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141,FALSE,s = n (out of range)
8,1B84C5567B126440995D3ED5AABA0565D71E1834604819FF9C17F5E9D5DD078F;462779AD4AAD39514614751A71085F2F10E1C7A593E4E030EFB5B8721CE55B0B,8080808080808080808080808080808080808080808080808080808080808080;8181818181818181818181818181818181818181818181818181818181818181,5485290A0E23A1912901F3CD0F2F8884CF8528B089F86ACFE5184670ECB8F06DFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF,FALSE,s = 2^256 - 1
9,1B84C5567B126440995D3ED5AABA0565D71E1834604819FF9C17F5E9D5DD078F;462779AD4AAD39514614751A71085F2F10E1C7A593E4E030EFB5B8721CE55B0B,8080808080808080808080808080808080808080808080808080808080808080;8181818181818181818181818181818181818181818181818181818181818181,030303030303030303030303030303030303030303030303030303030303030364BA611B7C1137D3DCBECDD845D34196AB9988932DC4CC92F882712234DA6A0C,FALSE,R x-coord not on curve
10,0303030303030303030303030303030303030303030303030303030303030303;462779AD4AAD39514614751A71085F2F10E1C7A593E4E030EFB5B8721CE55B0B,8080808080808080808080808080808080808080808080808080808080808080;8181818181818181818181818181818181818181818181818181818181818181,5485290A0E23A1912901F3CD0F2F8884CF8528B089F86ACFE5184670ECB8F06D64BA611B7C1137D3DCBECDD845D34196AB9988932DC4CC92F882712234DA6A0C,FALSE,Public key not on curve
11,1B84C5567B126440995D3ED5AABA0565D71E1834604819FF9C17F5E9D5DD078F;462779AD4AAD39514614751A71085F2F10E1C7A593E4E030EFB5B8721CE55B0B,8080808080808080808080808080808080808080808080808080808080808080;8181818181818181818181818181818181818181818181818181818181818181,5485290A0E23A1912901F3CD0F2F8884CF8528B089F86ACFE5184670ECB8F06D64BA611B7C1137D3DCBECDD845D34196AB9988932DC4CC92F882712234DA6A,FALSE,Signature too short
12,1B84C5567B126440995D3ED5AABA0565D71E1834604819FF9C17F5E9D5DD078F;462779AD4AAD39514614751A71085F2F10E1C7A593E4E030EFB5B8721CE55B0B,8080808080808080808080808080808080808080808080808080808080808080;8181818181818181818181818181818181818181818181818181818181818181,5485290A0E23A1912901F3CD0F2F8884CF8528B089F86ACFE5184670ECB8F06D64BA611B7C1137D3DCBECDD845D34196AB9988932DC4CC92F882712234DA6A0C00,FALSE,Signature too long
13,1B84C5567B126440995D3ED5AABA0565D71E1834604819FF9C17F5E9D5DD078F;462779AD4AAD39514614751A71085F2F10E1C7A593E4E030EFB5B8721CE55B0B,8080808080808080808080808080808080808080808080808080808080808080,5485290A0E23A1912901F3CD0F2F8884CF8528B089F86ACFE5184670ECB8F06D64BA611B7C1137D3DCBECDD845D34196AB9988932DC4CC92F882712234DA6A0C,FALSE,Pubkey/message count mismatch
14,,,5485290A0E23A1912901F3CD0F2F8884CF8528B089F86ACFE5184670ECB8F06D64BA611B7C1137D3DCBECDD845D34196AB9988932DC4CC92F882712234DA6A0C,FALSE,Empty signer list
15,1B84C5567B126440995D3ED5AABA0565D71E1834604819FF9C17F5E9D5DD078F;462779AD4AAD39514614751A71085F2F10E1C7A593E4E030EFB5B8721CE55B0B,80808080808080808080808080808080808080808080808080808080808080;8181818181818181818181818181818181818181818181818181818181818181,9FBE8AD98EB962605C09D4541BF3EA4F95323236E3D729824E946376160F703175198F905A3DA7CDF8B558F83DBE9D0BB43BA9BD4072DDEB4204ED49C48DBA2E,FALSE,Message too short
16,1B84C5567B126440995D3ED5AABA0565D71E1834604819FF9C17F5E9D5DD078F;462779AD4AAD39514614751A71085F2F10E1C7A593E4E030EFB5B8721CE55B0B,808080808080808080808080808080808080808080808080808080808080808000;8181818181818181818181818181818181818181818181818181818181818181,9FBE8AD98EB962605C09D4541BF3EA4F95323236E3D729824E946376160F70315EFE5CFEF5DE1A0B3B0A26CD6C5C4BC088A703C78AD72ADFB9E8F619122CAA77,FALSE,Message too long
1 index pubkeys messages sig expected_result comment
2 0 1B84C5567B126440995D3ED5AABA0565D71E1834604819FF9C17F5E9D5DD078F;462779AD4AAD39514614751A71085F2F10E1C7A593E4E030EFB5B8721CE55B0B 8080808080808080808080808080808080808080808080808080808080808080;8181818181818181818181818181818181818181818181818181818181818181 5485290A0E23A1912901F3CD0F2F8884CF8528B089F86ACFE5184670ECB8F06D64BA611B7C1137D3DCBECDD845D34196AB9988932DC4CC92F882712234DA6A0C TRUE Two-signer valid aggregate
3 1 1B84C5567B126440995D3ED5AABA0565D71E1834604819FF9C17F5E9D5DD078F;462779AD4AAD39514614751A71085F2F10E1C7A593E4E030EFB5B8721CE55B0B;989C0B76CB563971FDC9BEF31EC06C3560F3249D6EE9E5D83C57625596E05F6F 8080808080808080808080808080808080808080808080808080808080808080;8181818181818181818181818181818181818181818181818181818181818181;8282828282828282828282828282828282828282828282828282828282828282 2F02469D98943B63C85904CA5F34AD0525B650A79B55D4C5730FBE1AAC7C004FB113653059775BF839817EBAA37096FA331DD9A4BAB3AD57B9A30B6BA467527E TRUE Three-signer valid aggregate
4 2 1B84C5567B126440995D3ED5AABA0565D71E1834604819FF9C17F5E9D5DD078F;462779AD4AAD39514614751A71085F2F10E1C7A593E4E030EFB5B8721CE55B0B 8080808080808080808080808080808080808080808080808080808080808080;FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF 5485290A0E23A1912901F3CD0F2F8884CF8528B089F86ACFE5184670ECB8F06D64BA611B7C1137D3DCBECDD845D34196AB9988932DC4CC92F882712234DA6A0C FALSE Wrong message for second signer
5 3 462779AD4AAD39514614751A71085F2F10E1C7A593E4E030EFB5B8721CE55B0B;1B84C5567B126440995D3ED5AABA0565D71E1834604819FF9C17F5E9D5DD078F 8080808080808080808080808080808080808080808080808080808080808080;8181818181818181818181818181818181818181818181818181818181818181 5485290A0E23A1912901F3CD0F2F8884CF8528B089F86ACFE5184670ECB8F06D64BA611B7C1137D3DCBECDD845D34196AB9988932DC4CC92F882712234DA6A0C FALSE Pubkey order swapped
6 4 1B84C5567B126440995D3ED5AABA0565D71E1834604819FF9C17F5E9D5DD078F 8080808080808080808080808080808080808080808080808080808080808080 5485290A0E23A1912901F3CD0F2F8884CF8528B089F86ACFE5184670ECB8F06D64BA611B7C1137D3DCBECDD845D34196AB9988932DC4CC92F882712234DA6A0C FALSE Dropped second signer
7 5 1B84C5567B126440995D3ED5AABA0565D71E1834604819FF9C17F5E9D5DD078F;462779AD4AAD39514614751A71085F2F10E1C7A593E4E030EFB5B8721CE55B0B 8080808080808080808080808080808080808080808080808080808080808080;8181818181818181818181818181818181818181818181818181818181818181 5485290A0E23A1912901F3CD0F2F8884CF8528B089F86ACFE5184670ECB8F06D64BA611B7C1137D3DCBECDD845D34196AB9988932DC4CC92F882712234DA6A0D FALSE Tampered s (bit flip)
8 6 1B84C5567B126440995D3ED5AABA0565D71E1834604819FF9C17F5E9D5DD078F;462779AD4AAD39514614751A71085F2F10E1C7A593E4E030EFB5B8721CE55B0B 8080808080808080808080808080808080808080808080808080808080808080;8181818181818181818181818181818181818181818181818181818181818181 5485290A0E23A1912901F3CD0F2F8884CF8528B089F86ACFE5184670ECB8F06D0000000000000000000000000000000000000000000000000000000000000000 FALSE s = 0 (in range, wrong)
9 7 1B84C5567B126440995D3ED5AABA0565D71E1834604819FF9C17F5E9D5DD078F;462779AD4AAD39514614751A71085F2F10E1C7A593E4E030EFB5B8721CE55B0B 8080808080808080808080808080808080808080808080808080808080808080;8181818181818181818181818181818181818181818181818181818181818181 5485290A0E23A1912901F3CD0F2F8884CF8528B089F86ACFE5184670ECB8F06DFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 FALSE s = n (out of range)
10 8 1B84C5567B126440995D3ED5AABA0565D71E1834604819FF9C17F5E9D5DD078F;462779AD4AAD39514614751A71085F2F10E1C7A593E4E030EFB5B8721CE55B0B 8080808080808080808080808080808080808080808080808080808080808080;8181818181818181818181818181818181818181818181818181818181818181 5485290A0E23A1912901F3CD0F2F8884CF8528B089F86ACFE5184670ECB8F06DFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF FALSE s = 2^256 - 1
11 9 1B84C5567B126440995D3ED5AABA0565D71E1834604819FF9C17F5E9D5DD078F;462779AD4AAD39514614751A71085F2F10E1C7A593E4E030EFB5B8721CE55B0B 8080808080808080808080808080808080808080808080808080808080808080;8181818181818181818181818181818181818181818181818181818181818181 030303030303030303030303030303030303030303030303030303030303030364BA611B7C1137D3DCBECDD845D34196AB9988932DC4CC92F882712234DA6A0C FALSE R x-coord not on curve
12 10 0303030303030303030303030303030303030303030303030303030303030303;462779AD4AAD39514614751A71085F2F10E1C7A593E4E030EFB5B8721CE55B0B 8080808080808080808080808080808080808080808080808080808080808080;8181818181818181818181818181818181818181818181818181818181818181 5485290A0E23A1912901F3CD0F2F8884CF8528B089F86ACFE5184670ECB8F06D64BA611B7C1137D3DCBECDD845D34196AB9988932DC4CC92F882712234DA6A0C FALSE Public key not on curve
13 11 1B84C5567B126440995D3ED5AABA0565D71E1834604819FF9C17F5E9D5DD078F;462779AD4AAD39514614751A71085F2F10E1C7A593E4E030EFB5B8721CE55B0B 8080808080808080808080808080808080808080808080808080808080808080;8181818181818181818181818181818181818181818181818181818181818181 5485290A0E23A1912901F3CD0F2F8884CF8528B089F86ACFE5184670ECB8F06D64BA611B7C1137D3DCBECDD845D34196AB9988932DC4CC92F882712234DA6A FALSE Signature too short
14 12 1B84C5567B126440995D3ED5AABA0565D71E1834604819FF9C17F5E9D5DD078F;462779AD4AAD39514614751A71085F2F10E1C7A593E4E030EFB5B8721CE55B0B 8080808080808080808080808080808080808080808080808080808080808080;8181818181818181818181818181818181818181818181818181818181818181 5485290A0E23A1912901F3CD0F2F8884CF8528B089F86ACFE5184670ECB8F06D64BA611B7C1137D3DCBECDD845D34196AB9988932DC4CC92F882712234DA6A0C00 FALSE Signature too long
15 13 1B84C5567B126440995D3ED5AABA0565D71E1834604819FF9C17F5E9D5DD078F;462779AD4AAD39514614751A71085F2F10E1C7A593E4E030EFB5B8721CE55B0B 8080808080808080808080808080808080808080808080808080808080808080 5485290A0E23A1912901F3CD0F2F8884CF8528B089F86ACFE5184670ECB8F06D64BA611B7C1137D3DCBECDD845D34196AB9988932DC4CC92F882712234DA6A0C FALSE Pubkey/message count mismatch
16 14 5485290A0E23A1912901F3CD0F2F8884CF8528B089F86ACFE5184670ECB8F06D64BA611B7C1137D3DCBECDD845D34196AB9988932DC4CC92F882712234DA6A0C FALSE Empty signer list
17 15 1B84C5567B126440995D3ED5AABA0565D71E1834604819FF9C17F5E9D5DD078F;462779AD4AAD39514614751A71085F2F10E1C7A593E4E030EFB5B8721CE55B0B 80808080808080808080808080808080808080808080808080808080808080;8181818181818181818181818181818181818181818181818181818181818181 9FBE8AD98EB962605C09D4541BF3EA4F95323236E3D729824E946376160F703175198F905A3DA7CDF8B558F83DBE9D0BB43BA9BD4072DDEB4204ED49C48DBA2E FALSE Message too short
18 16 1B84C5567B126440995D3ED5AABA0565D71E1834604819FF9C17F5E9D5DD078F;462779AD4AAD39514614751A71085F2F10E1C7A593E4E030EFB5B8721CE55B0B 808080808080808080808080808080808080808080808080808080808080808000;8181818181818181818181818181818181818181818181818181818181818181 9FBE8AD98EB962605C09D4541BF3EA4F95323236E3D729824E946376160F70315EFE5CFEF5DE1A0B3B0A26CD6C5C4BC088A703C78AD72ADFB9E8F619122CAA77 FALSE Message too long