From 7d2b7940e91729acbb41de5538ee4b419cc59d41 Mon Sep 17 00:00:00 2001
From: Conrado Gouvea
Date: Fri, 1 Jul 2022 18:49:51 -0300
Subject: [PATCH 01/37] WIP
---
zip-frost.rst | 243 ++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 243 insertions(+)
create mode 100644 zip-frost.rst
diff --git a/zip-frost.rst b/zip-frost.rst
new file mode 100644
index 00000000..3a342d46
--- /dev/null
+++ b/zip-frost.rst
@@ -0,0 +1,243 @@
+::
+
+ ZIP: Unassigned {numbers are assigned by ZIP editors}
+ Title: FROST for Spend Authorization Signatures
+ Owners: Chelsea Komlo
+ Conrado Gouvea
+ Deirdre Connolly
+ Credits: First Credited
+ ...
+ Status: Draft
+ Category: Wallet
+ Created: 2022-08-dd
+ License: {usually MIT}
+ Pull-Request:
+
+
+Terminology
+===========
+
+{Edit this to reflect the key words that are actually used.}
+The key words "MUST", "MUST NOT", "SHOULD", and "MAY" in this document are to
+be interpreted as described in RFC 2119. [#RFC2119]_
+
+The terms below are to be interpreted as follows:
+
+{Term to be defined}
+ {Definition.}
+{Another term}
+ {Definition.}
+
+
+Abstract
+========
+
+This proposal defines the usage of FROST [#FROST]_, a threshold signature scheme,
+to generate signatures compatible with spend authorization signatures in the Zcash
+protocol, for the Sapling and Orchard network upgrades.
+
+
+Motivation
+==========
+
+In the Zcash protocol, Spend Authorization Signatures are employed to effectively
+authorize a transaction. The ability to generate these signatures with the user's
+private key is what effectively allows the to spend funds.
+
+This is security-critical step, since anyone who obtains access to the private
+key will be able to spend the user's funds. For this reason, one interesting
+possibility is to require multiple entities to allow the transaction to go through.
+This can be accomplished with threshold signatures, where... (TODO: explain)
+
+FROST is one of such threshold signatures protocols. (TODO: more about FROST)
+
+
+Requirements
+============
+
+- All signatures generated by following this ZIP MUST be verified successfully
+ as Sapling or Orchard spend authorization signatures.
+- (Must keep same security requirements from the spec?)
+- (Randomizer security requirements?)
+
+
+Non-requirements
+================
+
+- (Talk about how proof generation is not covered?)
+
+
+Specification
+=============
+
+Re-randomized FROST
+-------------------
+
+To add re-randomization to FROST, follow the specification [#FROST]_ with the
+following modifications:
+
+In Round Two, the Coordinator also generates a random scalar α by following the
+RedDSA.GenRandom procedure and sends it to each signer along with the message
+and the set of signing commitments.
+
+The `sign` function is changed to receive α and incorporate it into the
+computation of `sig_share`. It is specified as the following: ::
+
+ Inputs:
+ - identifier, Identifier i of the signer. Note identifier will never equal 0.
+ - sk_i, Signer secret key share, a Scalar.
+ - group_public_key, public key corresponding to the group signing key,
+ an Element in G.
+ - nonce_i, pair of Scalar values (hiding_nonce, binding_nonce) generated in
+ round one.
+ - msg, the message to be signed (sent by the Coordinator).
+ - commitment_list =
+ [(j, hiding_nonce_commitment_j, binding_nonce_commitment_j), ...], a
+ list of commitments issued in Round 1 by each signer and sent by the Coordinator.
+ Each element in the list indicates the signer identifier j and their two commitment
+ Element values (hiding_nonce_commitment_j, binding_nonce_commitment_j).s
+ This list MUST be sorted in ascending order by signer identifier.
+ - alpha, the randomizer Scalar.
+
+ Outputs: a Scalar value representing the signature share
+
+ def sign(identifier, sk_i, group_public_key, nonce_i, msg, commitment_list, alpha):
+ # Encode the commitment list
+ encoded_commitments = encode_group_commitment_list(commitment_list)
+
+ # Compute the binding factor
+ binding_factor = compute_binding_factor(encoded_commitments, msg)
+
+ # Compute the group commitment
+ group_commitment = compute_group_commitment(commitment_list, binding_factor)
+
+ # Compute Lagrange coefficient
+ participant_list = participants_from_commitment_list(commitment_list)
+ lambda_i = derive_lagrange_coefficient(identifier, participant_list)
+
+ # Compute the per-message challenge
+ challenge = compute_challenge(group_commitment, group_public_key, msg)
+
+ # Compute the signature share
+ (hiding_nonce, binding_nonce) = nonce_i
+ sig_share = hiding_nonce + (binding_nonce * binding_factor) + (lambda_i * (sk_i + alpha / len(commitment_list)) * challenge)
+
+ return sig_share
+
+
+When the Coordinator outputs the signature, the Coordinator must also compute and
+output G.ScalarBaseMult(α).
+
+
+Ciphersuites
+------------
+
+FROST(RedJubjub, BLAKE2b-512)
+'''''''''''''''''''''''''''''
+
+This ciphersuite uses Jubjub for the Group and BLAKE2b-512 for the Hash function `H`
+meant to produce signatures indistinguishable from RedJubjub Sapling Spend
+Authorization Signatures as specified in [#protocol]_.
+
+- Group: Jubjub [#protocol]_
+
+ - Order: 6554484396890773809930967563523245729705921265872317281365359162392183254199 (see [#protocol]_)
+ - Identity: as defined in [#protocol]_
+ - RandomScalar: Implemented by generating a random 64-byte string and invoking
+ DeserializeScalar on the result
+ - RandomNonZeroScalar: Implemented by generating a random 32-byte string that
+ is not equal to the all-zero string and invoking DeserializeScalar on the result.
+ - SerializeElement(P): Implemented as `LEBS2OSP_{ℓG}(repr_G(P))`, where LEBS2OSP and repr_G
+ are defined in [#protocol]_
+ [TODO: how to specify this? It's awkward to do this way, should we expand
+ the definitions?]
+ - DeserializeElement: Implemented as `abst_G(LEOS2BSP_{ℓG}(P))`, where LEOS2BSP and abst_G
+ are defined in [#protocol]_
+ - SerializeScalar: Implemented by outputting the little-endian 32-byte encoding
+ of the Scalar value.
+ - DeserializeScalar: Implemented by attempting to deserialize a Scalar from a
+ 32-byte string. This function can fail if the input does not represent a Scalar
+ between the value 0 and G.Order() - 1.
+
+- Hash (`H`): BLAKE2b-512 [#BLAKE]_ (BLAKE2b with 512-bit output and 16-byte personalization string),
+ and Nh = 64.
+
+ - H1(m): Implemented by computing BLAKE2b-512("FROST_RedJubjubRh", m), interpreting
+ the 64 bytes as a little-endian integer, and reducing the resulting integer
+ modulo L = 6554484396890773809930967563523245729705921265872317281365359162392183254199.
+ [TODO: what context string to use?]
+ - H2(m): LEOS2IP_{ℓ_H}(H(m)) (mod G.Order())
+ / Implemented by computing BLAKE2b-512("Zcash_RedJubjubH ", m), interpreting
+ the 64 bytes as a little-endian integer, and reducing the resulting integer
+ modulo L = 6554484396890773809930967563523245729705921265872317281365359162392183254199.
+ [TODO: use original expression or expand the definitions as above?]
+ - H3(m): Implemented by computing BLAKE2b-512("FROST_RedJubjubDi", m)
+ [TODO: what context string to use?]
+ - H4(m): Implemented by computing BLAKE2b-512("FROST_RedJubjubNo", m), interpreting
+ the 64 bytes as a little-endian integer, and reducing the resulting integer
+ modulo L = 6554484396890773809930967563523245729705921265872317281365359162392183254199.
+ [TODO: what context string to use?]
+
+
+FROST(RedPallas, BLAKE2b-512)
+'''''''''''''''''''''''''''''
+
+This ciphersuite uses Pallas for the Group and BLAKE2b-512 for the Hash function `H`
+meant to produce signatures indistinguishable from RedPallas Sapling Spend
+Authorization Signatures as specified in [#protocol]_.
+
+- Group: Pallas [#protocol]_
+
+ - Order: 0x40000000000000000000000000000000224698fc0994a8dd8c46eb2100000001 (see [#protocol]_)
+ - Identity: as defined in [#protocol]_
+ - RandomScalar: Implemented by generating a random 64-byte string and invoking
+ DeserializeScalar on the result
+ - RandomNonZeroScalar: Implemented by generating a random 32-byte string that
+ is not equal to the all-zero string and invoking DeserializeScalar on the result.
+ - SerializeElement(P): Implemented as `LEBS2OSP_{ℓG}(repr_G(P))`, where LEBS2OSP and repr_G
+ are defined in [#protocol]_
+ [TODO: how to specify this? It's awkward to do this way, should we expand
+ the definitions?]
+ - DeserializeElement: Implemented as `abst_G(LEOS2BSP_{ℓG}(P))`, where LEOS2BSP and abst_G
+ are defined in [#protocol]_
+ - SerializeScalar: Implemented by outputting the little-endian 32-byte encoding
+ of the Scalar value.
+ - DeserializeScalar: Implemented by attempting to deserialize a Scalar from a
+ 32-byte string. This function can fail if the input does not represent a Scalar
+ between the value 0 and G.Order() - 1.
+
+- Hash (`H`): BLAKE2b-512 [#BLAKE]_ (BLAKE2b with 512-bit output and 16-byte personalization string),
+ and Nh = 64.
+
+ - H1(m): Implemented by computing BLAKE2b-512("FROST_RedPallasRh", m), interpreting
+ the 64 bytes as a little-endian integer, and reducing the resulting integer
+ modulo L = 0x40000000000000000000000000000000224698fc0994a8dd8c46eb2100000001.
+ [TODO: what context string to use?]
+ - H2(m): LEOS2IP_{ℓ_H}(H(m)) (mod G.Order())
+ / Implemented by computing BLAKE2b-512("Zcash_RedPallasH ", m), interpreting
+ the 64 bytes as a little-endian integer, and reducing the resulting integer
+ modulo L = 0x40000000000000000000000000000000224698fc0994a8dd8c46eb2100000001.
+ [TODO: use original expression or expand the definitions as above?]
+ - H1(m): Implemented by computing BLAKE2b-512("FROST_RedPallasDi", m).
+ [TODO: what context string to use?]
+ - H4(m): Implemented by computing BLAKE2b-512("FROST_RedPallasNo", m), interpreting
+ the 64 bytes as a little-endian integer, and reducing the resulting integer
+ modulo L = 0x40000000000000000000000000000000224698fc0994a8dd8c46eb2100000001.
+ [TODO: what context string to use?]
+
+
+Reference implementation
+========================
+
+TODO: add links to implementation
+
+
+References
+==========
+
+.. [#BLAKE] `BLAKE2: simpler, smaller, fast as MD5 `_
+.. [#RFC2119] `RFC 2119: Key words for use in RFCs to Indicate Requirement Levels `_
+.. [#FROST] `Draft RFC: Two-Round Threshold Schnorr Signatures with FROST `_
+.. [#protocol] `Zcash Protocol Specification, Version 2022.3.0 or later `_
+.. [#protocol-introduction] `Zcash Protocol Specification, Version 2020.1.24. Section 1: Introduction `_
+.. [#zip-0000] `ZIP 0: ZIP Process `_
From 30f9a09904d779d33b90af5b0de481208f47b753 Mon Sep 17 00:00:00 2001
From: Conrado Gouvea
Date: Wed, 6 Jul 2022 14:04:12 -0300
Subject: [PATCH 02/37] make context string the right length
---
zip-frost.rst | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)
diff --git a/zip-frost.rst b/zip-frost.rst
index 3a342d46..c61489af 100644
--- a/zip-frost.rst
+++ b/zip-frost.rst
@@ -162,18 +162,18 @@ Authorization Signatures as specified in [#protocol]_.
- Hash (`H`): BLAKE2b-512 [#BLAKE]_ (BLAKE2b with 512-bit output and 16-byte personalization string),
and Nh = 64.
- - H1(m): Implemented by computing BLAKE2b-512("FROST_RedJubjubRh", m), interpreting
+ - H1(m): Implemented by computing BLAKE2b-512("FROST_RedJubjubR", m), interpreting
the 64 bytes as a little-endian integer, and reducing the resulting integer
modulo L = 6554484396890773809930967563523245729705921265872317281365359162392183254199.
[TODO: what context string to use?]
- H2(m): LEOS2IP_{ℓ_H}(H(m)) (mod G.Order())
- / Implemented by computing BLAKE2b-512("Zcash_RedJubjubH ", m), interpreting
+ / Implemented by computing BLAKE2b-512("Zcash_RedJubjubH", m), interpreting
the 64 bytes as a little-endian integer, and reducing the resulting integer
modulo L = 6554484396890773809930967563523245729705921265872317281365359162392183254199.
[TODO: use original expression or expand the definitions as above?]
- H3(m): Implemented by computing BLAKE2b-512("FROST_RedJubjubDi", m)
[TODO: what context string to use?]
- - H4(m): Implemented by computing BLAKE2b-512("FROST_RedJubjubNo", m), interpreting
+ - H4(m): Implemented by computing BLAKE2b-512("FROST_RedJubjubN", m), interpreting
the 64 bytes as a little-endian integer, and reducing the resulting integer
modulo L = 6554484396890773809930967563523245729705921265872317281365359162392183254199.
[TODO: what context string to use?]
@@ -209,18 +209,18 @@ Authorization Signatures as specified in [#protocol]_.
- Hash (`H`): BLAKE2b-512 [#BLAKE]_ (BLAKE2b with 512-bit output and 16-byte personalization string),
and Nh = 64.
- - H1(m): Implemented by computing BLAKE2b-512("FROST_RedPallasRh", m), interpreting
+ - H1(m): Implemented by computing BLAKE2b-512("FROST_RedPallasR", m), interpreting
the 64 bytes as a little-endian integer, and reducing the resulting integer
modulo L = 0x40000000000000000000000000000000224698fc0994a8dd8c46eb2100000001.
[TODO: what context string to use?]
- H2(m): LEOS2IP_{ℓ_H}(H(m)) (mod G.Order())
- / Implemented by computing BLAKE2b-512("Zcash_RedPallasH ", m), interpreting
+ / Implemented by computing BLAKE2b-512("Zcash_RedPallasH", m), interpreting
the 64 bytes as a little-endian integer, and reducing the resulting integer
modulo L = 0x40000000000000000000000000000000224698fc0994a8dd8c46eb2100000001.
[TODO: use original expression or expand the definitions as above?]
- - H1(m): Implemented by computing BLAKE2b-512("FROST_RedPallasDi", m).
+ - H1(m): Implemented by computing BLAKE2b-512("FROST_RedPallasD", m).
[TODO: what context string to use?]
- - H4(m): Implemented by computing BLAKE2b-512("FROST_RedPallasNo", m), interpreting
+ - H4(m): Implemented by computing BLAKE2b-512("FROST_RedPallasN", m), interpreting
the 64 bytes as a little-endian integer, and reducing the resulting integer
modulo L = 0x40000000000000000000000000000000224698fc0994a8dd8c46eb2100000001.
[TODO: what context string to use?]
From 8cbfddaba5acec6606930bcb69116c4acaa391b4 Mon Sep 17 00:00:00 2001
From: Conrado Gouvea
Date: Fri, 8 Jul 2022 12:20:27 -0300
Subject: [PATCH 03/37] Apply suggestions from code review
Co-authored-by: Deirdre Connolly
---
zip-frost.rst | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/zip-frost.rst b/zip-frost.rst
index c61489af..7d9d5d21 100644
--- a/zip-frost.rst
+++ b/zip-frost.rst
@@ -2,8 +2,8 @@
ZIP: Unassigned {numbers are assigned by ZIP editors}
Title: FROST for Spend Authorization Signatures
- Owners: Chelsea Komlo
- Conrado Gouvea
+ Owners: Conrado Gouvea
+ Chelsea Komlo
Deirdre Connolly
Credits: First Credited
...
@@ -59,7 +59,7 @@ Requirements
as Sapling or Orchard spend authorization signatures.
- (Must keep same security requirements from the spec?)
- (Randomizer security requirements?)
-
+- The party that generates alpha should also be the party that has access to the witness for the proof.
Non-requirements
================
From cf19f23f4f1b6fdf0246e251cc9a8ce6d5a71a64 Mon Sep 17 00:00:00 2001
From: Conrado Gouvea
Date: Fri, 8 Jul 2022 18:28:26 -0300
Subject: [PATCH 04/37] fix formulas; address comments
---
zip-frost.rst | 206 ++++++++++++++++++++++++++++++++++++++++++++------
1 file changed, 183 insertions(+), 23 deletions(-)
diff --git a/zip-frost.rst b/zip-frost.rst
index 7d9d5d21..c50d3cd5 100644
--- a/zip-frost.rst
+++ b/zip-frost.rst
@@ -32,9 +32,10 @@ The terms below are to be interpreted as follows:
Abstract
========
-This proposal defines the usage of FROST [#FROST]_, a threshold signature scheme,
-to generate signatures compatible with spend authorization signatures in the Zcash
-protocol, for the Sapling and Orchard network upgrades.
+This proposal adapts FROST [#FROST]_, a threshold signature scheme,
+to make it unlinkable, which is a requirement for its use in the Zcash protocol.
+The adapted scheme generates signatures compatible with spend authorization
+signatures in the Zcash protocol, for the Sapling and Orchard network upgrades.
Motivation
@@ -42,14 +43,17 @@ Motivation
In the Zcash protocol, Spend Authorization Signatures are employed to effectively
authorize a transaction. The ability to generate these signatures with the user's
-private key is what effectively allows the to spend funds.
+private key is what effectively allows the user to spend funds.
-This is security-critical step, since anyone who obtains access to the private
+This is a security-critical step, since anyone who obtains access to the private
key will be able to spend the user's funds. For this reason, one interesting
possibility is to require multiple entities to allow the transaction to go through.
This can be accomplished with threshold signatures, where... (TODO: explain)
-FROST is one of such threshold signatures protocols. (TODO: more about FROST)
+FROST is one of such threshold signatures protocols. However, it can't be used as-is
+since the Zcash protocol also requires re-randomizing public and private keys
+to ensure unlinkability between transactions. This ZIP specifies a variant of
+FROST with re-randomization support.
Requirements
@@ -57,14 +61,46 @@ Requirements
- All signatures generated by following this ZIP MUST be verified successfully
as Sapling or Orchard spend authorization signatures.
-- (Must keep same security requirements from the spec?)
-- (Randomizer security requirements?)
-- The party that generates alpha should also be the party that has access to the witness for the proof.
+- The signatures generated by following this ZIP should meet the security criteria
+ for Signature with Re-Randomizable Keys as specified in the Zcash protocol [#protocol]_
+- Follow the threat model described below.
+
+Threat Model
+------------
+
+In normal usage, a Zcash user follows multiple steps in order to generate a
+shielded transaction:
+
+- The transaction is created.
+- The transaction is signed with a randomized version of the user's spend
+ authorization private key.
+- The zero-knowledge proof for the transaction is created with the randomizer
+ as an auxiliary (secret) input, among others.
+
+When employing re-randomized FROST as specified in this ZIP, the goal is to
+split the spend authorization private key among multiple participants. This means
+that the proof generation will still be generated be a single participant, likely
+the one that created the transaction in the first place and that will actually
+submit the transaction to the network. Note that his user already controls the
+privacy of the transaction since they are responsible for creating the proof.
+
+This fits well into the "Coordinator" role from the FROST specification. The
+Coordinator is responsible for sending the message to be signed to all participants,
+and to aggregate the signature shares.
+
+With those considerations in mind, the threat model considered in this ZIP is:
+
+- The Coordinator is trusted with the privacy of the transaction (which includes
+ the unlinkability property). A rogue Coordinator will be able to break
+ unlinkability and privacy, but should not be able to create signed transactions
+ without the approval of `MIN_SIGNERS` participants, as specified in FROST.
+
Non-requirements
================
-- (Talk about how proof generation is not covered?)
+- The control of the transaction privacy is not distributed among participants
+ and is trusted with the Coordinator.
Specification
@@ -74,14 +110,58 @@ Re-randomized FROST
-------------------
To add re-randomization to FROST, follow the specification [#FROST]_ with the
-following modifications:
+following modifications.
-In Round Two, the Coordinator also generates a random scalar α by following the
-RedDSA.GenRandom procedure and sends it to each signer along with the message
-and the set of signing commitments.
+Randomizer Generation
+'''''''''''''''''''''
-The `sign` function is changed to receive α and incorporate it into the
-computation of `sig_share`. It is specified as the following: ::
+A new helper function is defined, which computes RedDSA.GenRandom:
+
+::
+
+ randomizer_generate():
+
+ Inputs:
+ - None
+
+ Outputs: randomizer, a Scalar
+
+ def randomizer_generate():
+ randomizer_input = random_bytes(64)
+ return H2(randomizer_input)
+
+
+Binding Factor Computation
+''''''''''''''''''''''''''
+
+The `compute_binding_factor` function is changed to receive the `randomizer`
+as follows: ::
+
+ Inputs:
+ - encoded_commitment_list, an encoded commitment list (as computed
+ by encode_group_commitment_list)
+ - msg, the message to be signed.
+ - randomizer, the randomizer Scalar.
+
+ Outputs: A Scalar representing the binding factor
+
+ def compute_binding_factor(encoded_commitment_list, msg, randomizer):
+ msg_hash = H3(msg)
+ rho_input = encoded_commitment_list || msg_hash || G.SerializeScalar(randomizer)
+ binding_factor = H1(rho_input)
+ return binding_factor
+
+
+Round Two - Signature Share Generation
+''''''''''''''''''''''''''''''''''''''
+
+In Round Two, the Coordinator generates a random scalar `randomizer` by calling
+`randomizer_generate` and sends it to each signer, over a secret channel,
+along with the message and the set of signing commitments. (Note that this differs
+from regular FROST which just requires an authenticated channel.)
+
+The `sign` function is changed to receive `randomizer` and incorporate it into the
+computation of the binding factor. It is specified as the following: ::
Inputs:
- identifier, Identifier i of the signer. Note identifier will never equal 0.
@@ -97,16 +177,19 @@ computation of `sig_share`. It is specified as the following: ::
Each element in the list indicates the signer identifier j and their two commitment
Element values (hiding_nonce_commitment_j, binding_nonce_commitment_j).s
This list MUST be sorted in ascending order by signer identifier.
- - alpha, the randomizer Scalar.
+ - randomizer, the randomizer Scalar.
Outputs: a Scalar value representing the signature share
- def sign(identifier, sk_i, group_public_key, nonce_i, msg, commitment_list, alpha):
+ def sign(identifier, sk_i, group_public_key, nonce_i, msg, commitment_list, randomizer):
+ # Compute the randomized group public key
+ randomized_group_public_key = randomizer * group_public_key
+
# Encode the commitment list
encoded_commitments = encode_group_commitment_list(commitment_list)
# Compute the binding factor
- binding_factor = compute_binding_factor(encoded_commitments, msg)
+ binding_factor = compute_binding_factor(encoded_commitments, msg, randomizer)
# Compute the group commitment
group_commitment = compute_group_commitment(commitment_list, binding_factor)
@@ -116,17 +199,94 @@ computation of `sig_share`. It is specified as the following: ::
lambda_i = derive_lagrange_coefficient(identifier, participant_list)
# Compute the per-message challenge
- challenge = compute_challenge(group_commitment, group_public_key, msg)
+ challenge = compute_challenge(group_commitment, randomized_group_public_key, msg)
# Compute the signature share
(hiding_nonce, binding_nonce) = nonce_i
- sig_share = hiding_nonce + (binding_nonce * binding_factor) + (lambda_i * (sk_i + alpha / len(commitment_list)) * challenge)
+ sig_share = hiding_nonce + (binding_nonce * binding_factor) + (lambda_i * sk_i * challenge)
return sig_share
-When the Coordinator outputs the signature, the Coordinator must also compute and
-output G.ScalarBaseMult(α).
+Signature Share Verification and Aggregation
+''''''''''''''''''''''''''''''''''''''''''''
+
+The `verify_signature_share` is also changed to incorporate the randomizer,
+as follows: ::
+
+ Inputs:
+ - identifier, Identifier i of the signer. Note identifier will never equal 0.
+ - PK_i, the public key for the ith signer, where PK_i = G.ScalarBaseMult(sk_i),
+ an Element in G
+ - comm_i, pair of Element values in G (hiding_nonce_commitment, binding_nonce_commitment)
+ generated in round one from the ith signer.
+ - sig_share_i, a Scalar value indicating the signature share as produced in
+ round two from the ith signer.
+ - commitment_list =
+ [(j, hiding_nonce_commitment_j, binding_nonce_commitment_j), ...], a
+ list of commitments issued in Round 1 by each signer, where each element
+ in the list indicates the signer identifier j and their two commitment
+ Element values (hiding_nonce_commitment_j, binding_nonce_commitment_j).s
+ This list MUST be sorted in ascending order by signer identifier.
+ - group_public_key, public key corresponding to the group signing key,
+ an Element in G.
+ - msg, the message to be signed.
+ - randomizer, the randomizer Scalar.
+
+ Outputs: True if the signature share is valid, and False otherwise.
+
+ def verify_signature_share(identifier, PK_i, comm_i, sig_share_i, commitment_list,
+ group_public_key, msg, randomizer):
+ # Compute the randomized group public key
+ randomized_group_public_key = randomizer * group_public_key
+
+ # Encode the commitment list
+ encoded_commitments = encode_group_commitment_list(commitment_list)
+
+ # Compute the binding factor
+ binding_factor = compute_binding_factor(encoded_commitments, msg, randomizer)
+
+ # Compute the group commitment
+ group_commitment = compute_group_commitment(commitment_list, binding_factor)
+
+ # Compute the commitment share
+ (hiding_nonce_commitment, binding_nonce_commitment) = comm_i
+ comm_share = hiding_nonce_commitment + (binding_nonce_commitment * binding_factor)
+
+ # Compute the challenge
+ challenge = compute_challenge(group_commitment, randomized_group_public_key, msg)
+
+ # Compute Lagrange coefficient
+ participant_list = participants_from_commitment_list(commitment_list)
+ lambda_i = derive_lagrange_coefficient(identifier, participant_list)
+
+ # Compute relation values
+ l = G.ScalarBaseMult(sig_share_i)
+ r = comm_share + ((challenge * lambda_i) * PK_i)
+
+ return l == r
+
+The `aggregate` function is changed to incorporate the randomizer as follows: ::
+
+ Inputs:
+ - group_commitment, the group commitment returned by compute_group_commitment,
+ an Element in G.
+ - sig_shares, a set of signature shares z_i, Scalar values, for each signer,
+ of length NUM_SIGNERS, where MIN_SIGNERS <= NUM_SIGNERS <= MAX_SIGNERS.
+ - group_public_key, public key corresponding to the group signing key,
+ - challenge, the challenge returned by compute_challenge, a Scalar.
+ - randomizer, the randomizer Scalar.
+
+ Outputs:
+ - (R, z), a Schnorr signature consisting of an Element R and Scalar z.
+ - randomized_group_public_key, the randomized group public key
+
+ def aggregate(group_commitment, sig_shares, group_public_key, challenge, randomizer):
+ randomized_group_public_key = randomizer * group_public_key
+ z = 0
+ for z_i in sig_shares:
+ z = z + z_i
+ return (group_commitment, z + randomizer * challenge), randomized_group_public_key
Ciphersuites
From 54dbd57d768eadd9d7ee44d79869fc1d3f691f67 Mon Sep 17 00:00:00 2001
From: Conrado Gouvea
Date: Mon, 11 Jul 2022 11:55:21 -0300
Subject: [PATCH 05/37] fix randomized_group_public_key computation
---
zip-frost.rst | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/zip-frost.rst b/zip-frost.rst
index c50d3cd5..ad5c12be 100644
--- a/zip-frost.rst
+++ b/zip-frost.rst
@@ -183,7 +183,7 @@ computation of the binding factor. It is specified as the following: ::
def sign(identifier, sk_i, group_public_key, nonce_i, msg, commitment_list, randomizer):
# Compute the randomized group public key
- randomized_group_public_key = randomizer * group_public_key
+ randomized_group_public_key = group_public_key + G * randomizer
# Encode the commitment list
encoded_commitments = encode_group_commitment_list(commitment_list)
@@ -282,7 +282,7 @@ The `aggregate` function is changed to incorporate the randomizer as follows: ::
- randomized_group_public_key, the randomized group public key
def aggregate(group_commitment, sig_shares, group_public_key, challenge, randomizer):
- randomized_group_public_key = randomizer * group_public_key
+ randomized_group_public_key = group_public_key + G * randomizer
z = 0
for z_i in sig_shares:
z = z + z_i
From 3a96a2f75b150eec634e50754431c5435103fe69 Mon Sep 17 00:00:00 2001
From: Conrado Gouvea
Date: Fri, 15 Jul 2022 18:38:08 -0300
Subject: [PATCH 06/37] Fixed threat model; cleaning up; added references
---
zip-frost.rst | 88 ++++++++++++++++++++++++---------------------------
1 file changed, 41 insertions(+), 47 deletions(-)
diff --git a/zip-frost.rst b/zip-frost.rst
index ad5c12be..6ef1bab8 100644
--- a/zip-frost.rst
+++ b/zip-frost.rst
@@ -41,14 +41,17 @@ signatures in the Zcash protocol, for the Sapling and Orchard network upgrades.
Motivation
==========
-In the Zcash protocol, Spend Authorization Signatures are employed to effectively
-authorize a transaction. The ability to generate these signatures with the user's
+In the Zcash protocol, Spend Authorization Signatures are employed to authorize
+a transaction. The ability to generate these signatures with the user's
private key is what effectively allows the user to spend funds.
This is a security-critical step, since anyone who obtains access to the private
key will be able to spend the user's funds. For this reason, one interesting
-possibility is to require multiple entities to allow the transaction to go through.
-This can be accomplished with threshold signatures, where... (TODO: explain)
+possibility is to require multiple users to allow the transaction to go through.
+This can be accomplished with threshold signatures, where the private key is
+split between users in a way that a threshold (e.g. 2 out of 3) of them must
+sign the transaction in order to create the final signature. This enables scenarios
+such as users and exchanges sharing custody of a wallet, for example.
FROST is one of such threshold signatures protocols. However, it can't be used as-is
since the Zcash protocol also requires re-randomizing public and private keys
@@ -62,7 +65,7 @@ Requirements
- All signatures generated by following this ZIP MUST be verified successfully
as Sapling or Orchard spend authorization signatures.
- The signatures generated by following this ZIP should meet the security criteria
- for Signature with Re-Randomizable Keys as specified in the Zcash protocol [#protocol]_
+ for Signature with Re-Randomizable Keys as specified in the Zcash protocol [#protocol-concretereddsa]_.
- Follow the threat model described below.
Threat Model
@@ -79,7 +82,7 @@ shielded transaction:
When employing re-randomized FROST as specified in this ZIP, the goal is to
split the spend authorization private key among multiple participants. This means
-that the proof generation will still be generated be a single participant, likely
+that the proof generation will still be generated by a single participant, likely
the one that created the transaction in the first place and that will actually
submit the transaction to the network. Note that his user already controls the
privacy of the transaction since they are responsible for creating the proof.
@@ -94,13 +97,17 @@ With those considerations in mind, the threat model considered in this ZIP is:
the unlinkability property). A rogue Coordinator will be able to break
unlinkability and privacy, but should not be able to create signed transactions
without the approval of `MIN_SIGNERS` participants, as specified in FROST.
+- All key share holders are also trusted with the privacy of the transaction.
+ A future specification may support a scenario where individual key share
+ holders are not trusted with it.
Non-requirements
================
-- The control of the transaction privacy is not distributed among participants
- and is trusted with the Coordinator.
+- This ZIP does not support removing the Coordinator role, as described in #[FROST]_.
+- This ZIP does not prevent key share holders from breaking the privacy
+ of the transaction.
Specification
@@ -211,7 +218,7 @@ computation of the binding factor. It is specified as the following: ::
Signature Share Verification and Aggregation
''''''''''''''''''''''''''''''''''''''''''''
-The `verify_signature_share` is also changed to incorporate the randomizer,
+The `verify_signature_share` is changed to incorporate the randomizer,
as follows: ::
Inputs:
@@ -292,27 +299,23 @@ The `aggregate` function is changed to incorporate the randomizer as follows: ::
Ciphersuites
------------
-FROST(RedJubjub, BLAKE2b-512)
+FROST(Jubjub, BLAKE2b-512)
'''''''''''''''''''''''''''''
This ciphersuite uses Jubjub for the Group and BLAKE2b-512 for the Hash function `H`
meant to produce signatures indistinguishable from RedJubjub Sapling Spend
-Authorization Signatures as specified in [#protocol]_.
+Authorization Signatures as specified in [#protocol-concretespendauthsig]_.
-- Group: Jubjub [#protocol]_
+- Group: Jubjub [#protocol-jubjub]_
- - Order: 6554484396890773809930967563523245729705921265872317281365359162392183254199 (see [#protocol]_)
- - Identity: as defined in [#protocol]_
+ - Order: 6554484396890773809930967563523245729705921265872317281365359162392183254199 (see [#protocol-jubjub]_)
+ - Identity: as defined in [#protocol-jubjub]_
- RandomScalar: Implemented by generating a random 64-byte string and invoking
DeserializeScalar on the result
- RandomNonZeroScalar: Implemented by generating a random 32-byte string that
is not equal to the all-zero string and invoking DeserializeScalar on the result.
- - SerializeElement(P): Implemented as `LEBS2OSP_{ℓG}(repr_G(P))`, where LEBS2OSP and repr_G
- are defined in [#protocol]_
- [TODO: how to specify this? It's awkward to do this way, should we expand
- the definitions?]
- - DeserializeElement: Implemented as `abst_G(LEOS2BSP_{ℓG}(P))`, where LEOS2BSP and abst_G
- are defined in [#protocol]_
+ - SerializeElement(P): Implemented as :math:`\mathsf{repr}_\mathbb{J}(P)` as defined in [#protocol-jubjub]_
+ - DeserializeElement(P): Implemented as :math:`\mathsf{abst}_\mathbb{J}(P)` as defined in [#protocol-jubjub]_
- SerializeScalar: Implemented by outputting the little-endian 32-byte encoding
of the Scalar value.
- DeserializeScalar: Implemented by attempting to deserialize a Scalar from a
@@ -325,41 +328,34 @@ Authorization Signatures as specified in [#protocol]_.
- H1(m): Implemented by computing BLAKE2b-512("FROST_RedJubjubR", m), interpreting
the 64 bytes as a little-endian integer, and reducing the resulting integer
modulo L = 6554484396890773809930967563523245729705921265872317281365359162392183254199.
- [TODO: what context string to use?]
- - H2(m): LEOS2IP_{ℓ_H}(H(m)) (mod G.Order())
- / Implemented by computing BLAKE2b-512("Zcash_RedJubjubH", m), interpreting
+ - H2(m): Implemented by computing BLAKE2b-512("Zcash_RedJubjubH", m), interpreting
the 64 bytes as a little-endian integer, and reducing the resulting integer
modulo L = 6554484396890773809930967563523245729705921265872317281365359162392183254199.
- [TODO: use original expression or expand the definitions as above?]
+ (This is equivalent to :math:`\mathsf{H}^\circledast(m)`, as defined in
+ [#protocol-concretereddsa]_ parametrized with [#protocol-jubjub]_.)
- H3(m): Implemented by computing BLAKE2b-512("FROST_RedJubjubDi", m)
- [TODO: what context string to use?]
- H4(m): Implemented by computing BLAKE2b-512("FROST_RedJubjubN", m), interpreting
the 64 bytes as a little-endian integer, and reducing the resulting integer
modulo L = 6554484396890773809930967563523245729705921265872317281365359162392183254199.
- [TODO: what context string to use?]
-FROST(RedPallas, BLAKE2b-512)
+FROST(Pallas, BLAKE2b-512)
'''''''''''''''''''''''''''''
This ciphersuite uses Pallas for the Group and BLAKE2b-512 for the Hash function `H`
meant to produce signatures indistinguishable from RedPallas Sapling Spend
-Authorization Signatures as specified in [#protocol]_.
+Authorization Signatures as specified in [#protocol-concretespendauthsig]_.
-- Group: Pallas [#protocol]_
+- Group: Pallas [#protocol-pallasandvesta]_
- - Order: 0x40000000000000000000000000000000224698fc0994a8dd8c46eb2100000001 (see [#protocol]_)
- - Identity: as defined in [#protocol]_
+ - Order: 0x40000000000000000000000000000000224698fc0994a8dd8c46eb2100000001 (see [#protocol-pallasandvesta]_)
+ - Identity: as defined in [#protocol-pallasandvesta]_
- RandomScalar: Implemented by generating a random 64-byte string and invoking
DeserializeScalar on the result
- RandomNonZeroScalar: Implemented by generating a random 32-byte string that
is not equal to the all-zero string and invoking DeserializeScalar on the result.
- - SerializeElement(P): Implemented as `LEBS2OSP_{ℓG}(repr_G(P))`, where LEBS2OSP and repr_G
- are defined in [#protocol]_
- [TODO: how to specify this? It's awkward to do this way, should we expand
- the definitions?]
- - DeserializeElement: Implemented as `abst_G(LEOS2BSP_{ℓG}(P))`, where LEOS2BSP and abst_G
- are defined in [#protocol]_
+ - SerializeElement(P): Implemented as :math:`\mathsf{repr}_\mathbb{P}(P)` as defined in [#protocol-pallasandvesta]_
+ - DeserializeElement(P): Implemented as :math:`\mathsf{abst}_\mathbb{P}(P)` as defined in [#protocol-pallasandvesta]_
- SerializeScalar: Implemented by outputting the little-endian 32-byte encoding
of the Scalar value.
- DeserializeScalar: Implemented by attempting to deserialize a Scalar from a
@@ -372,18 +368,15 @@ Authorization Signatures as specified in [#protocol]_.
- H1(m): Implemented by computing BLAKE2b-512("FROST_RedPallasR", m), interpreting
the 64 bytes as a little-endian integer, and reducing the resulting integer
modulo L = 0x40000000000000000000000000000000224698fc0994a8dd8c46eb2100000001.
- [TODO: what context string to use?]
- - H2(m): LEOS2IP_{ℓ_H}(H(m)) (mod G.Order())
- / Implemented by computing BLAKE2b-512("Zcash_RedPallasH", m), interpreting
+ - H2(m): Implemented by computing BLAKE2b-512("Zcash_RedPallasH", m), interpreting
the 64 bytes as a little-endian integer, and reducing the resulting integer
modulo L = 0x40000000000000000000000000000000224698fc0994a8dd8c46eb2100000001.
- [TODO: use original expression or expand the definitions as above?]
- - H1(m): Implemented by computing BLAKE2b-512("FROST_RedPallasD", m).
- [TODO: what context string to use?]
+ (This is equivalent to :math:`\mathsf{H}^\circledast(m)`, as defined in
+ [#protocol-concretereddsa]_ parametrized with [#protocol-pallasandvesta]_.)
+ - H3(m): Implemented by computing BLAKE2b-512("FROST_RedPallasD", m).
- H4(m): Implemented by computing BLAKE2b-512("FROST_RedPallasN", m), interpreting
the 64 bytes as a little-endian integer, and reducing the resulting integer
modulo L = 0x40000000000000000000000000000000224698fc0994a8dd8c46eb2100000001.
- [TODO: what context string to use?]
Reference implementation
@@ -398,6 +391,7 @@ References
.. [#BLAKE] `BLAKE2: simpler, smaller, fast as MD5 `_
.. [#RFC2119] `RFC 2119: Key words for use in RFCs to Indicate Requirement Levels `_
.. [#FROST] `Draft RFC: Two-Round Threshold Schnorr Signatures with FROST `_
-.. [#protocol] `Zcash Protocol Specification, Version 2022.3.0 or later `_
-.. [#protocol-introduction] `Zcash Protocol Specification, Version 2020.1.24. Section 1: Introduction `_
-.. [#zip-0000] `ZIP 0: ZIP Process `_
+.. [#protocol-concretereddsa] `Zcash Protocol Specification, Version 2022.3.4 [NU5]. Section 5.4.7: RedDSA, RedJubjub, and RedPallas `_
+.. [#protocol-concretespendauthsig] `Zcash Protocol Specification, Version 2022.3.4 [NU5]. Section 5.4.7.1: Spend Authorization Signature (Sapling and Orchard) `_
+.. [#protocol-jubjub] `Zcash Protocol Specification, Version 2022.3.4 [NU5]. Section 5.4.9.3: Jubjub `_
+.. [#protocol-pallasandvesta] `Zcash Protocol Specification, Version 2022.3.4 [NU5]. Section 5.4.9.6: Pallas and Vesta `_
From d420223db84cfa1e1a66aeacc4d3fc361c73ad81 Mon Sep 17 00:00:00 2001
From: Conrado Gouvea
Date: Mon, 18 Jul 2022 15:03:44 -0300
Subject: [PATCH 07/37] fix formula in verify_signature_share(), to match
sign()
---
zip-frost.rst | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/zip-frost.rst b/zip-frost.rst
index 6ef1bab8..4f9e616a 100644
--- a/zip-frost.rst
+++ b/zip-frost.rst
@@ -245,7 +245,7 @@ as follows: ::
def verify_signature_share(identifier, PK_i, comm_i, sig_share_i, commitment_list,
group_public_key, msg, randomizer):
# Compute the randomized group public key
- randomized_group_public_key = randomizer * group_public_key
+ randomized_group_public_key = group_public_key + G * randomizer
# Encode the commitment list
encoded_commitments = encode_group_commitment_list(commitment_list)
From fe882e9c27fea39e50eff21ce4e7d4fe14b70ddd Mon Sep 17 00:00:00 2001
From: Deirdre Connolly
Date: Wed, 20 Jul 2022 17:48:59 -0400
Subject: [PATCH 08/37] Update zip-frost.rst
Co-authored-by: teor
---
zip-frost.rst | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/zip-frost.rst b/zip-frost.rst
index 4f9e616a..73ba13e1 100644
--- a/zip-frost.rst
+++ b/zip-frost.rst
@@ -171,7 +171,8 @@ The `sign` function is changed to receive `randomizer` and incorporate it into t
computation of the binding factor. It is specified as the following: ::
Inputs:
- - identifier, Identifier i of the signer. Note identifier will never equal 0.
+ - identifier, Identifier i of the signer.
+ Note: identifier will never equal 0.
- sk_i, Signer secret key share, a Scalar.
- group_public_key, public key corresponding to the group signing key,
an Element in G.
From 8ba6cfb3143424ec2c142bbaad873ff1f716f069 Mon Sep 17 00:00:00 2001
From: Deirdre Connolly
Date: Wed, 20 Jul 2022 18:16:16 -0400
Subject: [PATCH 09/37] RedPallas s/Sapling/Orchard/
---
zip-frost.rst | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/zip-frost.rst b/zip-frost.rst
index 73ba13e1..770f3e3c 100644
--- a/zip-frost.rst
+++ b/zip-frost.rst
@@ -344,7 +344,7 @@ FROST(Pallas, BLAKE2b-512)
'''''''''''''''''''''''''''''
This ciphersuite uses Pallas for the Group and BLAKE2b-512 for the Hash function `H`
-meant to produce signatures indistinguishable from RedPallas Sapling Spend
+meant to produce signatures indistinguishable from RedPallas Orchard Spend
Authorization Signatures as specified in [#protocol-concretespendauthsig]_.
- Group: Pallas [#protocol-pallasandvesta]_
From 665acefc63668ccc3842baec5c2a18b83c1087e3 Mon Sep 17 00:00:00 2001
From: Conrado Gouvea
Date: Thu, 21 Jul 2022 19:24:47 -0300
Subject: [PATCH 10/37] Apply suggestions from code review
Co-authored-by: teor
---
zip-frost.rst | 35 +++++++++++++++++------------------
1 file changed, 17 insertions(+), 18 deletions(-)
diff --git a/zip-frost.rst b/zip-frost.rst
index 770f3e3c..e60208fd 100644
--- a/zip-frost.rst
+++ b/zip-frost.rst
@@ -53,7 +53,7 @@ split between users in a way that a threshold (e.g. 2 out of 3) of them must
sign the transaction in order to create the final signature. This enables scenarios
such as users and exchanges sharing custody of a wallet, for example.
-FROST is one of such threshold signatures protocols. However, it can't be used as-is
+FROST is one of such threshold signature protocols. However, it can't be used as-is
since the Zcash protocol also requires re-randomizing public and private keys
to ensure unlinkability between transactions. This ZIP specifies a variant of
FROST with re-randomization support.
@@ -62,11 +62,11 @@ FROST with re-randomization support.
Requirements
============
-- All signatures generated by following this ZIP MUST be verified successfully
+- All signatures generated by following this ZIP must be verified successfully.
as Sapling or Orchard spend authorization signatures.
- The signatures generated by following this ZIP should meet the security criteria
for Signature with Re-Randomizable Keys as specified in the Zcash protocol [#protocol-concretereddsa]_.
-- Follow the threat model described below.
+- The threat model described below must be taken into account.
Threat Model
------------
@@ -75,16 +75,15 @@ In normal usage, a Zcash user follows multiple steps in order to generate a
shielded transaction:
- The transaction is created.
-- The transaction is signed with a randomized version of the user's spend
+- The transaction is signed with a re-randomized version of the user's spend
authorization private key.
- The zero-knowledge proof for the transaction is created with the randomizer
as an auxiliary (secret) input, among others.
When employing re-randomized FROST as specified in this ZIP, the goal is to
-split the spend authorization private key among multiple participants. This means
+split the spend authorization private key :math:`\mathsf{ask}` among multiple possible signers. This means
that the proof generation will still be generated by a single participant, likely
-the one that created the transaction in the first place and that will actually
-submit the transaction to the network. Note that his user already controls the
+the one that created the transaction in the first place. Note that this user already controls the
privacy of the transaction since they are responsible for creating the proof.
This fits well into the "Coordinator" role from the FROST specification. The
@@ -106,8 +105,7 @@ Non-requirements
================
- This ZIP does not support removing the Coordinator role, as described in #[FROST]_.
-- This ZIP does not prevent key share holders from breaking the privacy
- of the transaction.
+- This ZIP does not prevent key share holders from linking the signing operation to a transaction in the blockchain.
Specification
@@ -122,7 +120,7 @@ following modifications.
Randomizer Generation
'''''''''''''''''''''
-A new helper function is defined, which computes RedDSA.GenRandom:
+A new helper function is defined, which computes :math:`\mathsf{RedDSA.GenRandom}`:
::
@@ -163,7 +161,7 @@ Round Two - Signature Share Generation
''''''''''''''''''''''''''''''''''''''
In Round Two, the Coordinator generates a random scalar `randomizer` by calling
-`randomizer_generate` and sends it to each signer, over a secret channel,
+`randomizer_generate` and sends it to each signer, over a confidential and authenticated channel,
along with the message and the set of signing commitments. (Note that this differs
from regular FROST which just requires an authenticated channel.)
@@ -172,7 +170,7 @@ computation of the binding factor. It is specified as the following: ::
Inputs:
- identifier, Identifier i of the signer.
- Note: identifier will never equal 0.
+ Note: FROST spec requires that identifier will never equal 0.
- sk_i, Signer secret key share, a Scalar.
- group_public_key, public key corresponding to the group signing key,
an Element in G.
@@ -183,7 +181,7 @@ computation of the binding factor. It is specified as the following: ::
[(j, hiding_nonce_commitment_j, binding_nonce_commitment_j), ...], a
list of commitments issued in Round 1 by each signer and sent by the Coordinator.
Each element in the list indicates the signer identifier j and their two commitment
- Element values (hiding_nonce_commitment_j, binding_nonce_commitment_j).s
+ Element values (hiding_nonce_commitment_j, binding_nonce_commitment_j).
This list MUST be sorted in ascending order by signer identifier.
- randomizer, the randomizer Scalar.
@@ -223,7 +221,8 @@ The `verify_signature_share` is changed to incorporate the randomizer,
as follows: ::
Inputs:
- - identifier, Identifier i of the signer. Note identifier will never equal 0.
+ - identifier, Identifier i of the signer.
+ Note: identifier will never equal 0.
- PK_i, the public key for the ith signer, where PK_i = G.ScalarBaseMult(sk_i),
an Element in G
- comm_i, pair of Element values in G (hiding_nonce_commitment, binding_nonce_commitment)
@@ -234,7 +233,7 @@ as follows: ::
[(j, hiding_nonce_commitment_j, binding_nonce_commitment_j), ...], a
list of commitments issued in Round 1 by each signer, where each element
in the list indicates the signer identifier j and their two commitment
- Element values (hiding_nonce_commitment_j, binding_nonce_commitment_j).s
+ Element values (hiding_nonce_commitment_j, binding_nonce_commitment_j).
This list MUST be sorted in ascending order by signer identifier.
- group_public_key, public key corresponding to the group signing key,
an Element in G.
@@ -320,7 +319,7 @@ Authorization Signatures as specified in [#protocol-concretespendauthsig]_.
- SerializeScalar: Implemented by outputting the little-endian 32-byte encoding
of the Scalar value.
- DeserializeScalar: Implemented by attempting to deserialize a Scalar from a
- 32-byte string. This function can fail if the input does not represent a Scalar
+ little-endian 32-byte string. This function can fail if the input does not represent a Scalar
between the value 0 and G.Order() - 1.
- Hash (`H`): BLAKE2b-512 [#BLAKE]_ (BLAKE2b with 512-bit output and 16-byte personalization string),
@@ -334,7 +333,7 @@ Authorization Signatures as specified in [#protocol-concretespendauthsig]_.
modulo L = 6554484396890773809930967563523245729705921265872317281365359162392183254199.
(This is equivalent to :math:`\mathsf{H}^\circledast(m)`, as defined in
[#protocol-concretereddsa]_ parametrized with [#protocol-jubjub]_.)
- - H3(m): Implemented by computing BLAKE2b-512("FROST_RedJubjubDi", m)
+ - H3(m): Implemented by computing BLAKE2b-512("FROST_RedJubjubD", m)
- H4(m): Implemented by computing BLAKE2b-512("FROST_RedJubjubN", m), interpreting
the 64 bytes as a little-endian integer, and reducing the resulting integer
modulo L = 6554484396890773809930967563523245729705921265872317281365359162392183254199.
@@ -360,7 +359,7 @@ Authorization Signatures as specified in [#protocol-concretespendauthsig]_.
- SerializeScalar: Implemented by outputting the little-endian 32-byte encoding
of the Scalar value.
- DeserializeScalar: Implemented by attempting to deserialize a Scalar from a
- 32-byte string. This function can fail if the input does not represent a Scalar
+ little-endian 32-byte string. This function can fail if the input does not represent a Scalar
between the value 0 and G.Order() - 1.
- Hash (`H`): BLAKE2b-512 [#BLAKE]_ (BLAKE2b with 512-bit output and 16-byte personalization string),
From 80a22f4010c2054dfc0a36dad19e0616eb078577 Mon Sep 17 00:00:00 2001
From: Conrado Gouvea
Date: Thu, 21 Jul 2022 22:00:23 -0300
Subject: [PATCH 11/37] addressed comments from ZIP sync
---
zip-frost.rst | 80 +++++++++++++++++++++++++++++++++++++++------------
1 file changed, 62 insertions(+), 18 deletions(-)
diff --git a/zip-frost.rst b/zip-frost.rst
index e60208fd..44fc9a21 100644
--- a/zip-frost.rst
+++ b/zip-frost.rst
@@ -23,10 +23,9 @@ be interpreted as described in RFC 2119. [#RFC2119]_
The terms below are to be interpreted as follows:
-{Term to be defined}
- {Definition.}
-{Another term}
- {Definition.}
+Unlinkability
+ The impossibility of determining if two transactions were generated by the same
+ party.
Abstract
@@ -47,9 +46,9 @@ private key is what effectively allows the user to spend funds.
This is a security-critical step, since anyone who obtains access to the private
key will be able to spend the user's funds. For this reason, one interesting
-possibility is to require multiple users to allow the transaction to go through.
+possibility is to require multiple parties to allow the transaction to go through.
This can be accomplished with threshold signatures, where the private key is
-split between users in a way that a threshold (e.g. 2 out of 3) of them must
+split between parties in a way that a threshold (e.g. 2 out of 3) of them must
sign the transaction in order to create the final signature. This enables scenarios
such as users and exchanges sharing custody of a wallet, for example.
@@ -81,13 +80,14 @@ shielded transaction:
as an auxiliary (secret) input, among others.
When employing re-randomized FROST as specified in this ZIP, the goal is to
-split the spend authorization private key :math:`\mathsf{ask}` among multiple possible signers. This means
-that the proof generation will still be generated by a single participant, likely
-the one that created the transaction in the first place. Note that this user already controls the
-privacy of the transaction since they are responsible for creating the proof.
+split the spend authorization private key :math:`\mathsf{ask}` among multiple
+possible signers. This means that the proof generation will still be generated
+by a single participant, likely the one that created the transaction in the first
+place. Note that this user already controls the privacy of the transaction since
+they are responsible for creating the proof.
-This fits well into the "Coordinator" role from the FROST specification. The
-Coordinator is responsible for sending the message to be signed to all participants,
+This fits well into the "Coordinator" role from the FROST specification [#frost-protocol]_.
+The Coordinator is responsible for sending the message to be signed to all participants,
and to aggregate the signature shares.
With those considerations in mind, the threat model considered in this ZIP is:
@@ -104,19 +104,43 @@ With those considerations in mind, the threat model considered in this ZIP is:
Non-requirements
================
-- This ZIP does not support removing the Coordinator role, as described in #[FROST]_.
+- This ZIP does not support removing the Coordinator role, as described in #[frost-removingcoordinator]_.
- This ZIP does not prevent key share holders from linking the signing operation to a transaction in the blockchain.
+- Like the FROST specification [#FROST], this ZIP does not specify a key generation
+ procedure; but refer to that specification for guidelines.
+- Network privacy is not in scope for this ZIP, and must be obtained with other
+ tools if desired.
Specification
=============
+Algorithms in this section are specified using Python pseudo-code, in the same
+fashion as the FROST specification [#FROST]_.
+
+The types Scalar, Element, and G are defined in #[frost-primeordergroup]_, as well
+as the notation for elliptic-curve arithmetic, which uses the additive notation.
+
+
Re-randomized FROST
-------------------
To add re-randomization to FROST, follow the specification [#FROST]_ with the
following modifications.
+
+Key Generation
+''''''''''''''
+
+While key generation is out of scope for this ZIP and the FROST spec [#FROST]_,
+it needs to be consistent with FROST, see [#frost-tdkg]_ for guidance. The
+spend authorization private key :math:`\mathsf{ask}` [#protocol-spendauthsig]_
+is the particular key that must be used in the context of this ZIP. Note that
+while Sapling allows creating it directly, in Orchard it is always derived
+from the spending key :math:`\mathsf{ask}`. This means that a trusted
+dealer key generation process might be required as detailed in [#frost-tdkg]_.
+
+
Randomizer Generation
'''''''''''''''''''''
@@ -157,6 +181,18 @@ as follows: ::
return binding_factor
+Round One - Commitment
+''''''''''''''''''''''
+
+Roune One is exactly the same as specified #[FROST]_. But for context, it
+involves these steps:
+
+- Each signer generates nonces and their corresponding public commitments.
+ A nonce is a pair of Scalar values, and a commitment is a pair of Element values.
+- The nonces are stored locally by the signer and kept private for use in the second round.
+- The commitments are sent to the Coordinator.
+
+
Round Two - Signature Share Generation
''''''''''''''''''''''''''''''''''''''
@@ -278,7 +314,8 @@ The `aggregate` function is changed to incorporate the randomizer as follows: ::
Inputs:
- group_commitment, the group commitment returned by compute_group_commitment,
an Element in G.
- - sig_shares, a set of signature shares z_i, Scalar values, for each signer,
+ - sig_shares, a set of signature shares z_i, Scalar values, for each signer
+ that participated in Round One,
of length NUM_SIGNERS, where MIN_SIGNERS <= NUM_SIGNERS <= MAX_SIGNERS.
- group_public_key, public key corresponding to the group signing key,
- challenge, the challenge returned by compute_challenge, a Scalar.
@@ -315,12 +352,13 @@ Authorization Signatures as specified in [#protocol-concretespendauthsig]_.
- RandomNonZeroScalar: Implemented by generating a random 32-byte string that
is not equal to the all-zero string and invoking DeserializeScalar on the result.
- SerializeElement(P): Implemented as :math:`\mathsf{repr}_\mathbb{J}(P)` as defined in [#protocol-jubjub]_
- - DeserializeElement(P): Implemented as :math:`\mathsf{abst}_\mathbb{J}(P)` as defined in [#protocol-jubjub]_
+ - DeserializeElement(P): Implemented as :math:`\mathsf{abst}_\mathbb{J}(P)` as defined in [#protocol-jubjub]_,
+ failing if :math:`\bot` is returned.
- SerializeScalar: Implemented by outputting the little-endian 32-byte encoding
of the Scalar value.
- DeserializeScalar: Implemented by attempting to deserialize a Scalar from a
little-endian 32-byte string. This function can fail if the input does not represent a Scalar
- between the value 0 and G.Order() - 1.
+ in the range \[0, G.Order() - 1\].
- Hash (`H`): BLAKE2b-512 [#BLAKE]_ (BLAKE2b with 512-bit output and 16-byte personalization string),
and Nh = 64.
@@ -355,12 +393,13 @@ Authorization Signatures as specified in [#protocol-concretespendauthsig]_.
- RandomNonZeroScalar: Implemented by generating a random 32-byte string that
is not equal to the all-zero string and invoking DeserializeScalar on the result.
- SerializeElement(P): Implemented as :math:`\mathsf{repr}_\mathbb{P}(P)` as defined in [#protocol-pallasandvesta]_
- - DeserializeElement(P): Implemented as :math:`\mathsf{abst}_\mathbb{P}(P)` as defined in [#protocol-pallasandvesta]_
+ - DeserializeElement(P): Implemented as :math:`\mathsf{abst}_\mathbb{P}(P)` as defined in [#protocol-pallasandvesta]_,
+ failing if :math:`\bot` is returned.
- SerializeScalar: Implemented by outputting the little-endian 32-byte encoding
of the Scalar value.
- DeserializeScalar: Implemented by attempting to deserialize a Scalar from a
little-endian 32-byte string. This function can fail if the input does not represent a Scalar
- between the value 0 and G.Order() - 1.
+ in the range \[0, G.Order() - 1\].
- Hash (`H`): BLAKE2b-512 [#BLAKE]_ (BLAKE2b with 512-bit output and 16-byte personalization string),
and Nh = 64.
@@ -391,7 +430,12 @@ References
.. [#BLAKE] `BLAKE2: simpler, smaller, fast as MD5 `_
.. [#RFC2119] `RFC 2119: Key words for use in RFCs to Indicate Requirement Levels `_
.. [#FROST] `Draft RFC: Two-Round Threshold Schnorr Signatures with FROST `_
+.. [#frost-protocol] `Draft RFC: Two-Round Threshold Schnorr Signatures with FROST. Section 5: Two-Round FROST Signing Protocol `_
+.. [#frost-removingcoordinator] `Draft RFC: Two-Round Threshold Schnorr Signatures with FROST. Section 7.3: Removing the Coordinator Role `_
+.. [#frost-primeordergroup] `Draft RFC: Two-Round Threshold Schnorr Signatures with FROST. Section 3.1: Prime-Order Group `_
+.. [#frost-tdkg] `Draft RFC: Two-Round Threshold Schnorr Signatures with FROST. Appendix B: Trusted Dealer Key Generation `_
.. [#protocol-concretereddsa] `Zcash Protocol Specification, Version 2022.3.4 [NU5]. Section 5.4.7: RedDSA, RedJubjub, and RedPallas `_
.. [#protocol-concretespendauthsig] `Zcash Protocol Specification, Version 2022.3.4 [NU5]. Section 5.4.7.1: Spend Authorization Signature (Sapling and Orchard) `_
+.. [#protocol-spendauthsig] `Zcash Protocol Specification, Version 2022.3.4 [NU5]. Section 4.15: Spend Authorization Signature (Sapling and Orchard) `_
.. [#protocol-jubjub] `Zcash Protocol Specification, Version 2022.3.4 [NU5]. Section 5.4.9.3: Jubjub `_
.. [#protocol-pallasandvesta] `Zcash Protocol Specification, Version 2022.3.4 [NU5]. Section 5.4.9.6: Pallas and Vesta `_
From 1342ead71a91a01473697b818e37b9799fb7078d Mon Sep 17 00:00:00 2001
From: Conrado Gouvea
Date: Fri, 22 Jul 2022 14:44:06 -0300
Subject: [PATCH 12/37] make explicit that key share holders can break
unlinkability
---
zip-frost.rst | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/zip-frost.rst b/zip-frost.rst
index 44fc9a21..bc7d7e81 100644
--- a/zip-frost.rst
+++ b/zip-frost.rst
@@ -96,7 +96,8 @@ With those considerations in mind, the threat model considered in this ZIP is:
the unlinkability property). A rogue Coordinator will be able to break
unlinkability and privacy, but should not be able to create signed transactions
without the approval of `MIN_SIGNERS` participants, as specified in FROST.
-- All key share holders are also trusted with the privacy of the transaction.
+- All key share holders are also trusted with the privacy and of the transaction,
+ thus a rogue key share holder will be able to break its privacy and unlinkability.
A future specification may support a scenario where individual key share
holders are not trusted with it.
From f1058ccfa99de841ac339cf84a7e353d56ec93c9 Mon Sep 17 00:00:00 2001
From: Conrado Gouvea
Date: Thu, 25 Aug 2022 15:37:10 -0300
Subject: [PATCH 13/37] update to spec V8
---
zip-frost.rst | 103 ++++++++++++++++++++++++++------------------------
1 file changed, 54 insertions(+), 49 deletions(-)
diff --git a/zip-frost.rst b/zip-frost.rst
index bc7d7e81..7b5b8b72 100644
--- a/zip-frost.rst
+++ b/zip-frost.rst
@@ -79,7 +79,7 @@ shielded transaction:
- The zero-knowledge proof for the transaction is created with the randomizer
as an auxiliary (secret) input, among others.
-When employing re-randomized FROST as specified in this ZIP, the goal is to
+When employing re-randomizable FROST as specified in this ZIP, the goal is to
split the spend authorization private key :math:`\mathsf{ask}` among multiple
possible signers. This means that the proof generation will still be generated
by a single participant, likely the one that created the transaction in the first
@@ -123,8 +123,8 @@ The types Scalar, Element, and G are defined in #[frost-primeordergroup]_, as we
as the notation for elliptic-curve arithmetic, which uses the additive notation.
-Re-randomized FROST
--------------------
+Re-randomizable FROST
+---------------------
To add re-randomization to FROST, follow the specification [#FROST]_ with the
following modifications.
@@ -158,7 +158,7 @@ A new helper function is defined, which computes :math:`\mathsf{RedDSA.GenRandom
def randomizer_generate():
randomizer_input = random_bytes(64)
- return H2(randomizer_input)
+ return H3(randomizer_input)
Binding Factor Computation
@@ -168,18 +168,27 @@ The `compute_binding_factor` function is changed to receive the `randomizer`
as follows: ::
Inputs:
- - encoded_commitment_list, an encoded commitment list (as computed
- by encode_group_commitment_list)
+ - commitment_list = [(i, hiding_nonce_commitment_i, binding_nonce_commitment_i), ...],
+ a list of commitments issued by each signer, where each element in the list
+ indicates the signer identifier i and their two commitment Element values
+ (hiding_nonce_commitment_i, binding_nonce_commitment_i). This list MUST be sorted
+ in ascending order by signer identifier.
- msg, the message to be signed.
- randomizer, the randomizer Scalar.
- Outputs: A Scalar representing the binding factor
+ Outputs: A list of (identifier, Scalar) tuples representing the binding factors.
- def compute_binding_factor(encoded_commitment_list, msg, randomizer):
- msg_hash = H3(msg)
- rho_input = encoded_commitment_list || msg_hash || G.SerializeScalar(randomizer)
- binding_factor = H1(rho_input)
- return binding_factor
+ def compute_binding_factors(commitment_list, msg, randomizer):
+ msg_hash = H4(msg)
+ encoded_commitment_hash = H5(encode_group_commitment_list(commitment_list))
+ rho_input_prefix = msg_hash || encoded_commitment_hash || G.SerializeScalar(randomizer)
+
+ binding_factor_list = []
+ for (identifier, hiding_nonce_commitment, binding_nonce_commitment) in commitment_list:
+ rho_input = rho_input_prefix || encode_uint16(identifier)
+ binding_factor = H1(rho_input)
+ binding_factor_list.append((identifier, binding_factor))
+ return binding_factor_list
Round One - Commitment
@@ -206,8 +215,7 @@ The `sign` function is changed to receive `randomizer` and incorporate it into t
computation of the binding factor. It is specified as the following: ::
Inputs:
- - identifier, Identifier i of the signer.
- Note: FROST spec requires that identifier will never equal 0.
+ - identifier, Identifier i of the signer. Note identifier will never equal 0.
- sk_i, Signer secret key share, a Scalar.
- group_public_key, public key corresponding to the group signing key,
an Element in G.
@@ -228,18 +236,16 @@ computation of the binding factor. It is specified as the following: ::
# Compute the randomized group public key
randomized_group_public_key = group_public_key + G * randomizer
- # Encode the commitment list
- encoded_commitments = encode_group_commitment_list(commitment_list)
-
- # Compute the binding factor
- binding_factor = compute_binding_factor(encoded_commitments, msg, randomizer)
+ # Compute the binding factor(s)
+ binding_factor_list = compute_binding_factors(commitment_list, msg)
+ binding_factor = binding_factor_for_participant(binding_factor_list, identifier, randomizer)
# Compute the group commitment
- group_commitment = compute_group_commitment(commitment_list, binding_factor)
+ group_commitment = compute_group_commitment(commitment_list, binding_factor_list)
# Compute Lagrange coefficient
participant_list = participants_from_commitment_list(commitment_list)
- lambda_i = derive_lagrange_coefficient(identifier, participant_list)
+ lambda_i = derive_lagrange_coefficient(Scalar(identifier), participant_list)
# Compute the per-message challenge
challenge = compute_challenge(group_commitment, randomized_group_public_key, msg)
@@ -258,8 +264,7 @@ The `verify_signature_share` is changed to incorporate the randomizer,
as follows: ::
Inputs:
- - identifier, Identifier i of the signer.
- Note: identifier will never equal 0.
+ - identifier, Identifier i of the signer. Note: identifier MUST never equal 0.
- PK_i, the public key for the ith signer, where PK_i = G.ScalarBaseMult(sk_i),
an Element in G
- comm_i, pair of Element values in G (hiding_nonce_commitment, binding_nonce_commitment)
@@ -284,29 +289,27 @@ as follows: ::
# Compute the randomized group public key
randomized_group_public_key = group_public_key + G * randomizer
- # Encode the commitment list
- encoded_commitments = encode_group_commitment_list(commitment_list)
-
- # Compute the binding factor
- binding_factor = compute_binding_factor(encoded_commitments, msg, randomizer)
+ # Compute the binding factors
+ binding_factor_list = compute_binding_factors(commitment_list, msg, randomizer)
+ binding_factor = binding_factor_for_participant(binding_factor_list, identifier)
# Compute the group commitment
- group_commitment = compute_group_commitment(commitment_list, binding_factor)
+ group_commitment = compute_group_commitment(commitment_list, binding_factor_list)
# Compute the commitment share
(hiding_nonce_commitment, binding_nonce_commitment) = comm_i
- comm_share = hiding_nonce_commitment + (binding_nonce_commitment * binding_factor)
+ comm_share = hiding_nonce_commitment + G.ScalarMult(binding_nonce_commitment, binding_factor)
# Compute the challenge
challenge = compute_challenge(group_commitment, randomized_group_public_key, msg)
# Compute Lagrange coefficient
participant_list = participants_from_commitment_list(commitment_list)
- lambda_i = derive_lagrange_coefficient(identifier, participant_list)
+ lambda_i = derive_lagrange_coefficient(Scalar(identifier), participant_list)
# Compute relation values
l = G.ScalarBaseMult(sig_share_i)
- r = comm_share + ((challenge * lambda_i) * PK_i)
+ r = comm_share + G.ScalarMult(PK_i, challenge * lambda_i)
return l == r
@@ -315,8 +318,7 @@ The `aggregate` function is changed to incorporate the randomizer as follows: ::
Inputs:
- group_commitment, the group commitment returned by compute_group_commitment,
an Element in G.
- - sig_shares, a set of signature shares z_i, Scalar values, for each signer
- that participated in Round One,
+ - sig_shares, a set of signature shares z_i, Scalar values, for each signer,
of length NUM_SIGNERS, where MIN_SIGNERS <= NUM_SIGNERS <= MAX_SIGNERS.
- group_public_key, public key corresponding to the group signing key,
- challenge, the challenge returned by compute_challenge, a Scalar.
@@ -348,8 +350,8 @@ Authorization Signatures as specified in [#protocol-concretespendauthsig]_.
- Order: 6554484396890773809930967563523245729705921265872317281365359162392183254199 (see [#protocol-jubjub]_)
- Identity: as defined in [#protocol-jubjub]_
- - RandomScalar: Implemented by generating a random 64-byte string and invoking
- DeserializeScalar on the result
+ - RandomScalar(): Implemented by returning a uniformly random Scalar in the range \[0, `G.Order()` - 1\].
+ Refer to {{frost-randomscalar}} for implementation guidance.
- RandomNonZeroScalar: Implemented by generating a random 32-byte string that
is not equal to the all-zero string and invoking DeserializeScalar on the result.
- SerializeElement(P): Implemented as :math:`\mathsf{repr}_\mathbb{J}(P)` as defined in [#protocol-jubjub]_
@@ -359,7 +361,7 @@ Authorization Signatures as specified in [#protocol-concretespendauthsig]_.
of the Scalar value.
- DeserializeScalar: Implemented by attempting to deserialize a Scalar from a
little-endian 32-byte string. This function can fail if the input does not represent a Scalar
- in the range \[0, G.Order() - 1\].
+ in the range \[0, `G.Order()` - 1\].
- Hash (`H`): BLAKE2b-512 [#BLAKE]_ (BLAKE2b with 512-bit output and 16-byte personalization string),
and Nh = 64.
@@ -372,10 +374,11 @@ Authorization Signatures as specified in [#protocol-concretespendauthsig]_.
modulo L = 6554484396890773809930967563523245729705921265872317281365359162392183254199.
(This is equivalent to :math:`\mathsf{H}^\circledast(m)`, as defined in
[#protocol-concretereddsa]_ parametrized with [#protocol-jubjub]_.)
- - H3(m): Implemented by computing BLAKE2b-512("FROST_RedJubjubD", m)
- - H4(m): Implemented by computing BLAKE2b-512("FROST_RedJubjubN", m), interpreting
+ - H3(m): Implemented by computing BLAKE2b-512("FROST_RedJubjubN", m), interpreting
the 64 bytes as a little-endian integer, and reducing the resulting integer
modulo L = 6554484396890773809930967563523245729705921265872317281365359162392183254199.
+ - H4(m): Implemented by computing BLAKE2b-512("FROST_RedJubjubM", m)
+ - H5(m): Implemented by computing BLAKE2b-512("FROST_RedJubjubC", m)
FROST(Pallas, BLAKE2b-512)
@@ -389,8 +392,8 @@ Authorization Signatures as specified in [#protocol-concretespendauthsig]_.
- Order: 0x40000000000000000000000000000000224698fc0994a8dd8c46eb2100000001 (see [#protocol-pallasandvesta]_)
- Identity: as defined in [#protocol-pallasandvesta]_
- - RandomScalar: Implemented by generating a random 64-byte string and invoking
- DeserializeScalar on the result
+ - RandomScalar(): Implemented by returning a uniformly random Scalar in the range \[0, `G.Order()` - 1\].
+ Refer to {{frost-randomscalar}} for implementation guidance.
- RandomNonZeroScalar: Implemented by generating a random 32-byte string that
is not equal to the all-zero string and invoking DeserializeScalar on the result.
- SerializeElement(P): Implemented as :math:`\mathsf{repr}_\mathbb{P}(P)` as defined in [#protocol-pallasandvesta]_
@@ -400,7 +403,7 @@ Authorization Signatures as specified in [#protocol-concretespendauthsig]_.
of the Scalar value.
- DeserializeScalar: Implemented by attempting to deserialize a Scalar from a
little-endian 32-byte string. This function can fail if the input does not represent a Scalar
- in the range \[0, G.Order() - 1\].
+ in the range \[0, `G.Order()` - 1\].
- Hash (`H`): BLAKE2b-512 [#BLAKE]_ (BLAKE2b with 512-bit output and 16-byte personalization string),
and Nh = 64.
@@ -413,10 +416,11 @@ Authorization Signatures as specified in [#protocol-concretespendauthsig]_.
modulo L = 0x40000000000000000000000000000000224698fc0994a8dd8c46eb2100000001.
(This is equivalent to :math:`\mathsf{H}^\circledast(m)`, as defined in
[#protocol-concretereddsa]_ parametrized with [#protocol-pallasandvesta]_.)
- - H3(m): Implemented by computing BLAKE2b-512("FROST_RedPallasD", m).
- - H4(m): Implemented by computing BLAKE2b-512("FROST_RedPallasN", m), interpreting
+ - H3(m): Implemented by computing BLAKE2b-512("FROST_RedPallasN", m), interpreting
the 64 bytes as a little-endian integer, and reducing the resulting integer
modulo L = 0x40000000000000000000000000000000224698fc0994a8dd8c46eb2100000001.
+ - H4(m): Implemented by computing BLAKE2b-512("FROST_RedPallasM", m).
+ - H5(m): Implemented by computing BLAKE2b-512("FROST_RedPallasC", m).
Reference implementation
@@ -430,11 +434,12 @@ References
.. [#BLAKE] `BLAKE2: simpler, smaller, fast as MD5 `_
.. [#RFC2119] `RFC 2119: Key words for use in RFCs to Indicate Requirement Levels `_
-.. [#FROST] `Draft RFC: Two-Round Threshold Schnorr Signatures with FROST `_
-.. [#frost-protocol] `Draft RFC: Two-Round Threshold Schnorr Signatures with FROST. Section 5: Two-Round FROST Signing Protocol `_
-.. [#frost-removingcoordinator] `Draft RFC: Two-Round Threshold Schnorr Signatures with FROST. Section 7.3: Removing the Coordinator Role `_
-.. [#frost-primeordergroup] `Draft RFC: Two-Round Threshold Schnorr Signatures with FROST. Section 3.1: Prime-Order Group `_
-.. [#frost-tdkg] `Draft RFC: Two-Round Threshold Schnorr Signatures with FROST. Appendix B: Trusted Dealer Key Generation `_
+.. [#FROST] `Draft RFC: Two-Round Threshold Schnorr Signatures with FROST `_
+.. [#frost-protocol] `Draft RFC: Two-Round Threshold Schnorr Signatures with FROST. Section 5: Two-Round FROST Signing Protocol `_
+.. [#frost-removingcoordinator] `Draft RFC: Two-Round Threshold Schnorr Signatures with FROST. Section 7.3: Removing the Coordinator Role `_
+.. [#frost-primeordergroup] `Draft RFC: Two-Round Threshold Schnorr Signatures with FROST. Section 3.1: Prime-Order Group `_
+.. [#frost-tdkg] `Draft RFC: Two-Round Threshold Schnorr Signatures with FROST. Appendix B: Trusted Dealer Key Generation `_
+.. [#frost-randomscalar] `Draft RFC: Two-Round Threshold Schnorr Signatures with FROST. Appendix C: Random Scalar Generation `_
.. [#protocol-concretereddsa] `Zcash Protocol Specification, Version 2022.3.4 [NU5]. Section 5.4.7: RedDSA, RedJubjub, and RedPallas `_
.. [#protocol-concretespendauthsig] `Zcash Protocol Specification, Version 2022.3.4 [NU5]. Section 5.4.7.1: Spend Authorization Signature (Sapling and Orchard) `_
.. [#protocol-spendauthsig] `Zcash Protocol Specification, Version 2022.3.4 [NU5]. Section 4.15: Spend Authorization Signature (Sapling and Orchard) `_
From d2d315f012b032e0c0049542ef9553794a9374dc Mon Sep 17 00:00:00 2001
From: Conrado Gouvea
Date: Mon, 29 Aug 2022 15:06:21 -0300
Subject: [PATCH 14/37] send randomizer point instead of randomizer
---
zip-frost.rst | 31 ++++++++++++++++---------------
1 file changed, 16 insertions(+), 15 deletions(-)
diff --git a/zip-frost.rst b/zip-frost.rst
index 7b5b8b72..27b33f14 100644
--- a/zip-frost.rst
+++ b/zip-frost.rst
@@ -164,7 +164,7 @@ A new helper function is defined, which computes :math:`\mathsf{RedDSA.GenRandom
Binding Factor Computation
''''''''''''''''''''''''''
-The `compute_binding_factor` function is changed to receive the `randomizer`
+The `compute_binding_factor` function is changed to receive the `randomizer_point`
as follows: ::
Inputs:
@@ -174,14 +174,14 @@ as follows: ::
(hiding_nonce_commitment_i, binding_nonce_commitment_i). This list MUST be sorted
in ascending order by signer identifier.
- msg, the message to be signed.
- - randomizer, the randomizer Scalar.
+ - randomizer_point, an element in G.
Outputs: A list of (identifier, Scalar) tuples representing the binding factors.
- def compute_binding_factors(commitment_list, msg, randomizer):
+ def compute_binding_factors(commitment_list, msg, randomizer_point):
msg_hash = H4(msg)
encoded_commitment_hash = H5(encode_group_commitment_list(commitment_list))
- rho_input_prefix = msg_hash || encoded_commitment_hash || G.SerializeScalar(randomizer)
+ rho_input_prefix = msg_hash || encoded_commitment_hash || G.SerializeElement(randomizer_point)
binding_factor_list = []
for (identifier, hiding_nonce_commitment, binding_nonce_commitment) in commitment_list:
@@ -207,7 +207,8 @@ Round Two - Signature Share Generation
''''''''''''''''''''''''''''''''''''''
In Round Two, the Coordinator generates a random scalar `randomizer` by calling
-`randomizer_generate` and sends it to each signer, over a confidential and authenticated channel,
+`randomizer_generate`. Then it computes `randomizer_point = G.ScalarBaseMult(randomizer)`
+and sends it to each signer, over a confidential and authenticated channel,
along with the message and the set of signing commitments. (Note that this differs
from regular FROST which just requires an authenticated channel.)
@@ -228,17 +229,17 @@ computation of the binding factor. It is specified as the following: ::
Each element in the list indicates the signer identifier j and their two commitment
Element values (hiding_nonce_commitment_j, binding_nonce_commitment_j).
This list MUST be sorted in ascending order by signer identifier.
- - randomizer, the randomizer Scalar.
+ - randomizer_point, an element in G (sent by the Coordinator).
Outputs: a Scalar value representing the signature share
- def sign(identifier, sk_i, group_public_key, nonce_i, msg, commitment_list, randomizer):
+ def sign(identifier, sk_i, group_public_key, nonce_i, msg, commitment_list, randomizer_point):
# Compute the randomized group public key
- randomized_group_public_key = group_public_key + G * randomizer
+ randomized_group_public_key = group_public_key + randomizer_point
# Compute the binding factor(s)
- binding_factor_list = compute_binding_factors(commitment_list, msg)
- binding_factor = binding_factor_for_participant(binding_factor_list, identifier, randomizer)
+ binding_factor_list = compute_binding_factors(commitment_list, msg, randomizer_point)
+ binding_factor = binding_factor_for_participant(binding_factor_list, identifier)
# Compute the group commitment
group_commitment = compute_group_commitment(commitment_list, binding_factor_list)
@@ -260,7 +261,7 @@ computation of the binding factor. It is specified as the following: ::
Signature Share Verification and Aggregation
''''''''''''''''''''''''''''''''''''''''''''
-The `verify_signature_share` is changed to incorporate the randomizer,
+The `verify_signature_share` is changed to incorporate the randomizer point,
as follows: ::
Inputs:
@@ -280,17 +281,17 @@ as follows: ::
- group_public_key, public key corresponding to the group signing key,
an Element in G.
- msg, the message to be signed.
- - randomizer, the randomizer Scalar.
+ - randomizer_point, an element in G.
Outputs: True if the signature share is valid, and False otherwise.
def verify_signature_share(identifier, PK_i, comm_i, sig_share_i, commitment_list,
- group_public_key, msg, randomizer):
+ group_public_key, msg, randomizer_point):
# Compute the randomized group public key
- randomized_group_public_key = group_public_key + G * randomizer
+ randomized_group_public_key = group_public_key + randomizer_point
# Compute the binding factors
- binding_factor_list = compute_binding_factors(commitment_list, msg, randomizer)
+ binding_factor_list = compute_binding_factors(commitment_list, msg, randomizer_point)
binding_factor = binding_factor_for_participant(binding_factor_list, identifier)
# Compute the group commitment
From 8740dce550338d0dd1faaae4f9805ccbab5c605c Mon Sep 17 00:00:00 2001
From: Conrado Gouvea
Date: Thu, 13 Oct 2022 17:07:27 -0300
Subject: [PATCH 15/37] update to v11
---
zip-frost.rst | 54 +++++++++++++++++++++++++--------------------------
1 file changed, 26 insertions(+), 28 deletions(-)
diff --git a/zip-frost.rst b/zip-frost.rst
index 27b33f14..e56218b9 100644
--- a/zip-frost.rst
+++ b/zip-frost.rst
@@ -169,23 +169,23 @@ as follows: ::
Inputs:
- commitment_list = [(i, hiding_nonce_commitment_i, binding_nonce_commitment_i), ...],
- a list of commitments issued by each signer, where each element in the list
- indicates the signer identifier i and their two commitment Element values
+ a list of commitments issued by each participant, where each element in the list
+ indicates the participant identifier i and their two commitment Element values
(hiding_nonce_commitment_i, binding_nonce_commitment_i). This list MUST be sorted
- in ascending order by signer identifier.
+ in ascending order by participant identifier.
- msg, the message to be signed.
- randomizer_point, an element in G.
Outputs: A list of (identifier, Scalar) tuples representing the binding factors.
- def compute_binding_factors(commitment_list, msg, randomizer_point):
+ def compute_binding_factors(commitment_list, msg):
msg_hash = H4(msg)
encoded_commitment_hash = H5(encode_group_commitment_list(commitment_list))
- rho_input_prefix = msg_hash || encoded_commitment_hash || G.SerializeElement(randomizer_point)
+ rho_input_prefix = msg_hash || encoded_commitment_hash
binding_factor_list = []
for (identifier, hiding_nonce_commitment, binding_nonce_commitment) in commitment_list:
- rho_input = rho_input_prefix || encode_uint16(identifier)
+ rho_input = rho_input_prefix || G.SerializeScalar(identifier)
binding_factor = H1(rho_input)
binding_factor_list.append((identifier, binding_factor))
return binding_factor_list
@@ -216,7 +216,7 @@ The `sign` function is changed to receive `randomizer` and incorporate it into t
computation of the binding factor. It is specified as the following: ::
Inputs:
- - identifier, Identifier i of the signer. Note identifier will never equal 0.
+ - identifier, Identifier i of the participant. Note identifier will never equal 0.
- sk_i, Signer secret key share, a Scalar.
- group_public_key, public key corresponding to the group signing key,
an Element in G.
@@ -225,15 +225,15 @@ computation of the binding factor. It is specified as the following: ::
- msg, the message to be signed (sent by the Coordinator).
- commitment_list =
[(j, hiding_nonce_commitment_j, binding_nonce_commitment_j), ...], a
- list of commitments issued in Round 1 by each signer and sent by the Coordinator.
- Each element in the list indicates the signer identifier j and their two commitment
+ list of commitments issued in Round 1 by each participant and sent by the Coordinator.
+ Each element in the list indicates the participant identifier j and their two commitment
Element values (hiding_nonce_commitment_j, binding_nonce_commitment_j).
- This list MUST be sorted in ascending order by signer identifier.
+ This list MUST be sorted in ascending order by participant identifier.
- randomizer_point, an element in G (sent by the Coordinator).
Outputs: a Scalar value representing the signature share
- def sign(identifier, sk_i, group_public_key, nonce_i, msg, commitment_list, randomizer_point):
+ def sign(identifier, sk_i, group_public_key, nonce_i, msg, commitment_list):
# Compute the randomized group public key
randomized_group_public_key = group_public_key + randomizer_point
@@ -246,7 +246,7 @@ computation of the binding factor. It is specified as the following: ::
# Compute Lagrange coefficient
participant_list = participants_from_commitment_list(commitment_list)
- lambda_i = derive_lagrange_coefficient(Scalar(identifier), participant_list)
+ lambda_i = derive_lagrange_coefficient(identifier, participant_list)
# Compute the per-message challenge
challenge = compute_challenge(group_commitment, randomized_group_public_key, msg)
@@ -265,19 +265,19 @@ The `verify_signature_share` is changed to incorporate the randomizer point,
as follows: ::
Inputs:
- - identifier, Identifier i of the signer. Note: identifier MUST never equal 0.
- - PK_i, the public key for the ith signer, where PK_i = G.ScalarBaseMult(sk_i),
+ - identifier, Identifier i of the participant. Note: identifier MUST never equal 0.
+ - PK_i, the public key for the ith participant, where PK_i = G.ScalarBaseMult(sk_i),
an Element in G
- comm_i, pair of Element values in G (hiding_nonce_commitment, binding_nonce_commitment)
- generated in round one from the ith signer.
+ generated in round one from the ith participant.
- sig_share_i, a Scalar value indicating the signature share as produced in
- round two from the ith signer.
+ round two from the ith participant.
- commitment_list =
[(j, hiding_nonce_commitment_j, binding_nonce_commitment_j), ...], a
- list of commitments issued in Round 1 by each signer, where each element
- in the list indicates the signer identifier j and their two commitment
+ list of commitments issued in Round 1 by each participant, where each element
+ in the list indicates the participant identifier j and their two commitment
Element values (hiding_nonce_commitment_j, binding_nonce_commitment_j).
- This list MUST be sorted in ascending order by signer identifier.
+ This list MUST be sorted in ascending order by participant identifier.
- group_public_key, public key corresponding to the group signing key,
an Element in G.
- msg, the message to be signed.
@@ -306,7 +306,7 @@ as follows: ::
# Compute Lagrange coefficient
participant_list = participants_from_commitment_list(commitment_list)
- lambda_i = derive_lagrange_coefficient(Scalar(identifier), participant_list)
+ lambda_i = derive_lagrange_coefficient(identifier, participant_list)
# Compute relation values
l = G.ScalarBaseMult(sig_share_i)
@@ -319,8 +319,8 @@ The `aggregate` function is changed to incorporate the randomizer as follows: ::
Inputs:
- group_commitment, the group commitment returned by compute_group_commitment,
an Element in G.
- - sig_shares, a set of signature shares z_i, Scalar values, for each signer,
- of length NUM_SIGNERS, where MIN_SIGNERS <= NUM_SIGNERS <= MAX_SIGNERS.
+ - sig_shares, a set of signature shares z_i, Scalar values, for each participant,
+ of length NUM_PARTICIPANTS, where MIN_PARTICIPANTS <= NUM_PARTICIPANTS <= MAX_PARTICIPANTS.
- group_public_key, public key corresponding to the group signing key,
- challenge, the challenge returned by compute_challenge, a Scalar.
- randomizer, the randomizer Scalar.
@@ -353,11 +353,10 @@ Authorization Signatures as specified in [#protocol-concretespendauthsig]_.
- Identity: as defined in [#protocol-jubjub]_
- RandomScalar(): Implemented by returning a uniformly random Scalar in the range \[0, `G.Order()` - 1\].
Refer to {{frost-randomscalar}} for implementation guidance.
- - RandomNonZeroScalar: Implemented by generating a random 32-byte string that
- is not equal to the all-zero string and invoking DeserializeScalar on the result.
- SerializeElement(P): Implemented as :math:`\mathsf{repr}_\mathbb{J}(P)` as defined in [#protocol-jubjub]_
- DeserializeElement(P): Implemented as :math:`\mathsf{abst}_\mathbb{J}(P)` as defined in [#protocol-jubjub]_,
- failing if :math:`\bot` is returned.
+ failing if :math:`\bot` is returned. Additionally, this function validates that the resulting
+ element is not the group identity element.
- SerializeScalar: Implemented by outputting the little-endian 32-byte encoding
of the Scalar value.
- DeserializeScalar: Implemented by attempting to deserialize a Scalar from a
@@ -395,11 +394,10 @@ Authorization Signatures as specified in [#protocol-concretespendauthsig]_.
- Identity: as defined in [#protocol-pallasandvesta]_
- RandomScalar(): Implemented by returning a uniformly random Scalar in the range \[0, `G.Order()` - 1\].
Refer to {{frost-randomscalar}} for implementation guidance.
- - RandomNonZeroScalar: Implemented by generating a random 32-byte string that
- is not equal to the all-zero string and invoking DeserializeScalar on the result.
- SerializeElement(P): Implemented as :math:`\mathsf{repr}_\mathbb{P}(P)` as defined in [#protocol-pallasandvesta]_
- DeserializeElement(P): Implemented as :math:`\mathsf{abst}_\mathbb{P}(P)` as defined in [#protocol-pallasandvesta]_,
- failing if :math:`\bot` is returned.
+ failing if :math:`\bot` is returned. Additionally, this function validates that the resulting
+ element is not the group identity element.
- SerializeScalar: Implemented by outputting the little-endian 32-byte encoding
of the Scalar value.
- DeserializeScalar: Implemented by attempting to deserialize a Scalar from a
From b2e5634248fa2852abaf7b948a7bd3e9058d4c40 Mon Sep 17 00:00:00 2001
From: Conrado Gouvea
Date: Fri, 21 Oct 2022 16:28:43 -0300
Subject: [PATCH 16/37] small fixes
---
zip-frost.rst | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/zip-frost.rst b/zip-frost.rst
index e56218b9..35a62be6 100644
--- a/zip-frost.rst
+++ b/zip-frost.rst
@@ -178,7 +178,7 @@ as follows: ::
Outputs: A list of (identifier, Scalar) tuples representing the binding factors.
- def compute_binding_factors(commitment_list, msg):
+ def compute_binding_factors(commitment_list, msg, randomizer_point):
msg_hash = H4(msg)
encoded_commitment_hash = H5(encode_group_commitment_list(commitment_list))
rho_input_prefix = msg_hash || encoded_commitment_hash
@@ -212,8 +212,8 @@ and sends it to each signer, over a confidential and authenticated channel,
along with the message and the set of signing commitments. (Note that this differs
from regular FROST which just requires an authenticated channel.)
-The `sign` function is changed to receive `randomizer` and incorporate it into the
-computation of the binding factor. It is specified as the following: ::
+The `sign` function is changed to receive `randomizer_point` and incorporate it
+into the computation of the binding factor. It is specified as the following: ::
Inputs:
- identifier, Identifier i of the participant. Note identifier will never equal 0.
From 5183e06b2ff12859530779d5b650108f20c82c3c Mon Sep 17 00:00:00 2001
From: Conrado Gouvea
Date: Tue, 10 Jan 2023 18:39:53 -0300
Subject: [PATCH 17/37] update to last RFC version
---
zip-frost.rst | 127 +++++++++++++++++++++++++++++---------------------
1 file changed, 73 insertions(+), 54 deletions(-)
diff --git a/zip-frost.rst b/zip-frost.rst
index 35a62be6..59e5c78d 100644
--- a/zip-frost.rst
+++ b/zip-frost.rst
@@ -170,13 +170,14 @@ as follows: ::
Inputs:
- commitment_list = [(i, hiding_nonce_commitment_i, binding_nonce_commitment_i), ...],
a list of commitments issued by each participant, where each element in the list
- indicates the participant identifier i and their two commitment Element values
+ indicates a NonZeroScalar identifier i and two commitment Element values
(hiding_nonce_commitment_i, binding_nonce_commitment_i). This list MUST be sorted
- in ascending order by participant identifier.
+ in ascending order by identifier.
- msg, the message to be signed.
- randomizer_point, an element in G.
- Outputs: A list of (identifier, Scalar) tuples representing the binding factors.
+ Outputs:
+ - binding_factor_list, a list of (NonZeroScalar, Scalar) tuples representing the binding factors.
def compute_binding_factors(commitment_list, msg, randomizer_point):
msg_hash = H4(msg)
@@ -216,22 +217,23 @@ The `sign` function is changed to receive `randomizer_point` and incorporate it
into the computation of the binding factor. It is specified as the following: ::
Inputs:
- - identifier, Identifier i of the participant. Note identifier will never equal 0.
+ - identifier, identifier i of the participant, a NonZeroScalar.
- sk_i, Signer secret key share, a Scalar.
- group_public_key, public key corresponding to the group signing key,
- an Element in G.
+ an Element.
- nonce_i, pair of Scalar values (hiding_nonce, binding_nonce) generated in
round one.
- - msg, the message to be signed (sent by the Coordinator).
+ - msg, the message to be signed, a byte string.
- commitment_list =
[(j, hiding_nonce_commitment_j, binding_nonce_commitment_j), ...], a
list of commitments issued in Round 1 by each participant and sent by the Coordinator.
- Each element in the list indicates the participant identifier j and their two commitment
+ Each element in the list indicates a NonZeroScalar identifier j and two commitment
Element values (hiding_nonce_commitment_j, binding_nonce_commitment_j).
- This list MUST be sorted in ascending order by participant identifier.
+ This list MUST be sorted in ascending order by identifier.
- randomizer_point, an element in G (sent by the Coordinator).
- Outputs: a Scalar value representing the signature share
+ Outputs:
+ - sig_share, a signature share, a Scalar.
def sign(identifier, sk_i, group_public_key, nonce_i, msg, commitment_list):
# Compute the randomized group public key
@@ -244,9 +246,9 @@ into the computation of the binding factor. It is specified as the following: ::
# Compute the group commitment
group_commitment = compute_group_commitment(commitment_list, binding_factor_list)
- # Compute Lagrange coefficient
+ # Compute the interpolating value
participant_list = participants_from_commitment_list(commitment_list)
- lambda_i = derive_lagrange_coefficient(identifier, participant_list)
+ lambda_i = derive_interpolating_value(identifier, participant_list)
# Compute the per-message challenge
challenge = compute_challenge(group_commitment, randomized_group_public_key, msg)
@@ -261,29 +263,67 @@ into the computation of the binding factor. It is specified as the following: ::
Signature Share Verification and Aggregation
''''''''''''''''''''''''''''''''''''''''''''
+The `aggregate` function is changed to incorporate the randomizer as follows: ::
+
+ Inputs:
+ - commitment_list =
+ [(j, hiding_nonce_commitment_j, binding_nonce_commitment_j), ...], a
+ list of commitments issued in Round 1 by each participant, where each element
+ in the list indicates a NonZeroScalar identifier j and two commitment
+ Element values (hiding_nonce_commitment_j, binding_nonce_commitment_j).
+ This list MUST be sorted in ascending order by identifier.
+ - msg, the message to be signed, a byte string.
+ - sig_shares, a set of signature shares z_i, Scalar values, for each participant,
+ of length NUM_PARTICIPANTS, where MIN_PARTICIPANTS <= NUM_PARTICIPANTS <= MAX_PARTICIPANTS.
+ - group_public_key, public key corresponding to the group signing key,
+ - randomizer, the randomizer Scalar.
+
+ Outputs:
+ - (R, z), a Schnorr signature consisting of an Element R and Scalar z.
+ - randomized_group_public_key, the randomized group public key
+
+ def aggregate(commitment_list, msg, sig_shares, group_public_key, randomizer):
+ # Compute the binding factors
+ binding_factor_list = compute_binding_factors(commitment_list, msg)
+
+ # Compute the group commitment
+ group_commitment = compute_group_commitment(commitment_list, binding_factor_list)
+
+ # Compute the challenge
+ randomized_group_public_key = group_public_key + G * randomizer
+ challenge = compute_challenge(group_commitment, randomized_group_public_key, msg)
+
+ # Compute aggregated signature
+ z = Scalar(0)
+ for z_i in sig_shares:
+ z = z + z_i
+ return (group_commitment, z + randomizer * challenge), randomized_group_public_key
+
+
The `verify_signature_share` is changed to incorporate the randomizer point,
as follows: ::
Inputs:
- - identifier, Identifier i of the participant. Note: identifier MUST never equal 0.
- - PK_i, the public key for the ith participant, where PK_i = G.ScalarBaseMult(sk_i),
- an Element in G
+ - identifier, identifier i of the participant, a NonZeroScalar.
+ - PK_i, the public key for the i-th participant, where PK_i = G.ScalarBaseMult(sk_i),
+ an Element.
- comm_i, pair of Element values in G (hiding_nonce_commitment, binding_nonce_commitment)
- generated in round one from the ith participant.
+ generated in round one from the i-th participant.
- sig_share_i, a Scalar value indicating the signature share as produced in
- round two from the ith participant.
+ round two from the i-th participant.
- commitment_list =
[(j, hiding_nonce_commitment_j, binding_nonce_commitment_j), ...], a
list of commitments issued in Round 1 by each participant, where each element
- in the list indicates the participant identifier j and their two commitment
+ in the list indicates a NonZeroScalar identifier j and two commitment
Element values (hiding_nonce_commitment_j, binding_nonce_commitment_j).
- This list MUST be sorted in ascending order by participant identifier.
+ This list MUST be sorted in ascending order by identifier.
- group_public_key, public key corresponding to the group signing key,
- an Element in G.
- - msg, the message to be signed.
+ an Element.
+ - msg, the message to be signed, a byte string.
- randomizer_point, an element in G.
- Outputs: True if the signature share is valid, and False otherwise.
+ Outputs:
+ - True if the signature share is valid, and False otherwise.
def verify_signature_share(identifier, PK_i, comm_i, sig_share_i, commitment_list,
group_public_key, msg, randomizer_point):
@@ -304,9 +344,9 @@ as follows: ::
# Compute the challenge
challenge = compute_challenge(group_commitment, randomized_group_public_key, msg)
- # Compute Lagrange coefficient
+ # Compute the interpolating value
participant_list = participants_from_commitment_list(commitment_list)
- lambda_i = derive_lagrange_coefficient(identifier, participant_list)
+ lambda_i = derive_interpolating_value(identifier, participant_list)
# Compute relation values
l = G.ScalarBaseMult(sig_share_i)
@@ -314,27 +354,6 @@ as follows: ::
return l == r
-The `aggregate` function is changed to incorporate the randomizer as follows: ::
-
- Inputs:
- - group_commitment, the group commitment returned by compute_group_commitment,
- an Element in G.
- - sig_shares, a set of signature shares z_i, Scalar values, for each participant,
- of length NUM_PARTICIPANTS, where MIN_PARTICIPANTS <= NUM_PARTICIPANTS <= MAX_PARTICIPANTS.
- - group_public_key, public key corresponding to the group signing key,
- - challenge, the challenge returned by compute_challenge, a Scalar.
- - randomizer, the randomizer Scalar.
-
- Outputs:
- - (R, z), a Schnorr signature consisting of an Element R and Scalar z.
- - randomized_group_public_key, the randomized group public key
-
- def aggregate(group_commitment, sig_shares, group_public_key, challenge, randomizer):
- randomized_group_public_key = group_public_key + G * randomizer
- z = 0
- for z_i in sig_shares:
- z = z + z_i
- return (group_commitment, z + randomizer * challenge), randomized_group_public_key
Ciphersuites
@@ -351,17 +370,17 @@ Authorization Signatures as specified in [#protocol-concretespendauthsig]_.
- Order: 6554484396890773809930967563523245729705921265872317281365359162392183254199 (see [#protocol-jubjub]_)
- Identity: as defined in [#protocol-jubjub]_
- - RandomScalar(): Implemented by returning a uniformly random Scalar in the range \[0, `G.Order()` - 1\].
- Refer to {{frost-randomscalar}} for implementation guidance.
+ - RandomScalar(): Implemented by returning a uniformly random Scalar in the range
+ \[0, `G.Order()` - 1\]. Refer to {{frost-randomscalar}} for implementation guidance.
- SerializeElement(P): Implemented as :math:`\mathsf{repr}_\mathbb{J}(P)` as defined in [#protocol-jubjub]_
- DeserializeElement(P): Implemented as :math:`\mathsf{abst}_\mathbb{J}(P)` as defined in [#protocol-jubjub]_,
failing if :math:`\bot` is returned. Additionally, this function validates that the resulting
- element is not the group identity element.
+ element is not the group identity element, returning an error if the check fails.
- SerializeScalar: Implemented by outputting the little-endian 32-byte encoding
of the Scalar value.
- DeserializeScalar: Implemented by attempting to deserialize a Scalar from a
- little-endian 32-byte string. This function can fail if the input does not represent a Scalar
- in the range \[0, `G.Order()` - 1\].
+ little-endian 32-byte string. This function can fail if the input does not
+ represent a Scalar in the range \[0, `G.Order()` - 1\].
- Hash (`H`): BLAKE2b-512 [#BLAKE]_ (BLAKE2b with 512-bit output and 16-byte personalization string),
and Nh = 64.
@@ -392,17 +411,17 @@ Authorization Signatures as specified in [#protocol-concretespendauthsig]_.
- Order: 0x40000000000000000000000000000000224698fc0994a8dd8c46eb2100000001 (see [#protocol-pallasandvesta]_)
- Identity: as defined in [#protocol-pallasandvesta]_
- - RandomScalar(): Implemented by returning a uniformly random Scalar in the range \[0, `G.Order()` - 1\].
- Refer to {{frost-randomscalar}} for implementation guidance.
+ - RandomScalar(): Implemented by returning a uniformly random Scalar in the range
+ \[0, `G.Order()` - 1\]. Refer to {{frost-randomscalar}} for implementation guidance.
- SerializeElement(P): Implemented as :math:`\mathsf{repr}_\mathbb{P}(P)` as defined in [#protocol-pallasandvesta]_
- DeserializeElement(P): Implemented as :math:`\mathsf{abst}_\mathbb{P}(P)` as defined in [#protocol-pallasandvesta]_,
failing if :math:`\bot` is returned. Additionally, this function validates that the resulting
- element is not the group identity element.
+ element is not the group identity element, returning an error if the check fails.
- SerializeScalar: Implemented by outputting the little-endian 32-byte encoding
of the Scalar value.
- DeserializeScalar: Implemented by attempting to deserialize a Scalar from a
- little-endian 32-byte string. This function can fail if the input does not represent a Scalar
- in the range \[0, `G.Order()` - 1\].
+ little-endian 32-byte string. This function can fail if the input does not
+ represent a Scalar in the range \[0, `G.Order()` - 1\].
- Hash (`H`): BLAKE2b-512 [#BLAKE]_ (BLAKE2b with 512-bit output and 16-byte personalization string),
and Nh = 64.
From a7fd12c00daf0c2307e39d0db981396646c4da8a Mon Sep 17 00:00:00 2001
From: Conrado Gouvea
Date: Wed, 11 Jan 2023 19:25:08 -0300
Subject: [PATCH 18/37] Apply suggestions from code review
Co-authored-by: Daira Hopwood
---
zip-frost.rst | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/zip-frost.rst b/zip-frost.rst
index 59e5c78d..feabd010 100644
--- a/zip-frost.rst
+++ b/zip-frost.rst
@@ -1,6 +1,6 @@
::
- ZIP: Unassigned {numbers are assigned by ZIP editors}
+ ZIP: 312
Title: FROST for Spend Authorization Signatures
Owners: Conrado Gouvea
Chelsea Komlo
@@ -24,7 +24,8 @@ be interpreted as described in RFC 2119. [#RFC2119]_
The terms below are to be interpreted as follows:
Unlinkability
- The impossibility of determining if two transactions were generated by the same
+ The property of statistical independence of signatures from the signers' long-term keys, ensuring that
+ (for perfectly uniform generation of Randomizers and no leakage of metadata) it is impossible to determine whether two transactions were generated by the same
party.
From bf9edbd0ef86fe978570365c4d4de9c2d3429fa6 Mon Sep 17 00:00:00 2001
From: Conrado Gouvea
Date: Thu, 12 Jan 2023 11:37:57 -0300
Subject: [PATCH 19/37] Clean up header
---
zip-frost.rst | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/zip-frost.rst b/zip-frost.rst
index feabd010..9085cbd8 100644
--- a/zip-frost.rst
+++ b/zip-frost.rst
@@ -5,12 +5,11 @@
Owners: Conrado Gouvea
Chelsea Komlo
Deirdre Connolly
- Credits: First Credited
- ...
Status: Draft
Category: Wallet
Created: 2022-08-dd
- License: {usually MIT}
+ License: MIT
+ Discussions-To:
Pull-Request:
From 48f8c2c80e890385a9619e4dbf3b62ff8963b010 Mon Sep 17 00:00:00 2001
From: Conrado Gouvea
Date: Thu, 12 Jan 2023 11:38:19 -0300
Subject: [PATCH 20/37] rename to zip-312.rst
---
zip-0312.rst | 464 ++++++++++++++++++++++++++++++++++++++++++++++++-
zip-frost.rst | 465 --------------------------------------------------
2 files changed, 461 insertions(+), 468 deletions(-)
delete mode 100644 zip-frost.rst
diff --git a/zip-0312.rst b/zip-0312.rst
index 8f9c3c40..9085cbd8 100644
--- a/zip-0312.rst
+++ b/zip-0312.rst
@@ -1,7 +1,465 @@
::
ZIP: 312
- Title: Shielded Multisignatures using FROST
- Status: Reserved
- Category: Standards / RPC / Wallet
+ Title: FROST for Spend Authorization Signatures
+ Owners: Conrado Gouvea
+ Chelsea Komlo
+ Deirdre Connolly
+ Status: Draft
+ Category: Wallet
+ Created: 2022-08-dd
+ License: MIT
Discussions-To:
+ Pull-Request:
+
+
+Terminology
+===========
+
+{Edit this to reflect the key words that are actually used.}
+The key words "MUST", "MUST NOT", "SHOULD", and "MAY" in this document are to
+be interpreted as described in RFC 2119. [#RFC2119]_
+
+The terms below are to be interpreted as follows:
+
+Unlinkability
+ The property of statistical independence of signatures from the signers' long-term keys, ensuring that
+ (for perfectly uniform generation of Randomizers and no leakage of metadata) it is impossible to determine whether two transactions were generated by the same
+ party.
+
+
+Abstract
+========
+
+This proposal adapts FROST [#FROST]_, a threshold signature scheme,
+to make it unlinkable, which is a requirement for its use in the Zcash protocol.
+The adapted scheme generates signatures compatible with spend authorization
+signatures in the Zcash protocol, for the Sapling and Orchard network upgrades.
+
+
+Motivation
+==========
+
+In the Zcash protocol, Spend Authorization Signatures are employed to authorize
+a transaction. The ability to generate these signatures with the user's
+private key is what effectively allows the user to spend funds.
+
+This is a security-critical step, since anyone who obtains access to the private
+key will be able to spend the user's funds. For this reason, one interesting
+possibility is to require multiple parties to allow the transaction to go through.
+This can be accomplished with threshold signatures, where the private key is
+split between parties in a way that a threshold (e.g. 2 out of 3) of them must
+sign the transaction in order to create the final signature. This enables scenarios
+such as users and exchanges sharing custody of a wallet, for example.
+
+FROST is one of such threshold signature protocols. However, it can't be used as-is
+since the Zcash protocol also requires re-randomizing public and private keys
+to ensure unlinkability between transactions. This ZIP specifies a variant of
+FROST with re-randomization support.
+
+
+Requirements
+============
+
+- All signatures generated by following this ZIP must be verified successfully.
+ as Sapling or Orchard spend authorization signatures.
+- The signatures generated by following this ZIP should meet the security criteria
+ for Signature with Re-Randomizable Keys as specified in the Zcash protocol [#protocol-concretereddsa]_.
+- The threat model described below must be taken into account.
+
+Threat Model
+------------
+
+In normal usage, a Zcash user follows multiple steps in order to generate a
+shielded transaction:
+
+- The transaction is created.
+- The transaction is signed with a re-randomized version of the user's spend
+ authorization private key.
+- The zero-knowledge proof for the transaction is created with the randomizer
+ as an auxiliary (secret) input, among others.
+
+When employing re-randomizable FROST as specified in this ZIP, the goal is to
+split the spend authorization private key :math:`\mathsf{ask}` among multiple
+possible signers. This means that the proof generation will still be generated
+by a single participant, likely the one that created the transaction in the first
+place. Note that this user already controls the privacy of the transaction since
+they are responsible for creating the proof.
+
+This fits well into the "Coordinator" role from the FROST specification [#frost-protocol]_.
+The Coordinator is responsible for sending the message to be signed to all participants,
+and to aggregate the signature shares.
+
+With those considerations in mind, the threat model considered in this ZIP is:
+
+- The Coordinator is trusted with the privacy of the transaction (which includes
+ the unlinkability property). A rogue Coordinator will be able to break
+ unlinkability and privacy, but should not be able to create signed transactions
+ without the approval of `MIN_SIGNERS` participants, as specified in FROST.
+- All key share holders are also trusted with the privacy and of the transaction,
+ thus a rogue key share holder will be able to break its privacy and unlinkability.
+ A future specification may support a scenario where individual key share
+ holders are not trusted with it.
+
+
+Non-requirements
+================
+
+- This ZIP does not support removing the Coordinator role, as described in #[frost-removingcoordinator]_.
+- This ZIP does not prevent key share holders from linking the signing operation to a transaction in the blockchain.
+- Like the FROST specification [#FROST], this ZIP does not specify a key generation
+ procedure; but refer to that specification for guidelines.
+- Network privacy is not in scope for this ZIP, and must be obtained with other
+ tools if desired.
+
+
+Specification
+=============
+
+Algorithms in this section are specified using Python pseudo-code, in the same
+fashion as the FROST specification [#FROST]_.
+
+The types Scalar, Element, and G are defined in #[frost-primeordergroup]_, as well
+as the notation for elliptic-curve arithmetic, which uses the additive notation.
+
+
+Re-randomizable FROST
+---------------------
+
+To add re-randomization to FROST, follow the specification [#FROST]_ with the
+following modifications.
+
+
+Key Generation
+''''''''''''''
+
+While key generation is out of scope for this ZIP and the FROST spec [#FROST]_,
+it needs to be consistent with FROST, see [#frost-tdkg]_ for guidance. The
+spend authorization private key :math:`\mathsf{ask}` [#protocol-spendauthsig]_
+is the particular key that must be used in the context of this ZIP. Note that
+while Sapling allows creating it directly, in Orchard it is always derived
+from the spending key :math:`\mathsf{ask}`. This means that a trusted
+dealer key generation process might be required as detailed in [#frost-tdkg]_.
+
+
+Randomizer Generation
+'''''''''''''''''''''
+
+A new helper function is defined, which computes :math:`\mathsf{RedDSA.GenRandom}`:
+
+::
+
+ randomizer_generate():
+
+ Inputs:
+ - None
+
+ Outputs: randomizer, a Scalar
+
+ def randomizer_generate():
+ randomizer_input = random_bytes(64)
+ return H3(randomizer_input)
+
+
+Binding Factor Computation
+''''''''''''''''''''''''''
+
+The `compute_binding_factor` function is changed to receive the `randomizer_point`
+as follows: ::
+
+ Inputs:
+ - commitment_list = [(i, hiding_nonce_commitment_i, binding_nonce_commitment_i), ...],
+ a list of commitments issued by each participant, where each element in the list
+ indicates a NonZeroScalar identifier i and two commitment Element values
+ (hiding_nonce_commitment_i, binding_nonce_commitment_i). This list MUST be sorted
+ in ascending order by identifier.
+ - msg, the message to be signed.
+ - randomizer_point, an element in G.
+
+ Outputs:
+ - binding_factor_list, a list of (NonZeroScalar, Scalar) tuples representing the binding factors.
+
+ def compute_binding_factors(commitment_list, msg, randomizer_point):
+ msg_hash = H4(msg)
+ encoded_commitment_hash = H5(encode_group_commitment_list(commitment_list))
+ rho_input_prefix = msg_hash || encoded_commitment_hash
+
+ binding_factor_list = []
+ for (identifier, hiding_nonce_commitment, binding_nonce_commitment) in commitment_list:
+ rho_input = rho_input_prefix || G.SerializeScalar(identifier)
+ binding_factor = H1(rho_input)
+ binding_factor_list.append((identifier, binding_factor))
+ return binding_factor_list
+
+
+Round One - Commitment
+''''''''''''''''''''''
+
+Roune One is exactly the same as specified #[FROST]_. But for context, it
+involves these steps:
+
+- Each signer generates nonces and their corresponding public commitments.
+ A nonce is a pair of Scalar values, and a commitment is a pair of Element values.
+- The nonces are stored locally by the signer and kept private for use in the second round.
+- The commitments are sent to the Coordinator.
+
+
+Round Two - Signature Share Generation
+''''''''''''''''''''''''''''''''''''''
+
+In Round Two, the Coordinator generates a random scalar `randomizer` by calling
+`randomizer_generate`. Then it computes `randomizer_point = G.ScalarBaseMult(randomizer)`
+and sends it to each signer, over a confidential and authenticated channel,
+along with the message and the set of signing commitments. (Note that this differs
+from regular FROST which just requires an authenticated channel.)
+
+The `sign` function is changed to receive `randomizer_point` and incorporate it
+into the computation of the binding factor. It is specified as the following: ::
+
+ Inputs:
+ - identifier, identifier i of the participant, a NonZeroScalar.
+ - sk_i, Signer secret key share, a Scalar.
+ - group_public_key, public key corresponding to the group signing key,
+ an Element.
+ - nonce_i, pair of Scalar values (hiding_nonce, binding_nonce) generated in
+ round one.
+ - msg, the message to be signed, a byte string.
+ - commitment_list =
+ [(j, hiding_nonce_commitment_j, binding_nonce_commitment_j), ...], a
+ list of commitments issued in Round 1 by each participant and sent by the Coordinator.
+ Each element in the list indicates a NonZeroScalar identifier j and two commitment
+ Element values (hiding_nonce_commitment_j, binding_nonce_commitment_j).
+ This list MUST be sorted in ascending order by identifier.
+ - randomizer_point, an element in G (sent by the Coordinator).
+
+ Outputs:
+ - sig_share, a signature share, a Scalar.
+
+ def sign(identifier, sk_i, group_public_key, nonce_i, msg, commitment_list):
+ # Compute the randomized group public key
+ randomized_group_public_key = group_public_key + randomizer_point
+
+ # Compute the binding factor(s)
+ binding_factor_list = compute_binding_factors(commitment_list, msg, randomizer_point)
+ binding_factor = binding_factor_for_participant(binding_factor_list, identifier)
+
+ # Compute the group commitment
+ group_commitment = compute_group_commitment(commitment_list, binding_factor_list)
+
+ # Compute the interpolating value
+ participant_list = participants_from_commitment_list(commitment_list)
+ lambda_i = derive_interpolating_value(identifier, participant_list)
+
+ # Compute the per-message challenge
+ challenge = compute_challenge(group_commitment, randomized_group_public_key, msg)
+
+ # Compute the signature share
+ (hiding_nonce, binding_nonce) = nonce_i
+ sig_share = hiding_nonce + (binding_nonce * binding_factor) + (lambda_i * sk_i * challenge)
+
+ return sig_share
+
+
+Signature Share Verification and Aggregation
+''''''''''''''''''''''''''''''''''''''''''''
+
+The `aggregate` function is changed to incorporate the randomizer as follows: ::
+
+ Inputs:
+ - commitment_list =
+ [(j, hiding_nonce_commitment_j, binding_nonce_commitment_j), ...], a
+ list of commitments issued in Round 1 by each participant, where each element
+ in the list indicates a NonZeroScalar identifier j and two commitment
+ Element values (hiding_nonce_commitment_j, binding_nonce_commitment_j).
+ This list MUST be sorted in ascending order by identifier.
+ - msg, the message to be signed, a byte string.
+ - sig_shares, a set of signature shares z_i, Scalar values, for each participant,
+ of length NUM_PARTICIPANTS, where MIN_PARTICIPANTS <= NUM_PARTICIPANTS <= MAX_PARTICIPANTS.
+ - group_public_key, public key corresponding to the group signing key,
+ - randomizer, the randomizer Scalar.
+
+ Outputs:
+ - (R, z), a Schnorr signature consisting of an Element R and Scalar z.
+ - randomized_group_public_key, the randomized group public key
+
+ def aggregate(commitment_list, msg, sig_shares, group_public_key, randomizer):
+ # Compute the binding factors
+ binding_factor_list = compute_binding_factors(commitment_list, msg)
+
+ # Compute the group commitment
+ group_commitment = compute_group_commitment(commitment_list, binding_factor_list)
+
+ # Compute the challenge
+ randomized_group_public_key = group_public_key + G * randomizer
+ challenge = compute_challenge(group_commitment, randomized_group_public_key, msg)
+
+ # Compute aggregated signature
+ z = Scalar(0)
+ for z_i in sig_shares:
+ z = z + z_i
+ return (group_commitment, z + randomizer * challenge), randomized_group_public_key
+
+
+The `verify_signature_share` is changed to incorporate the randomizer point,
+as follows: ::
+
+ Inputs:
+ - identifier, identifier i of the participant, a NonZeroScalar.
+ - PK_i, the public key for the i-th participant, where PK_i = G.ScalarBaseMult(sk_i),
+ an Element.
+ - comm_i, pair of Element values in G (hiding_nonce_commitment, binding_nonce_commitment)
+ generated in round one from the i-th participant.
+ - sig_share_i, a Scalar value indicating the signature share as produced in
+ round two from the i-th participant.
+ - commitment_list =
+ [(j, hiding_nonce_commitment_j, binding_nonce_commitment_j), ...], a
+ list of commitments issued in Round 1 by each participant, where each element
+ in the list indicates a NonZeroScalar identifier j and two commitment
+ Element values (hiding_nonce_commitment_j, binding_nonce_commitment_j).
+ This list MUST be sorted in ascending order by identifier.
+ - group_public_key, public key corresponding to the group signing key,
+ an Element.
+ - msg, the message to be signed, a byte string.
+ - randomizer_point, an element in G.
+
+ Outputs:
+ - True if the signature share is valid, and False otherwise.
+
+ def verify_signature_share(identifier, PK_i, comm_i, sig_share_i, commitment_list,
+ group_public_key, msg, randomizer_point):
+ # Compute the randomized group public key
+ randomized_group_public_key = group_public_key + randomizer_point
+
+ # Compute the binding factors
+ binding_factor_list = compute_binding_factors(commitment_list, msg, randomizer_point)
+ binding_factor = binding_factor_for_participant(binding_factor_list, identifier)
+
+ # Compute the group commitment
+ group_commitment = compute_group_commitment(commitment_list, binding_factor_list)
+
+ # Compute the commitment share
+ (hiding_nonce_commitment, binding_nonce_commitment) = comm_i
+ comm_share = hiding_nonce_commitment + G.ScalarMult(binding_nonce_commitment, binding_factor)
+
+ # Compute the challenge
+ challenge = compute_challenge(group_commitment, randomized_group_public_key, msg)
+
+ # Compute the interpolating value
+ participant_list = participants_from_commitment_list(commitment_list)
+ lambda_i = derive_interpolating_value(identifier, participant_list)
+
+ # Compute relation values
+ l = G.ScalarBaseMult(sig_share_i)
+ r = comm_share + G.ScalarMult(PK_i, challenge * lambda_i)
+
+ return l == r
+
+
+
+Ciphersuites
+------------
+
+FROST(Jubjub, BLAKE2b-512)
+'''''''''''''''''''''''''''''
+
+This ciphersuite uses Jubjub for the Group and BLAKE2b-512 for the Hash function `H`
+meant to produce signatures indistinguishable from RedJubjub Sapling Spend
+Authorization Signatures as specified in [#protocol-concretespendauthsig]_.
+
+- Group: Jubjub [#protocol-jubjub]_
+
+ - Order: 6554484396890773809930967563523245729705921265872317281365359162392183254199 (see [#protocol-jubjub]_)
+ - Identity: as defined in [#protocol-jubjub]_
+ - RandomScalar(): Implemented by returning a uniformly random Scalar in the range
+ \[0, `G.Order()` - 1\]. Refer to {{frost-randomscalar}} for implementation guidance.
+ - SerializeElement(P): Implemented as :math:`\mathsf{repr}_\mathbb{J}(P)` as defined in [#protocol-jubjub]_
+ - DeserializeElement(P): Implemented as :math:`\mathsf{abst}_\mathbb{J}(P)` as defined in [#protocol-jubjub]_,
+ failing if :math:`\bot` is returned. Additionally, this function validates that the resulting
+ element is not the group identity element, returning an error if the check fails.
+ - SerializeScalar: Implemented by outputting the little-endian 32-byte encoding
+ of the Scalar value.
+ - DeserializeScalar: Implemented by attempting to deserialize a Scalar from a
+ little-endian 32-byte string. This function can fail if the input does not
+ represent a Scalar in the range \[0, `G.Order()` - 1\].
+
+- Hash (`H`): BLAKE2b-512 [#BLAKE]_ (BLAKE2b with 512-bit output and 16-byte personalization string),
+ and Nh = 64.
+
+ - H1(m): Implemented by computing BLAKE2b-512("FROST_RedJubjubR", m), interpreting
+ the 64 bytes as a little-endian integer, and reducing the resulting integer
+ modulo L = 6554484396890773809930967563523245729705921265872317281365359162392183254199.
+ - H2(m): Implemented by computing BLAKE2b-512("Zcash_RedJubjubH", m), interpreting
+ the 64 bytes as a little-endian integer, and reducing the resulting integer
+ modulo L = 6554484396890773809930967563523245729705921265872317281365359162392183254199.
+ (This is equivalent to :math:`\mathsf{H}^\circledast(m)`, as defined in
+ [#protocol-concretereddsa]_ parametrized with [#protocol-jubjub]_.)
+ - H3(m): Implemented by computing BLAKE2b-512("FROST_RedJubjubN", m), interpreting
+ the 64 bytes as a little-endian integer, and reducing the resulting integer
+ modulo L = 6554484396890773809930967563523245729705921265872317281365359162392183254199.
+ - H4(m): Implemented by computing BLAKE2b-512("FROST_RedJubjubM", m)
+ - H5(m): Implemented by computing BLAKE2b-512("FROST_RedJubjubC", m)
+
+
+FROST(Pallas, BLAKE2b-512)
+'''''''''''''''''''''''''''''
+
+This ciphersuite uses Pallas for the Group and BLAKE2b-512 for the Hash function `H`
+meant to produce signatures indistinguishable from RedPallas Orchard Spend
+Authorization Signatures as specified in [#protocol-concretespendauthsig]_.
+
+- Group: Pallas [#protocol-pallasandvesta]_
+
+ - Order: 0x40000000000000000000000000000000224698fc0994a8dd8c46eb2100000001 (see [#protocol-pallasandvesta]_)
+ - Identity: as defined in [#protocol-pallasandvesta]_
+ - RandomScalar(): Implemented by returning a uniformly random Scalar in the range
+ \[0, `G.Order()` - 1\]. Refer to {{frost-randomscalar}} for implementation guidance.
+ - SerializeElement(P): Implemented as :math:`\mathsf{repr}_\mathbb{P}(P)` as defined in [#protocol-pallasandvesta]_
+ - DeserializeElement(P): Implemented as :math:`\mathsf{abst}_\mathbb{P}(P)` as defined in [#protocol-pallasandvesta]_,
+ failing if :math:`\bot` is returned. Additionally, this function validates that the resulting
+ element is not the group identity element, returning an error if the check fails.
+ - SerializeScalar: Implemented by outputting the little-endian 32-byte encoding
+ of the Scalar value.
+ - DeserializeScalar: Implemented by attempting to deserialize a Scalar from a
+ little-endian 32-byte string. This function can fail if the input does not
+ represent a Scalar in the range \[0, `G.Order()` - 1\].
+
+- Hash (`H`): BLAKE2b-512 [#BLAKE]_ (BLAKE2b with 512-bit output and 16-byte personalization string),
+ and Nh = 64.
+
+ - H1(m): Implemented by computing BLAKE2b-512("FROST_RedPallasR", m), interpreting
+ the 64 bytes as a little-endian integer, and reducing the resulting integer
+ modulo L = 0x40000000000000000000000000000000224698fc0994a8dd8c46eb2100000001.
+ - H2(m): Implemented by computing BLAKE2b-512("Zcash_RedPallasH", m), interpreting
+ the 64 bytes as a little-endian integer, and reducing the resulting integer
+ modulo L = 0x40000000000000000000000000000000224698fc0994a8dd8c46eb2100000001.
+ (This is equivalent to :math:`\mathsf{H}^\circledast(m)`, as defined in
+ [#protocol-concretereddsa]_ parametrized with [#protocol-pallasandvesta]_.)
+ - H3(m): Implemented by computing BLAKE2b-512("FROST_RedPallasN", m), interpreting
+ the 64 bytes as a little-endian integer, and reducing the resulting integer
+ modulo L = 0x40000000000000000000000000000000224698fc0994a8dd8c46eb2100000001.
+ - H4(m): Implemented by computing BLAKE2b-512("FROST_RedPallasM", m).
+ - H5(m): Implemented by computing BLAKE2b-512("FROST_RedPallasC", m).
+
+
+Reference implementation
+========================
+
+TODO: add links to implementation
+
+
+References
+==========
+
+.. [#BLAKE] `BLAKE2: simpler, smaller, fast as MD5 `_
+.. [#RFC2119] `RFC 2119: Key words for use in RFCs to Indicate Requirement Levels `_
+.. [#FROST] `Draft RFC: Two-Round Threshold Schnorr Signatures with FROST `_
+.. [#frost-protocol] `Draft RFC: Two-Round Threshold Schnorr Signatures with FROST. Section 5: Two-Round FROST Signing Protocol `_
+.. [#frost-removingcoordinator] `Draft RFC: Two-Round Threshold Schnorr Signatures with FROST. Section 7.3: Removing the Coordinator Role `_
+.. [#frost-primeordergroup] `Draft RFC: Two-Round Threshold Schnorr Signatures with FROST. Section 3.1: Prime-Order Group `_
+.. [#frost-tdkg] `Draft RFC: Two-Round Threshold Schnorr Signatures with FROST. Appendix B: Trusted Dealer Key Generation `_
+.. [#frost-randomscalar] `Draft RFC: Two-Round Threshold Schnorr Signatures with FROST. Appendix C: Random Scalar Generation `_
+.. [#protocol-concretereddsa] `Zcash Protocol Specification, Version 2022.3.4 [NU5]. Section 5.4.7: RedDSA, RedJubjub, and RedPallas `_
+.. [#protocol-concretespendauthsig] `Zcash Protocol Specification, Version 2022.3.4 [NU5]. Section 5.4.7.1: Spend Authorization Signature (Sapling and Orchard) `_
+.. [#protocol-spendauthsig] `Zcash Protocol Specification, Version 2022.3.4 [NU5]. Section 4.15: Spend Authorization Signature (Sapling and Orchard) `_
+.. [#protocol-jubjub] `Zcash Protocol Specification, Version 2022.3.4 [NU5]. Section 5.4.9.3: Jubjub `_
+.. [#protocol-pallasandvesta] `Zcash Protocol Specification, Version 2022.3.4 [NU5]. Section 5.4.9.6: Pallas and Vesta `_
diff --git a/zip-frost.rst b/zip-frost.rst
deleted file mode 100644
index 9085cbd8..00000000
--- a/zip-frost.rst
+++ /dev/null
@@ -1,465 +0,0 @@
-::
-
- ZIP: 312
- Title: FROST for Spend Authorization Signatures
- Owners: Conrado Gouvea
- Chelsea Komlo
- Deirdre Connolly
- Status: Draft
- Category: Wallet
- Created: 2022-08-dd
- License: MIT
- Discussions-To:
- Pull-Request:
-
-
-Terminology
-===========
-
-{Edit this to reflect the key words that are actually used.}
-The key words "MUST", "MUST NOT", "SHOULD", and "MAY" in this document are to
-be interpreted as described in RFC 2119. [#RFC2119]_
-
-The terms below are to be interpreted as follows:
-
-Unlinkability
- The property of statistical independence of signatures from the signers' long-term keys, ensuring that
- (for perfectly uniform generation of Randomizers and no leakage of metadata) it is impossible to determine whether two transactions were generated by the same
- party.
-
-
-Abstract
-========
-
-This proposal adapts FROST [#FROST]_, a threshold signature scheme,
-to make it unlinkable, which is a requirement for its use in the Zcash protocol.
-The adapted scheme generates signatures compatible with spend authorization
-signatures in the Zcash protocol, for the Sapling and Orchard network upgrades.
-
-
-Motivation
-==========
-
-In the Zcash protocol, Spend Authorization Signatures are employed to authorize
-a transaction. The ability to generate these signatures with the user's
-private key is what effectively allows the user to spend funds.
-
-This is a security-critical step, since anyone who obtains access to the private
-key will be able to spend the user's funds. For this reason, one interesting
-possibility is to require multiple parties to allow the transaction to go through.
-This can be accomplished with threshold signatures, where the private key is
-split between parties in a way that a threshold (e.g. 2 out of 3) of them must
-sign the transaction in order to create the final signature. This enables scenarios
-such as users and exchanges sharing custody of a wallet, for example.
-
-FROST is one of such threshold signature protocols. However, it can't be used as-is
-since the Zcash protocol also requires re-randomizing public and private keys
-to ensure unlinkability between transactions. This ZIP specifies a variant of
-FROST with re-randomization support.
-
-
-Requirements
-============
-
-- All signatures generated by following this ZIP must be verified successfully.
- as Sapling or Orchard spend authorization signatures.
-- The signatures generated by following this ZIP should meet the security criteria
- for Signature with Re-Randomizable Keys as specified in the Zcash protocol [#protocol-concretereddsa]_.
-- The threat model described below must be taken into account.
-
-Threat Model
-------------
-
-In normal usage, a Zcash user follows multiple steps in order to generate a
-shielded transaction:
-
-- The transaction is created.
-- The transaction is signed with a re-randomized version of the user's spend
- authorization private key.
-- The zero-knowledge proof for the transaction is created with the randomizer
- as an auxiliary (secret) input, among others.
-
-When employing re-randomizable FROST as specified in this ZIP, the goal is to
-split the spend authorization private key :math:`\mathsf{ask}` among multiple
-possible signers. This means that the proof generation will still be generated
-by a single participant, likely the one that created the transaction in the first
-place. Note that this user already controls the privacy of the transaction since
-they are responsible for creating the proof.
-
-This fits well into the "Coordinator" role from the FROST specification [#frost-protocol]_.
-The Coordinator is responsible for sending the message to be signed to all participants,
-and to aggregate the signature shares.
-
-With those considerations in mind, the threat model considered in this ZIP is:
-
-- The Coordinator is trusted with the privacy of the transaction (which includes
- the unlinkability property). A rogue Coordinator will be able to break
- unlinkability and privacy, but should not be able to create signed transactions
- without the approval of `MIN_SIGNERS` participants, as specified in FROST.
-- All key share holders are also trusted with the privacy and of the transaction,
- thus a rogue key share holder will be able to break its privacy and unlinkability.
- A future specification may support a scenario where individual key share
- holders are not trusted with it.
-
-
-Non-requirements
-================
-
-- This ZIP does not support removing the Coordinator role, as described in #[frost-removingcoordinator]_.
-- This ZIP does not prevent key share holders from linking the signing operation to a transaction in the blockchain.
-- Like the FROST specification [#FROST], this ZIP does not specify a key generation
- procedure; but refer to that specification for guidelines.
-- Network privacy is not in scope for this ZIP, and must be obtained with other
- tools if desired.
-
-
-Specification
-=============
-
-Algorithms in this section are specified using Python pseudo-code, in the same
-fashion as the FROST specification [#FROST]_.
-
-The types Scalar, Element, and G are defined in #[frost-primeordergroup]_, as well
-as the notation for elliptic-curve arithmetic, which uses the additive notation.
-
-
-Re-randomizable FROST
----------------------
-
-To add re-randomization to FROST, follow the specification [#FROST]_ with the
-following modifications.
-
-
-Key Generation
-''''''''''''''
-
-While key generation is out of scope for this ZIP and the FROST spec [#FROST]_,
-it needs to be consistent with FROST, see [#frost-tdkg]_ for guidance. The
-spend authorization private key :math:`\mathsf{ask}` [#protocol-spendauthsig]_
-is the particular key that must be used in the context of this ZIP. Note that
-while Sapling allows creating it directly, in Orchard it is always derived
-from the spending key :math:`\mathsf{ask}`. This means that a trusted
-dealer key generation process might be required as detailed in [#frost-tdkg]_.
-
-
-Randomizer Generation
-'''''''''''''''''''''
-
-A new helper function is defined, which computes :math:`\mathsf{RedDSA.GenRandom}`:
-
-::
-
- randomizer_generate():
-
- Inputs:
- - None
-
- Outputs: randomizer, a Scalar
-
- def randomizer_generate():
- randomizer_input = random_bytes(64)
- return H3(randomizer_input)
-
-
-Binding Factor Computation
-''''''''''''''''''''''''''
-
-The `compute_binding_factor` function is changed to receive the `randomizer_point`
-as follows: ::
-
- Inputs:
- - commitment_list = [(i, hiding_nonce_commitment_i, binding_nonce_commitment_i), ...],
- a list of commitments issued by each participant, where each element in the list
- indicates a NonZeroScalar identifier i and two commitment Element values
- (hiding_nonce_commitment_i, binding_nonce_commitment_i). This list MUST be sorted
- in ascending order by identifier.
- - msg, the message to be signed.
- - randomizer_point, an element in G.
-
- Outputs:
- - binding_factor_list, a list of (NonZeroScalar, Scalar) tuples representing the binding factors.
-
- def compute_binding_factors(commitment_list, msg, randomizer_point):
- msg_hash = H4(msg)
- encoded_commitment_hash = H5(encode_group_commitment_list(commitment_list))
- rho_input_prefix = msg_hash || encoded_commitment_hash
-
- binding_factor_list = []
- for (identifier, hiding_nonce_commitment, binding_nonce_commitment) in commitment_list:
- rho_input = rho_input_prefix || G.SerializeScalar(identifier)
- binding_factor = H1(rho_input)
- binding_factor_list.append((identifier, binding_factor))
- return binding_factor_list
-
-
-Round One - Commitment
-''''''''''''''''''''''
-
-Roune One is exactly the same as specified #[FROST]_. But for context, it
-involves these steps:
-
-- Each signer generates nonces and their corresponding public commitments.
- A nonce is a pair of Scalar values, and a commitment is a pair of Element values.
-- The nonces are stored locally by the signer and kept private for use in the second round.
-- The commitments are sent to the Coordinator.
-
-
-Round Two - Signature Share Generation
-''''''''''''''''''''''''''''''''''''''
-
-In Round Two, the Coordinator generates a random scalar `randomizer` by calling
-`randomizer_generate`. Then it computes `randomizer_point = G.ScalarBaseMult(randomizer)`
-and sends it to each signer, over a confidential and authenticated channel,
-along with the message and the set of signing commitments. (Note that this differs
-from regular FROST which just requires an authenticated channel.)
-
-The `sign` function is changed to receive `randomizer_point` and incorporate it
-into the computation of the binding factor. It is specified as the following: ::
-
- Inputs:
- - identifier, identifier i of the participant, a NonZeroScalar.
- - sk_i, Signer secret key share, a Scalar.
- - group_public_key, public key corresponding to the group signing key,
- an Element.
- - nonce_i, pair of Scalar values (hiding_nonce, binding_nonce) generated in
- round one.
- - msg, the message to be signed, a byte string.
- - commitment_list =
- [(j, hiding_nonce_commitment_j, binding_nonce_commitment_j), ...], a
- list of commitments issued in Round 1 by each participant and sent by the Coordinator.
- Each element in the list indicates a NonZeroScalar identifier j and two commitment
- Element values (hiding_nonce_commitment_j, binding_nonce_commitment_j).
- This list MUST be sorted in ascending order by identifier.
- - randomizer_point, an element in G (sent by the Coordinator).
-
- Outputs:
- - sig_share, a signature share, a Scalar.
-
- def sign(identifier, sk_i, group_public_key, nonce_i, msg, commitment_list):
- # Compute the randomized group public key
- randomized_group_public_key = group_public_key + randomizer_point
-
- # Compute the binding factor(s)
- binding_factor_list = compute_binding_factors(commitment_list, msg, randomizer_point)
- binding_factor = binding_factor_for_participant(binding_factor_list, identifier)
-
- # Compute the group commitment
- group_commitment = compute_group_commitment(commitment_list, binding_factor_list)
-
- # Compute the interpolating value
- participant_list = participants_from_commitment_list(commitment_list)
- lambda_i = derive_interpolating_value(identifier, participant_list)
-
- # Compute the per-message challenge
- challenge = compute_challenge(group_commitment, randomized_group_public_key, msg)
-
- # Compute the signature share
- (hiding_nonce, binding_nonce) = nonce_i
- sig_share = hiding_nonce + (binding_nonce * binding_factor) + (lambda_i * sk_i * challenge)
-
- return sig_share
-
-
-Signature Share Verification and Aggregation
-''''''''''''''''''''''''''''''''''''''''''''
-
-The `aggregate` function is changed to incorporate the randomizer as follows: ::
-
- Inputs:
- - commitment_list =
- [(j, hiding_nonce_commitment_j, binding_nonce_commitment_j), ...], a
- list of commitments issued in Round 1 by each participant, where each element
- in the list indicates a NonZeroScalar identifier j and two commitment
- Element values (hiding_nonce_commitment_j, binding_nonce_commitment_j).
- This list MUST be sorted in ascending order by identifier.
- - msg, the message to be signed, a byte string.
- - sig_shares, a set of signature shares z_i, Scalar values, for each participant,
- of length NUM_PARTICIPANTS, where MIN_PARTICIPANTS <= NUM_PARTICIPANTS <= MAX_PARTICIPANTS.
- - group_public_key, public key corresponding to the group signing key,
- - randomizer, the randomizer Scalar.
-
- Outputs:
- - (R, z), a Schnorr signature consisting of an Element R and Scalar z.
- - randomized_group_public_key, the randomized group public key
-
- def aggregate(commitment_list, msg, sig_shares, group_public_key, randomizer):
- # Compute the binding factors
- binding_factor_list = compute_binding_factors(commitment_list, msg)
-
- # Compute the group commitment
- group_commitment = compute_group_commitment(commitment_list, binding_factor_list)
-
- # Compute the challenge
- randomized_group_public_key = group_public_key + G * randomizer
- challenge = compute_challenge(group_commitment, randomized_group_public_key, msg)
-
- # Compute aggregated signature
- z = Scalar(0)
- for z_i in sig_shares:
- z = z + z_i
- return (group_commitment, z + randomizer * challenge), randomized_group_public_key
-
-
-The `verify_signature_share` is changed to incorporate the randomizer point,
-as follows: ::
-
- Inputs:
- - identifier, identifier i of the participant, a NonZeroScalar.
- - PK_i, the public key for the i-th participant, where PK_i = G.ScalarBaseMult(sk_i),
- an Element.
- - comm_i, pair of Element values in G (hiding_nonce_commitment, binding_nonce_commitment)
- generated in round one from the i-th participant.
- - sig_share_i, a Scalar value indicating the signature share as produced in
- round two from the i-th participant.
- - commitment_list =
- [(j, hiding_nonce_commitment_j, binding_nonce_commitment_j), ...], a
- list of commitments issued in Round 1 by each participant, where each element
- in the list indicates a NonZeroScalar identifier j and two commitment
- Element values (hiding_nonce_commitment_j, binding_nonce_commitment_j).
- This list MUST be sorted in ascending order by identifier.
- - group_public_key, public key corresponding to the group signing key,
- an Element.
- - msg, the message to be signed, a byte string.
- - randomizer_point, an element in G.
-
- Outputs:
- - True if the signature share is valid, and False otherwise.
-
- def verify_signature_share(identifier, PK_i, comm_i, sig_share_i, commitment_list,
- group_public_key, msg, randomizer_point):
- # Compute the randomized group public key
- randomized_group_public_key = group_public_key + randomizer_point
-
- # Compute the binding factors
- binding_factor_list = compute_binding_factors(commitment_list, msg, randomizer_point)
- binding_factor = binding_factor_for_participant(binding_factor_list, identifier)
-
- # Compute the group commitment
- group_commitment = compute_group_commitment(commitment_list, binding_factor_list)
-
- # Compute the commitment share
- (hiding_nonce_commitment, binding_nonce_commitment) = comm_i
- comm_share = hiding_nonce_commitment + G.ScalarMult(binding_nonce_commitment, binding_factor)
-
- # Compute the challenge
- challenge = compute_challenge(group_commitment, randomized_group_public_key, msg)
-
- # Compute the interpolating value
- participant_list = participants_from_commitment_list(commitment_list)
- lambda_i = derive_interpolating_value(identifier, participant_list)
-
- # Compute relation values
- l = G.ScalarBaseMult(sig_share_i)
- r = comm_share + G.ScalarMult(PK_i, challenge * lambda_i)
-
- return l == r
-
-
-
-Ciphersuites
-------------
-
-FROST(Jubjub, BLAKE2b-512)
-'''''''''''''''''''''''''''''
-
-This ciphersuite uses Jubjub for the Group and BLAKE2b-512 for the Hash function `H`
-meant to produce signatures indistinguishable from RedJubjub Sapling Spend
-Authorization Signatures as specified in [#protocol-concretespendauthsig]_.
-
-- Group: Jubjub [#protocol-jubjub]_
-
- - Order: 6554484396890773809930967563523245729705921265872317281365359162392183254199 (see [#protocol-jubjub]_)
- - Identity: as defined in [#protocol-jubjub]_
- - RandomScalar(): Implemented by returning a uniformly random Scalar in the range
- \[0, `G.Order()` - 1\]. Refer to {{frost-randomscalar}} for implementation guidance.
- - SerializeElement(P): Implemented as :math:`\mathsf{repr}_\mathbb{J}(P)` as defined in [#protocol-jubjub]_
- - DeserializeElement(P): Implemented as :math:`\mathsf{abst}_\mathbb{J}(P)` as defined in [#protocol-jubjub]_,
- failing if :math:`\bot` is returned. Additionally, this function validates that the resulting
- element is not the group identity element, returning an error if the check fails.
- - SerializeScalar: Implemented by outputting the little-endian 32-byte encoding
- of the Scalar value.
- - DeserializeScalar: Implemented by attempting to deserialize a Scalar from a
- little-endian 32-byte string. This function can fail if the input does not
- represent a Scalar in the range \[0, `G.Order()` - 1\].
-
-- Hash (`H`): BLAKE2b-512 [#BLAKE]_ (BLAKE2b with 512-bit output and 16-byte personalization string),
- and Nh = 64.
-
- - H1(m): Implemented by computing BLAKE2b-512("FROST_RedJubjubR", m), interpreting
- the 64 bytes as a little-endian integer, and reducing the resulting integer
- modulo L = 6554484396890773809930967563523245729705921265872317281365359162392183254199.
- - H2(m): Implemented by computing BLAKE2b-512("Zcash_RedJubjubH", m), interpreting
- the 64 bytes as a little-endian integer, and reducing the resulting integer
- modulo L = 6554484396890773809930967563523245729705921265872317281365359162392183254199.
- (This is equivalent to :math:`\mathsf{H}^\circledast(m)`, as defined in
- [#protocol-concretereddsa]_ parametrized with [#protocol-jubjub]_.)
- - H3(m): Implemented by computing BLAKE2b-512("FROST_RedJubjubN", m), interpreting
- the 64 bytes as a little-endian integer, and reducing the resulting integer
- modulo L = 6554484396890773809930967563523245729705921265872317281365359162392183254199.
- - H4(m): Implemented by computing BLAKE2b-512("FROST_RedJubjubM", m)
- - H5(m): Implemented by computing BLAKE2b-512("FROST_RedJubjubC", m)
-
-
-FROST(Pallas, BLAKE2b-512)
-'''''''''''''''''''''''''''''
-
-This ciphersuite uses Pallas for the Group and BLAKE2b-512 for the Hash function `H`
-meant to produce signatures indistinguishable from RedPallas Orchard Spend
-Authorization Signatures as specified in [#protocol-concretespendauthsig]_.
-
-- Group: Pallas [#protocol-pallasandvesta]_
-
- - Order: 0x40000000000000000000000000000000224698fc0994a8dd8c46eb2100000001 (see [#protocol-pallasandvesta]_)
- - Identity: as defined in [#protocol-pallasandvesta]_
- - RandomScalar(): Implemented by returning a uniformly random Scalar in the range
- \[0, `G.Order()` - 1\]. Refer to {{frost-randomscalar}} for implementation guidance.
- - SerializeElement(P): Implemented as :math:`\mathsf{repr}_\mathbb{P}(P)` as defined in [#protocol-pallasandvesta]_
- - DeserializeElement(P): Implemented as :math:`\mathsf{abst}_\mathbb{P}(P)` as defined in [#protocol-pallasandvesta]_,
- failing if :math:`\bot` is returned. Additionally, this function validates that the resulting
- element is not the group identity element, returning an error if the check fails.
- - SerializeScalar: Implemented by outputting the little-endian 32-byte encoding
- of the Scalar value.
- - DeserializeScalar: Implemented by attempting to deserialize a Scalar from a
- little-endian 32-byte string. This function can fail if the input does not
- represent a Scalar in the range \[0, `G.Order()` - 1\].
-
-- Hash (`H`): BLAKE2b-512 [#BLAKE]_ (BLAKE2b with 512-bit output and 16-byte personalization string),
- and Nh = 64.
-
- - H1(m): Implemented by computing BLAKE2b-512("FROST_RedPallasR", m), interpreting
- the 64 bytes as a little-endian integer, and reducing the resulting integer
- modulo L = 0x40000000000000000000000000000000224698fc0994a8dd8c46eb2100000001.
- - H2(m): Implemented by computing BLAKE2b-512("Zcash_RedPallasH", m), interpreting
- the 64 bytes as a little-endian integer, and reducing the resulting integer
- modulo L = 0x40000000000000000000000000000000224698fc0994a8dd8c46eb2100000001.
- (This is equivalent to :math:`\mathsf{H}^\circledast(m)`, as defined in
- [#protocol-concretereddsa]_ parametrized with [#protocol-pallasandvesta]_.)
- - H3(m): Implemented by computing BLAKE2b-512("FROST_RedPallasN", m), interpreting
- the 64 bytes as a little-endian integer, and reducing the resulting integer
- modulo L = 0x40000000000000000000000000000000224698fc0994a8dd8c46eb2100000001.
- - H4(m): Implemented by computing BLAKE2b-512("FROST_RedPallasM", m).
- - H5(m): Implemented by computing BLAKE2b-512("FROST_RedPallasC", m).
-
-
-Reference implementation
-========================
-
-TODO: add links to implementation
-
-
-References
-==========
-
-.. [#BLAKE] `BLAKE2: simpler, smaller, fast as MD5 `_
-.. [#RFC2119] `RFC 2119: Key words for use in RFCs to Indicate Requirement Levels `_
-.. [#FROST] `Draft RFC: Two-Round Threshold Schnorr Signatures with FROST `_
-.. [#frost-protocol] `Draft RFC: Two-Round Threshold Schnorr Signatures with FROST. Section 5: Two-Round FROST Signing Protocol `_
-.. [#frost-removingcoordinator] `Draft RFC: Two-Round Threshold Schnorr Signatures with FROST. Section 7.3: Removing the Coordinator Role `_
-.. [#frost-primeordergroup] `Draft RFC: Two-Round Threshold Schnorr Signatures with FROST. Section 3.1: Prime-Order Group `_
-.. [#frost-tdkg] `Draft RFC: Two-Round Threshold Schnorr Signatures with FROST. Appendix B: Trusted Dealer Key Generation `_
-.. [#frost-randomscalar] `Draft RFC: Two-Round Threshold Schnorr Signatures with FROST. Appendix C: Random Scalar Generation `_
-.. [#protocol-concretereddsa] `Zcash Protocol Specification, Version 2022.3.4 [NU5]. Section 5.4.7: RedDSA, RedJubjub, and RedPallas `_
-.. [#protocol-concretespendauthsig] `Zcash Protocol Specification, Version 2022.3.4 [NU5]. Section 5.4.7.1: Spend Authorization Signature (Sapling and Orchard) `_
-.. [#protocol-spendauthsig] `Zcash Protocol Specification, Version 2022.3.4 [NU5]. Section 4.15: Spend Authorization Signature (Sapling and Orchard) `_
-.. [#protocol-jubjub] `Zcash Protocol Specification, Version 2022.3.4 [NU5]. Section 5.4.9.3: Jubjub `_
-.. [#protocol-pallasandvesta] `Zcash Protocol Specification, Version 2022.3.4 [NU5]. Section 5.4.9.6: Pallas and Vesta `_
From 115f2caac8e61085eaa1aad8a565de5c4da8e1ba Mon Sep 17 00:00:00 2001
From: Conrado Gouvea
Date: Thu, 12 Jan 2023 14:12:36 -0300
Subject: [PATCH 21/37] Apply suggestions from code review
Co-authored-by: Daira Hopwood
---
zip-0312.rst | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/zip-0312.rst b/zip-0312.rst
index 9085cbd8..54b6d457 100644
--- a/zip-0312.rst
+++ b/zip-0312.rst
@@ -10,7 +10,7 @@
Created: 2022-08-dd
License: MIT
Discussions-To:
- Pull-Request:
+ Pull-Request:
Terminology
@@ -61,8 +61,9 @@ FROST with re-randomization support.
Requirements
============
-- All signatures generated by following this ZIP must be verified successfully.
- as Sapling or Orchard spend authorization signatures.
+- All signatures generated by following this ZIP must be verified successfully
+ as Sapling or Orchard spend authorization signatures using the appropriate
+ validating key.
- The signatures generated by following this ZIP should meet the security criteria
for Signature with Re-Randomizable Keys as specified in the Zcash protocol [#protocol-concretereddsa]_.
- The threat model described below must be taken into account.
From 564642f1ffbd7dab3c8099ac1ed10fae2ecc1208 Mon Sep 17 00:00:00 2001
From: Conrado Gouvea
Date: Fri, 13 Jan 2023 11:07:56 -0300
Subject: [PATCH 22/37] Apply suggestions from code review
Co-authored-by: Daira Hopwood
---
zip-0312.rst | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/zip-0312.rst b/zip-0312.rst
index 54b6d457..3a0de1ae 100644
--- a/zip-0312.rst
+++ b/zip-0312.rst
@@ -82,7 +82,7 @@ shielded transaction:
When employing re-randomizable FROST as specified in this ZIP, the goal is to
split the spend authorization private key :math:`\mathsf{ask}` among multiple
-possible signers. This means that the proof generation will still be generated
+possible signers. This means that the proof generation will still be performed
by a single participant, likely the one that created the transaction in the first
place. Note that this user already controls the privacy of the transaction since
they are responsible for creating the proof.
@@ -97,7 +97,7 @@ With those considerations in mind, the threat model considered in this ZIP is:
the unlinkability property). A rogue Coordinator will be able to break
unlinkability and privacy, but should not be able to create signed transactions
without the approval of `MIN_SIGNERS` participants, as specified in FROST.
-- All key share holders are also trusted with the privacy and of the transaction,
+- All key share holders are also trusted with the privacy of the transaction,
thus a rogue key share holder will be able to break its privacy and unlinkability.
A future specification may support a scenario where individual key share
holders are not trusted with it.
@@ -165,7 +165,7 @@ A new helper function is defined, which computes :math:`\mathsf{RedDSA.GenRandom
Binding Factor Computation
''''''''''''''''''''''''''
-The `compute_binding_factor` function is changed to receive the `randomizer_point`
+The `compute_binding_factors` function is changed to receive the `randomizer_point`
as follows: ::
Inputs:
@@ -361,7 +361,7 @@ Ciphersuites
------------
FROST(Jubjub, BLAKE2b-512)
-'''''''''''''''''''''''''''''
+''''''''''''''''''''''''''
This ciphersuite uses Jubjub for the Group and BLAKE2b-512 for the Hash function `H`
meant to produce signatures indistinguishable from RedJubjub Sapling Spend
@@ -388,7 +388,7 @@ Authorization Signatures as specified in [#protocol-concretespendauthsig]_.
- H1(m): Implemented by computing BLAKE2b-512("FROST_RedJubjubR", m), interpreting
the 64 bytes as a little-endian integer, and reducing the resulting integer
- modulo L = 6554484396890773809930967563523245729705921265872317281365359162392183254199.
+ modulo `G.Order()`.
- H2(m): Implemented by computing BLAKE2b-512("Zcash_RedJubjubH", m), interpreting
the 64 bytes as a little-endian integer, and reducing the resulting integer
modulo L = 6554484396890773809930967563523245729705921265872317281365359162392183254199.
@@ -402,7 +402,7 @@ Authorization Signatures as specified in [#protocol-concretespendauthsig]_.
FROST(Pallas, BLAKE2b-512)
-'''''''''''''''''''''''''''''
+''''''''''''''''''''''''''
This ciphersuite uses Pallas for the Group and BLAKE2b-512 for the Hash function `H`
meant to produce signatures indistinguishable from RedPallas Orchard Spend
From f72d8cdb5d15e83ea36619489204a3992972368e Mon Sep 17 00:00:00 2001
From: Conrado Gouvea
Date: Fri, 13 Jan 2023 17:07:41 -0300
Subject: [PATCH 23/37] address multiple comments
---
zip-0312.rst | 78 ++++++++++++++++++++++++++++++++--------------------
1 file changed, 48 insertions(+), 30 deletions(-)
diff --git a/zip-0312.rst b/zip-0312.rst
index 3a0de1ae..3d6a2c76 100644
--- a/zip-0312.rst
+++ b/zip-0312.rst
@@ -99,8 +99,6 @@ With those considerations in mind, the threat model considered in this ZIP is:
without the approval of `MIN_SIGNERS` participants, as specified in FROST.
- All key share holders are also trusted with the privacy of the transaction,
thus a rogue key share holder will be able to break its privacy and unlinkability.
- A future specification may support a scenario where individual key share
- holders are not trusted with it.
Non-requirements
@@ -120,8 +118,12 @@ Specification
Algorithms in this section are specified using Python pseudo-code, in the same
fashion as the FROST specification [#FROST]_.
-The types Scalar, Element, and G are defined in #[frost-primeordergroup]_, as well
-as the notation for elliptic-curve arithmetic, which uses the additive notation.
+The types Scalar, Element, and G are defined in #[frost-primeordergroup]_, as
+well as the notation for elliptic-curve arithmetic, which uses the additive
+notation. Note that this notation differs from that used in the Zcash Protocol
+Specification. For example, `G.ScalarMult(P, k)` is used for scalar
+multiplication, where the protocol spec would use :math:`[k] P` with the group
+implied by :math:`P`.
Re-randomizable FROST
@@ -138,9 +140,9 @@ While key generation is out of scope for this ZIP and the FROST spec [#FROST]_,
it needs to be consistent with FROST, see [#frost-tdkg]_ for guidance. The
spend authorization private key :math:`\mathsf{ask}` [#protocol-spendauthsig]_
is the particular key that must be used in the context of this ZIP. Note that
-while Sapling allows creating it directly, in Orchard it is always derived
-from the spending key :math:`\mathsf{ask}`. This means that a trusted
-dealer key generation process might be required as detailed in [#frost-tdkg]_.
+the :math:`\mathsf{ask}` is usually derived from the spending key :math:`\mathsf{sk}`,
+though that is not required. Doing so might require a trusted dealer key generation
+process as detailed in [#frost-tdkg]_ (as opposed to distributed key generation).
Randomizer Generation
@@ -183,7 +185,7 @@ as follows: ::
def compute_binding_factors(commitment_list, msg, randomizer_point):
msg_hash = H4(msg)
encoded_commitment_hash = H5(encode_group_commitment_list(commitment_list))
- rho_input_prefix = msg_hash || encoded_commitment_hash
+ rho_input_prefix = msg_hash || encoded_commitment_hash || G.SerializeElement(randomizer_point)
binding_factor_list = []
for (identifier, hiding_nonce_commitment, binding_nonce_commitment) in commitment_list:
@@ -214,6 +216,16 @@ and sends it to each signer, over a confidential and authenticated channel,
along with the message and the set of signing commitments. (Note that this differs
from regular FROST which just requires an authenticated channel.)
+In Zcash, the message that needs to be signed is actually the SIGHASH
+transaction hash, which is does not convey enough information for the signers to
+decide if they want to authorize the transaction or not. Therefore, in practice,
+more data is needed to be sent from the Coordinator to the signers, possibly the
+transaction itself, openings of value commitments, decryption of note
+ciphertexts, etc.; and the signers must check that the given SIGHASH matches the
+data sent from the Coordinator, or compute the SIGHASH themselves from that
+data. However, the specific mechanism for that process is outside the scope of
+this ZIP.
+
The `sign` function is changed to receive `randomizer_point` and incorporate it
into the computation of the binding factor. It is specified as the following: ::
@@ -284,14 +296,17 @@ The `aggregate` function is changed to incorporate the randomizer as follows: ::
- randomized_group_public_key, the randomized group public key
def aggregate(commitment_list, msg, sig_shares, group_public_key, randomizer):
+ # Compute the randomized group public key
+ randomizer_point = G.ScalarBaseMult(randomizer)
+ randomized_group_public_key = group_public_key + randomizer_point
+
# Compute the binding factors
- binding_factor_list = compute_binding_factors(commitment_list, msg)
+ binding_factor_list = compute_binding_factors(commitment_list, msg, randomizer_point)
# Compute the group commitment
group_commitment = compute_group_commitment(commitment_list, binding_factor_list)
# Compute the challenge
- randomized_group_public_key = group_public_key + G * randomizer
challenge = compute_challenge(group_commitment, randomized_group_public_key, msg)
# Compute aggregated signature
@@ -367,16 +382,18 @@ This ciphersuite uses Jubjub for the Group and BLAKE2b-512 for the Hash function
meant to produce signatures indistinguishable from RedJubjub Sapling Spend
Authorization Signatures as specified in [#protocol-concretespendauthsig]_.
-- Group: Jubjub [#protocol-jubjub]_
+- Group: Jubjub [#protocol-jubjub]_ with base point :math:``\mathcal{G}^{\mathsf{Sapling}}`
+ as defined in [#protocol-concretespendauthsig]_.
- - Order: 6554484396890773809930967563523245729705921265872317281365359162392183254199 (see [#protocol-jubjub]_)
- - Identity: as defined in [#protocol-jubjub]_
+ - Order: :math:`r_\mathbb{J}` as defined in [#protocol-jubjub]_.
+ - Identity: as defined in [#protocol-jubjub]_.
- RandomScalar(): Implemented by returning a uniformly random Scalar in the range
\[0, `G.Order()` - 1\]. Refer to {{frost-randomscalar}} for implementation guidance.
- SerializeElement(P): Implemented as :math:`\mathsf{repr}_\mathbb{J}(P)` as defined in [#protocol-jubjub]_
- DeserializeElement(P): Implemented as :math:`\mathsf{abst}_\mathbb{J}(P)` as defined in [#protocol-jubjub]_,
- failing if :math:`\bot` is returned. Additionally, this function validates that the resulting
- element is not the group identity element, returning an error if the check fails.
+ returning an error if :math:`\bot` is returned. Additionally, this function
+ validates that the resulting element is not the group identity element,
+ returning an error if the check fails.
- SerializeScalar: Implemented by outputting the little-endian 32-byte encoding
of the Scalar value.
- DeserializeScalar: Implemented by attempting to deserialize a Scalar from a
@@ -391,14 +408,14 @@ Authorization Signatures as specified in [#protocol-concretespendauthsig]_.
modulo `G.Order()`.
- H2(m): Implemented by computing BLAKE2b-512("Zcash_RedJubjubH", m), interpreting
the 64 bytes as a little-endian integer, and reducing the resulting integer
- modulo L = 6554484396890773809930967563523245729705921265872317281365359162392183254199.
- (This is equivalent to :math:`\mathsf{H}^\circledast(m)`, as defined in
- [#protocol-concretereddsa]_ parametrized with [#protocol-jubjub]_.)
+ modulo `G.Order()`.
+ (This is equivalent to :math:`\mathsf{H}^\circledast(m)`, as defined by
+ the :math:`\mathsf{RedJubjub}` scheme instantiated in [#protocol-concretereddsa]_.)
- H3(m): Implemented by computing BLAKE2b-512("FROST_RedJubjubN", m), interpreting
the 64 bytes as a little-endian integer, and reducing the resulting integer
- modulo L = 6554484396890773809930967563523245729705921265872317281365359162392183254199.
- - H4(m): Implemented by computing BLAKE2b-512("FROST_RedJubjubM", m)
- - H5(m): Implemented by computing BLAKE2b-512("FROST_RedJubjubC", m)
+ modulo `G.Order()`.
+ - H4(m): Implemented by computing BLAKE2b-512("FROST_RedJubjubM", m).
+ - H5(m): Implemented by computing BLAKE2b-512("FROST_RedJubjubC", m).
FROST(Pallas, BLAKE2b-512)
@@ -408,13 +425,14 @@ This ciphersuite uses Pallas for the Group and BLAKE2b-512 for the Hash function
meant to produce signatures indistinguishable from RedPallas Orchard Spend
Authorization Signatures as specified in [#protocol-concretespendauthsig]_.
-- Group: Pallas [#protocol-pallasandvesta]_
+- Group: Pallas [#protocol-pallasandvesta]_ with base point :math:``\mathcal{G}^{\mathsf{Orchard}}`
+ as defined in [#protocol-concretespendauthsig]_.
- - Order: 0x40000000000000000000000000000000224698fc0994a8dd8c46eb2100000001 (see [#protocol-pallasandvesta]_)
- - Identity: as defined in [#protocol-pallasandvesta]_
+ - Order: :math:`r_\mathbb{P}` as defined in [#protocol-pallasandvesta]_.
+ - Identity: as defined in [#protocol-pallasandvesta]_.
- RandomScalar(): Implemented by returning a uniformly random Scalar in the range
\[0, `G.Order()` - 1\]. Refer to {{frost-randomscalar}} for implementation guidance.
- - SerializeElement(P): Implemented as :math:`\mathsf{repr}_\mathbb{P}(P)` as defined in [#protocol-pallasandvesta]_
+ - SerializeElement(P): Implemented as :math:`\mathsf{repr}_\mathbb{P}(P)` as defined in [#protocol-pallasandvesta]_.
- DeserializeElement(P): Implemented as :math:`\mathsf{abst}_\mathbb{P}(P)` as defined in [#protocol-pallasandvesta]_,
failing if :math:`\bot` is returned. Additionally, this function validates that the resulting
element is not the group identity element, returning an error if the check fails.
@@ -429,15 +447,15 @@ Authorization Signatures as specified in [#protocol-concretespendauthsig]_.
- H1(m): Implemented by computing BLAKE2b-512("FROST_RedPallasR", m), interpreting
the 64 bytes as a little-endian integer, and reducing the resulting integer
- modulo L = 0x40000000000000000000000000000000224698fc0994a8dd8c46eb2100000001.
+ modulo `G.Order()`.
- H2(m): Implemented by computing BLAKE2b-512("Zcash_RedPallasH", m), interpreting
the 64 bytes as a little-endian integer, and reducing the resulting integer
- modulo L = 0x40000000000000000000000000000000224698fc0994a8dd8c46eb2100000001.
- (This is equivalent to :math:`\mathsf{H}^\circledast(m)`, as defined in
- [#protocol-concretereddsa]_ parametrized with [#protocol-pallasandvesta]_.)
+ modulo `G.Order()`.
+ (This is equivalent to :math:`\mathsf{H}^\circledast(m)`, as defined by
+ the :math:`\mathsf{RedPallas}` scheme instantiated in [#protocol-concretereddsa]_.)
- H3(m): Implemented by computing BLAKE2b-512("FROST_RedPallasN", m), interpreting
the 64 bytes as a little-endian integer, and reducing the resulting integer
- modulo L = 0x40000000000000000000000000000000224698fc0994a8dd8c46eb2100000001.
+ modulo `G.Order()`.
- H4(m): Implemented by computing BLAKE2b-512("FROST_RedPallasM", m).
- H5(m): Implemented by computing BLAKE2b-512("FROST_RedPallasC", m).
From bc16e533af9c0f6ff01aa7ddeac2e41f0f99f36a Mon Sep 17 00:00:00 2001
From: Conrado Gouvea
Date: Fri, 13 Jan 2023 17:54:29 -0300
Subject: [PATCH 24/37] add lines about signature verification in each
ciphersuite
---
zip-0312.rst | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/zip-0312.rst b/zip-0312.rst
index 3d6a2c76..934763b8 100644
--- a/zip-0312.rst
+++ b/zip-0312.rst
@@ -417,6 +417,9 @@ Authorization Signatures as specified in [#protocol-concretespendauthsig]_.
- H4(m): Implemented by computing BLAKE2b-512("FROST_RedJubjubM", m).
- H5(m): Implemented by computing BLAKE2b-512("FROST_RedJubjubC", m).
+Signature verification is as specified in [#protocol-concretespendauthsig]_
+for RedJubjub.
+
FROST(Pallas, BLAKE2b-512)
''''''''''''''''''''''''''
@@ -459,6 +462,8 @@ Authorization Signatures as specified in [#protocol-concretespendauthsig]_.
- H4(m): Implemented by computing BLAKE2b-512("FROST_RedPallasM", m).
- H5(m): Implemented by computing BLAKE2b-512("FROST_RedPallasC", m).
+Signature verification is as specified in [#protocol-concretespendauthsig]_
+for RedPallas.
Reference implementation
========================
From 8fa30b0f4455d1383ab2322a66760b7c3849b3c8 Mon Sep 17 00:00:00 2001
From: Conrado Gouvea
Date: Mon, 16 Jan 2023 19:03:20 -0300
Subject: [PATCH 25/37] Apply suggestions from code review
Co-authored-by: Daira Hopwood
---
zip-0312.rst | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/zip-0312.rst b/zip-0312.rst
index 934763b8..66663400 100644
--- a/zip-0312.rst
+++ b/zip-0312.rst
@@ -217,9 +217,9 @@ along with the message and the set of signing commitments. (Note that this diffe
from regular FROST which just requires an authenticated channel.)
In Zcash, the message that needs to be signed is actually the SIGHASH
-transaction hash, which is does not convey enough information for the signers to
+transaction hash, which does not convey enough information for the signers to
decide if they want to authorize the transaction or not. Therefore, in practice,
-more data is needed to be sent from the Coordinator to the signers, possibly the
+more data is needed to be sent (over the same encrypted, authenticated channel) from the Coordinator to the signers, possibly the
transaction itself, openings of value commitments, decryption of note
ciphertexts, etc.; and the signers must check that the given SIGHASH matches the
data sent from the Coordinator, or compute the SIGHASH themselves from that
From 8836e22610c4575b7c46f8a31e3819de1ac7efbf Mon Sep 17 00:00:00 2001
From: Conrado Gouvea
Date: Mon, 16 Jan 2023 19:20:31 -0300
Subject: [PATCH 26/37] add initial Rationale section
---
zip-0312.rst | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 63 insertions(+)
diff --git a/zip-0312.rst b/zip-0312.rst
index 66663400..61bc25a9 100644
--- a/zip-0312.rst
+++ b/zip-0312.rst
@@ -372,6 +372,8 @@ as follows: ::
+
+
Ciphersuites
------------
@@ -465,6 +467,66 @@ Authorization Signatures as specified in [#protocol-concretespendauthsig]_.
Signature verification is as specified in [#protocol-concretespendauthsig]_
for RedPallas.
+Rationale
+=========
+
+FROST is a threshold Schnorr signature scheme, and Zcash Spend Authorization are
+also Schnorr signatures, which allows the usage of FROST with Zcash. However,
+since there is no widespread standard for Schnorr signatures, it must be ensured
+that the signatures generated by the FROST variant specified in this ZIP can be
+verified successfully by a Zcash implementation following its specification. In
+practice this entails making sure that the generated signature can be verified
+by the :math:`\mathsf{RedDSA.Validate}` function specified in
+[#protocol-concretereddsa]_:
+
+- The FROST signature, when split into R and S in the first step of
+ :math:`\mathsf{RedDSA.Validate}`, must yield the values expected by the
+ function. This is ensured by defining SerializeElement and SerializeScalar in
+ each ciphersuite to yield those values.
+- The challenge c used during FROST signing must be equal to the challenge c
+ computed during :math:`\mathsf{RedDSA.Validate}`. This requires defining the
+ ciphersuite H2 function as the :math:`\mathsf{H}^\circledast(m)` Zcash
+ function in the ciphersuites, and making sure its input will be the same.
+ Fortunately FROST and Zcash use the same input order (R, public key, message)
+ so we just need to make sure that SerializeElement (used to compute the
+ encoded public key before passing to the hash function) matches what
+ :math:`\mathsf{RedDSA.Validate}` expects; which is possible since both
+ :underline:`R` and :underline:`vk` (the public key) are encoded in the same
+ way in Zcash.
+- Note that `r` (and thus `R`) will not be generated as specified in RedDSA.Sign.
+ This is not an issue however, since with Schnorr signatures it does not matter
+ for the verifier how the `r` value was chosen, it just needs to be generated
+ uniformly at random, which is true for FROST.
+- The above will ensure that the verification equation in
+ :math:`\mathsf{RedDSA.Validate}` will pass, since FROST ensures the exact same
+ equation will be valid as described in [#frost-primeorderverify]_.
+
+The second step is adding the re-randomization functionality so that each FROST
+signing generates a re-randomized signature:
+
+- Anywhere the public key is used, the randomized public key must be used instead.
+ This is exactly what is done in the functions defined above.
+- The re-randomization must be done in each signature share generation, such
+ that the aggregated signature must be valid under verification with the
+ randomized public key. The `R` value from the signature is not influenced by
+ the randomizer so we just need to focus on the `z` value (using FROST
+ notation). Recall that `z` must equal to `r + (c * sk)`. FROST generates
+ signature shares so that when they are all add up to this value. Under
+ re-randomization it must be equal to `r + (c * (sk + randomizer))` (see
+ :math:`\mathsf{RedDSA.RandomizedPrivate}`, which refers to the randomizer as
+ :math:`\alpha`). This can be rewritten as `r + (c * sk) + (c * randomizer)`.
+ In other words, we can simply generate the signature shares using the original
+ FROST procedure, and then add `(c * randomizer)` to `z` in the aggregate step.
+- The re-randomization procedure must be exactly the same as in
+ [#protocol-concretereddsa]_ to ensure that re-randomized keys are uniformly
+ distributed and signatures are unlinkable. This is also true; observe that
+ `randomizer_generate` is exactly the same as
+ :math:`\mathsf{RedDSA.GenRandom}`; and signature generation is compatible with
+ :math:`\mathsf{RedDSA.RandomizedPrivate}`,
+ :math:`\mathsf{RedDSA.RandomizedPublic}`, :math:`\mathsf{RedDSA.Sign}` and
+ :math:`\mathsf{RedDSA.Validate}` as explained in the previous item.
+
+
Reference implementation
========================
@@ -480,6 +542,7 @@ References
.. [#frost-protocol] `Draft RFC: Two-Round Threshold Schnorr Signatures with FROST. Section 5: Two-Round FROST Signing Protocol `_
.. [#frost-removingcoordinator] `Draft RFC: Two-Round Threshold Schnorr Signatures with FROST. Section 7.3: Removing the Coordinator Role `_
.. [#frost-primeordergroup] `Draft RFC: Two-Round Threshold Schnorr Signatures with FROST. Section 3.1: Prime-Order Group `_
+.. [#frost-primeorderverify] `Draft RFC: Two-Round Threshold Schnorr Signatures with FROST. Appendix B: Schnorr Signature Generation and Verification for Prime-Order Groups `_
.. [#frost-tdkg] `Draft RFC: Two-Round Threshold Schnorr Signatures with FROST. Appendix B: Trusted Dealer Key Generation `_
.. [#frost-randomscalar] `Draft RFC: Two-Round Threshold Schnorr Signatures with FROST. Appendix C: Random Scalar Generation `_
.. [#protocol-concretereddsa] `Zcash Protocol Specification, Version 2022.3.4 [NU5]. Section 5.4.7: RedDSA, RedJubjub, and RedPallas `_
From 9512ead14c755009077e28ad6ba2ba5250452a96 Mon Sep 17 00:00:00 2001
From: Deirdre Connolly
Date: Tue, 7 Feb 2023 12:30:05 -0500
Subject: [PATCH 27/37] Update zip-0312.rst
---
zip-0312.rst | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/zip-0312.rst b/zip-0312.rst
index 61bc25a9..bc30129b 100644
--- a/zip-0312.rst
+++ b/zip-0312.rst
@@ -219,12 +219,12 @@ from regular FROST which just requires an authenticated channel.)
In Zcash, the message that needs to be signed is actually the SIGHASH
transaction hash, which does not convey enough information for the signers to
decide if they want to authorize the transaction or not. Therefore, in practice,
-more data is needed to be sent (over the same encrypted, authenticated channel) from the Coordinator to the signers, possibly the
-transaction itself, openings of value commitments, decryption of note
-ciphertexts, etc.; and the signers must check that the given SIGHASH matches the
-data sent from the Coordinator, or compute the SIGHASH themselves from that
-data. However, the specific mechanism for that process is outside the scope of
-this ZIP.
+more data is needed to be sent (over the same encrypted, authenticated channel)
+from the Coordinator to the signers, possibly the transaction itself, openings of
+value commitments, decryption of note ciphertexts, etc.; and the signers must check
+that the given SIGHASH matches the data sent from the Coordinator, or compute the
+SIGHASH themselves from that data. However, the specific mechanism for that process
+is outside the scope of this ZIP.
The `sign` function is changed to receive `randomizer_point` and incorporate it
into the computation of the binding factor. It is specified as the following: ::
From 91573df230ba9b0476b3836e3ce929885d6076a9 Mon Sep 17 00:00:00 2001
From: Deirdre Connolly
Date: Tue, 7 Feb 2023 12:31:45 -0500
Subject: [PATCH 28/37] Update zip-0312.rst
---
zip-0312.rst | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/zip-0312.rst b/zip-0312.rst
index bc30129b..a59bd6fc 100644
--- a/zip-0312.rst
+++ b/zip-0312.rst
@@ -23,9 +23,11 @@ be interpreted as described in RFC 2119. [#RFC2119]_
The terms below are to be interpreted as follows:
Unlinkability
- The property of statistical independence of signatures from the signers' long-term keys, ensuring that
- (for perfectly uniform generation of Randomizers and no leakage of metadata) it is impossible to determine whether two transactions were generated by the same
- party.
+ The property of statistical independence of signatures from the
+ signers' long-term keys, ensuring that (for perfectly uniform
+ generation of Randomizers and no leakage of metadata) it is
+ impossible to determine whether two transactions were generated
+ by the same party.
Abstract
From 1c837a24508b077c4b5a8db51950099122574772 Mon Sep 17 00:00:00 2001
From: Deirdre Connolly
Date: Tue, 7 Feb 2023 12:34:17 -0500
Subject: [PATCH 29/37] Update zip-0312.rst
---
zip-0312.rst | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/zip-0312.rst b/zip-0312.rst
index a59bd6fc..af8f48b4 100644
--- a/zip-0312.rst
+++ b/zip-0312.rst
@@ -106,8 +106,10 @@ With those considerations in mind, the threat model considered in this ZIP is:
Non-requirements
================
-- This ZIP does not support removing the Coordinator role, as described in #[frost-removingcoordinator]_.
-- This ZIP does not prevent key share holders from linking the signing operation to a transaction in the blockchain.
+- This ZIP does not support removing the Coordinator role, as described in
+ #[frost-removingcoordinator]_.
+- This ZIP does not prevent key share holders from linking the signing operation to a
+ transaction in the blockchain.
- Like the FROST specification [#FROST], this ZIP does not specify a key generation
procedure; but refer to that specification for guidelines.
- Network privacy is not in scope for this ZIP, and must be obtained with other
From 7bffa045d62b6e4f44693c39f40cd7ad6ea1a7ad Mon Sep 17 00:00:00 2001
From: Conrado Gouvea
Date: Thu, 9 Feb 2023 19:57:00 -0300
Subject: [PATCH 30/37] rename randomizer_point to randomizer_commitment
---
zip-0312.rst | 40 ++++++++++++++++++++--------------------
1 file changed, 20 insertions(+), 20 deletions(-)
diff --git a/zip-0312.rst b/zip-0312.rst
index af8f48b4..76cf1527 100644
--- a/zip-0312.rst
+++ b/zip-0312.rst
@@ -171,7 +171,7 @@ A new helper function is defined, which computes :math:`\mathsf{RedDSA.GenRandom
Binding Factor Computation
''''''''''''''''''''''''''
-The `compute_binding_factors` function is changed to receive the `randomizer_point`
+The `compute_binding_factors` function is changed to receive the `randomizer_commitment`
as follows: ::
Inputs:
@@ -181,15 +181,15 @@ as follows: ::
(hiding_nonce_commitment_i, binding_nonce_commitment_i). This list MUST be sorted
in ascending order by identifier.
- msg, the message to be signed.
- - randomizer_point, an element in G.
+ - randomizer_commitment, an element in G.
Outputs:
- binding_factor_list, a list of (NonZeroScalar, Scalar) tuples representing the binding factors.
- def compute_binding_factors(commitment_list, msg, randomizer_point):
+ def compute_binding_factors(commitment_list, msg, randomizer_commitment):
msg_hash = H4(msg)
encoded_commitment_hash = H5(encode_group_commitment_list(commitment_list))
- rho_input_prefix = msg_hash || encoded_commitment_hash || G.SerializeElement(randomizer_point)
+ rho_input_prefix = msg_hash || encoded_commitment_hash || G.SerializeElement(randomizer_commitment)
binding_factor_list = []
for (identifier, hiding_nonce_commitment, binding_nonce_commitment) in commitment_list:
@@ -215,7 +215,7 @@ Round Two - Signature Share Generation
''''''''''''''''''''''''''''''''''''''
In Round Two, the Coordinator generates a random scalar `randomizer` by calling
-`randomizer_generate`. Then it computes `randomizer_point = G.ScalarBaseMult(randomizer)`
+`randomizer_generate`. Then it computes `randomizer_commitment = G.ScalarBaseMult(randomizer)`
and sends it to each signer, over a confidential and authenticated channel,
along with the message and the set of signing commitments. (Note that this differs
from regular FROST which just requires an authenticated channel.)
@@ -223,14 +223,14 @@ from regular FROST which just requires an authenticated channel.)
In Zcash, the message that needs to be signed is actually the SIGHASH
transaction hash, which does not convey enough information for the signers to
decide if they want to authorize the transaction or not. Therefore, in practice,
-more data is needed to be sent (over the same encrypted, authenticated channel)
-from the Coordinator to the signers, possibly the transaction itself, openings of
-value commitments, decryption of note ciphertexts, etc.; and the signers must check
+more data is needed to be sent (over the same encrypted, authenticated channel)
+from the Coordinator to the signers, possibly the transaction itself, openings of
+value commitments, decryption of note ciphertexts, etc.; and the signers must check
that the given SIGHASH matches the data sent from the Coordinator, or compute the
-SIGHASH themselves from that data. However, the specific mechanism for that process
+SIGHASH themselves from that data. However, the specific mechanism for that process
is outside the scope of this ZIP.
-The `sign` function is changed to receive `randomizer_point` and incorporate it
+The `sign` function is changed to receive `randomizer_commitment` and incorporate it
into the computation of the binding factor. It is specified as the following: ::
Inputs:
@@ -247,17 +247,17 @@ into the computation of the binding factor. It is specified as the following: ::
Each element in the list indicates a NonZeroScalar identifier j and two commitment
Element values (hiding_nonce_commitment_j, binding_nonce_commitment_j).
This list MUST be sorted in ascending order by identifier.
- - randomizer_point, an element in G (sent by the Coordinator).
+ - randomizer_commitment, an element in G (sent by the Coordinator).
Outputs:
- sig_share, a signature share, a Scalar.
def sign(identifier, sk_i, group_public_key, nonce_i, msg, commitment_list):
# Compute the randomized group public key
- randomized_group_public_key = group_public_key + randomizer_point
+ randomized_group_public_key = group_public_key + randomizer_commitment
# Compute the binding factor(s)
- binding_factor_list = compute_binding_factors(commitment_list, msg, randomizer_point)
+ binding_factor_list = compute_binding_factors(commitment_list, msg, randomizer_commitment)
binding_factor = binding_factor_for_participant(binding_factor_list, identifier)
# Compute the group commitment
@@ -301,11 +301,11 @@ The `aggregate` function is changed to incorporate the randomizer as follows: ::
def aggregate(commitment_list, msg, sig_shares, group_public_key, randomizer):
# Compute the randomized group public key
- randomizer_point = G.ScalarBaseMult(randomizer)
- randomized_group_public_key = group_public_key + randomizer_point
+ randomizer_commitment = G.ScalarBaseMult(randomizer)
+ randomized_group_public_key = group_public_key + randomizer_commitment
# Compute the binding factors
- binding_factor_list = compute_binding_factors(commitment_list, msg, randomizer_point)
+ binding_factor_list = compute_binding_factors(commitment_list, msg, randomizer_commitment)
# Compute the group commitment
group_commitment = compute_group_commitment(commitment_list, binding_factor_list)
@@ -340,18 +340,18 @@ as follows: ::
- group_public_key, public key corresponding to the group signing key,
an Element.
- msg, the message to be signed, a byte string.
- - randomizer_point, an element in G.
+ - randomizer_commitment, an element in G.
Outputs:
- True if the signature share is valid, and False otherwise.
def verify_signature_share(identifier, PK_i, comm_i, sig_share_i, commitment_list,
- group_public_key, msg, randomizer_point):
+ group_public_key, msg, randomizer_commitment):
# Compute the randomized group public key
- randomized_group_public_key = group_public_key + randomizer_point
+ randomized_group_public_key = group_public_key + randomizer_commitment
# Compute the binding factors
- binding_factor_list = compute_binding_factors(commitment_list, msg, randomizer_point)
+ binding_factor_list = compute_binding_factors(commitment_list, msg, randomizer_commitment)
binding_factor = binding_factor_for_participant(binding_factor_list, identifier)
# Compute the group commitment
From c0c16432a4c299ac85db491f7c40cecae5b80050 Mon Sep 17 00:00:00 2001
From: Conrado Gouvea
Date: Mon, 21 Aug 2023 18:22:26 -0300
Subject: [PATCH 31/37] update with simplified design
---
zip-0312.html | 383 +++++++++++++++++++++++++++++++++++++++++++++++++-
zip-0312.rst | 287 ++++++++++---------------------------
2 files changed, 450 insertions(+), 220 deletions(-)
diff --git a/zip-0312.html b/zip-0312.html
index c5d670b1..85f1793e 100644
--- a/zip-0312.html
+++ b/zip-0312.html
@@ -1,16 +1,389 @@
- ZIP 312: Shielded Multisignatures using FROST
+ ZIP 312: FROST for Spend Authorization Signatures
+
{Edit this to reflect the key words that are actually used.} The key words "MUST", "MUST NOT", "SHOULD", and "MAY" in this document are to be interpreted as described in RFC 2119. 2
+
The terms below are to be interpreted as follows:
+
+
Unlinkability
+
The property of statistical independence of signatures from the signers' long-term keys, ensuring that (for perfectly uniform generation of Randomizers and no leakage of metadata) it is impossible to determine whether two transactions were generated by the same party.
+
+
+
Abstract
+
This proposal adapts FROST 3, a threshold signature scheme, to make it unlinkable, which is a requirement for its use in the Zcash protocol. The adapted scheme generates signatures compatible with spend authorization signatures in the Zcash protocol, for the Sapling and Orchard network upgrades.
+
+
Motivation
+
In the Zcash protocol, Spend Authorization Signatures are employed to authorize a transaction. The ability to generate these signatures with the user's private key is what effectively allows the user to spend funds.
+
This is a security-critical step, since anyone who obtains access to the private key will be able to spend the user's funds. For this reason, one interesting possibility is to require multiple parties to allow the transaction to go through. This can be accomplished with threshold signatures, where the private key is split between parties in a way that a threshold (e.g. 2 out of 3) of them must sign the transaction in order to create the final signature. This enables scenarios such as users and exchanges sharing custody of a wallet, for example.
+
FROST is one of such threshold signature protocols. However, it can't be used as-is since the Zcash protocol also requires re-randomizing public and private keys to ensure unlinkability between transactions. This ZIP specifies a variant of FROST with re-randomization support.
+
+
Requirements
+
+
All signatures generated by following this ZIP must be verified successfully as Sapling or Orchard spend authorization signatures using the appropriate validating key.
+
The signatures generated by following this ZIP should meet the security criteria for Signature with Re-Randomizable Keys as specified in the Zcash protocol 10.
+
The threat model described below must be taken into account.
+
+
Threat Model
+
In normal usage, a Zcash user follows multiple steps in order to generate a shielded transaction:
+
+
The transaction is created.
+
The transaction is signed with a re-randomized version of the user's spend authorization private key.
+
The zero-knowledge proof for the transaction is created with the randomizer as an auxiliary (secret) input, among others.
+
+
When employing re-randomizable FROST as specified in this ZIP, the goal is to split the spend authorization private key
+ \(\mathsf{ask}\)
+ among multiple possible signers. This means that the proof generation will still be performed by a single participant, likely the one that created the transaction in the first place. Note that this user already controls the privacy of the transaction since they are responsible for creating the proof.
+
This fits well into the "Coordinator" role from the FROST specification 4. The Coordinator is responsible for sending the message to be signed to all participants, and to aggregate the signature shares.
+
With those considerations in mind, the threat model considered in this ZIP is:
+
+
The Coordinator is trusted with the privacy of the transaction (which includes the unlinkability property). A rogue Coordinator will be able to break unlinkability and privacy, but should not be able to create signed transactions without the approval of MIN_PARTICIPANTS participants, as specified in FROST.
+
All key share holders are also trusted with the privacy of the transaction, thus a rogue key share holder will be able to break its privacy and unlinkability.
+
+
+
+
Non-requirements
+
+
This ZIP does not support removing the Coordinator role, as described in #[frost-removingcoordinator]_.
+
This ZIP does not prevent key share holders from linking the signing operation to a transaction in the blockchain.
+
Like the FROST specification 3, this ZIP does not specify a key generation procedure; but refer to that specification for guidelines.
+
Network privacy is not in scope for this ZIP, and must be obtained with other tools if desired.
+
+
+
Specification
+
Algorithms in this section are specified using Python pseudo-code, in the same fashion as the FROST specification 3.
+
The types Scalar, Element, and G are defined in #[frost-primeordergroup]_, as well as the notation for elliptic-curve arithmetic, which uses the additive notation. Note that this notation differs from that used in the Zcash Protocol Specification. For example, G.ScalarMult(P, k) is used for scalar multiplication, where the protocol spec would use
+ \([k] P\)
+ with the group implied by
+ \(P\)
+ .
+
Re-randomizable FROST
+
To add re-randomization to FROST, follow the specification 3 with the following modifications.
+
Key Generation
+
While key generation is out of scope for this ZIP and the FROST spec 3, it needs to be consistent with FROST, see 8 for guidance. The spend authorization private key
+ \(\mathsf{ask}\)
+ 12 is the particular key that must be used in the context of this ZIP. Note that the
+ \(\mathsf{ask}\)
+ is usually derived from the spending key
+ \(\mathsf{sk}\)
+ , though that is not required. This allows using distributed key generation, since the key it generates is unpredictable. Note however that note deriving
+ \(\mathsf{ask}\)
+ from
+ \(\mathsf{sk}\)
+ prevents using seed phrases to recover the original secret (which may be something desirable in the context of FROST).
+
+
Randomizer Generation
+
A new helper function is defined, which computes
+ \(\mathsf{RedDSA.GenRandom}\)
+ :
Roune One is exactly the same as specified 3. But for context, it involves these steps:
+
+
Each signer generates nonces and their corresponding public commitments. A nonce is a pair of Scalar values, and a commitment is a pair of Element values.
+
The nonces are stored locally by the signer and kept private for use in the second round.
+
The commitments are sent to the Coordinator.
+
+
+
Round Two - Signature Share Generation
+
In Round Two, the Coordinator generates a random scalar randomizer by calling randomizer_generate and sends it to each signer, over a confidential and authenticated channel, along with the message and the set of signing commitments. (Note that this differs from regular FROST which just requires an authenticated channel.)
+
In Zcash, the message that needs to be signed is actually the SIGHASH transaction hash, which does not convey enough information for the signers to decide if they want to authorize the transaction or not. Therefore, in practice, more data is needed to be sent (over the same encrypted, authenticated channel) from the Coordinator to the signers, possibly the transaction itself, openings of value commitments, decryption of note ciphertexts, etc.; and the signers must check that the given SIGHASH matches the data sent from the Coordinator, or compute the SIGHASH themselves from that data. However, the specific mechanism for that process is outside the scope of this ZIP.
+
The sign function remains unchanged, but its inputs must be modified relative to the randomizer as following:
This ciphersuite uses Jubjub for the Group and BLAKE2b-512 for the Hash function H meant to produce signatures indistinguishable from RedJubjub Sapling Spend Authorization Signatures as specified in 11.
+
+
Group: Jubjub 13 with base point
+ \(\mathcal{G}^{\mathsf{Sapling}}\)
+ as defined in 11.
+
RandomScalar(): Implemented by returning a uniformly random Scalar in the range [0, G.Order() - 1]. Refer to {{frost-randomscalar}} for implementation guidance.
+
SerializeElement(P): Implemented as
+ \(\mathsf{repr}_\mathbb{J}(P)\)
+ as defined in 13
+
DeserializeElement(P): Implemented as
+ \(\mathsf{abst}_\mathbb{J}(P)\)
+ as defined in 13, returning an error if
+ \(\bot\)
+ is returned. Additionally, this function validates that the resulting element is not the group identity element, returning an error if the check fails.
+
SerializeScalar: Implemented by outputting the little-endian 32-byte encoding of the Scalar value.
+
DeserializeScalar: Implemented by attempting to deserialize a Scalar from a little-endian 32-byte string. This function can fail if the input does not represent a Scalar in the range [0, G.Order() - 1].
+
+
+
Hash (H): BLAKE2b-512 1 (BLAKE2b with 512-bit output and 16-byte personalization string), and Nh = 64.
+
+
H1(m): Implemented by computing BLAKE2b-512("FROST_RedJubjubR", m), interpreting the 64 bytes as a little-endian integer, and reducing the resulting integer modulo G.Order().
+
H2(m): Implemented by computing BLAKE2b-512("Zcash_RedJubjubH", m), interpreting the 64 bytes as a little-endian integer, and reducing the resulting integer modulo G.Order(). (This is equivalent to
+ \(\mathsf{H}^\circledast(m)\)
+ , as defined by the
+ \(\mathsf{RedJubjub}\)
+ scheme instantiated in 10.)
+
H3(m): Implemented by computing BLAKE2b-512("FROST_RedJubjubN", m), interpreting the 64 bytes as a little-endian integer, and reducing the resulting integer modulo G.Order().
+
H4(m): Implemented by computing BLAKE2b-512("FROST_RedJubjubM", m).
+
H5(m): Implemented by computing BLAKE2b-512("FROST_RedJubjubC", m).
+
+
+
+
Signature verification is as specified in 11 for RedJubjub.
+
+
FROST(Pallas, BLAKE2b-512)
+
This ciphersuite uses Pallas for the Group and BLAKE2b-512 for the Hash function H meant to produce signatures indistinguishable from RedPallas Orchard Spend Authorization Signatures as specified in 11.
+
+
Group: Pallas 14 with base point
+ \(\mathcal{G}^{\mathsf{Orchard}}\)
+ as defined in 11.
+
RandomScalar(): Implemented by returning a uniformly random Scalar in the range [0, G.Order() - 1]. Refer to {{frost-randomscalar}} for implementation guidance.
+
SerializeElement(P): Implemented as
+ \(\mathsf{repr}_\mathbb{P}(P)\)
+ as defined in 14.
+
DeserializeElement(P): Implemented as
+ \(\mathsf{abst}_\mathbb{P}(P)\)
+ as defined in 14, failing if
+ \(\bot\)
+ is returned. Additionally, this function validates that the resulting element is not the group identity element, returning an error if the check fails.
+
SerializeScalar: Implemented by outputting the little-endian 32-byte encoding of the Scalar value.
+
DeserializeScalar: Implemented by attempting to deserialize a Scalar from a little-endian 32-byte string. This function can fail if the input does not represent a Scalar in the range [0, G.Order() - 1].
+
+
+
Hash (H): BLAKE2b-512 1 (BLAKE2b with 512-bit output and 16-byte personalization string), and Nh = 64.
+
+
H1(m): Implemented by computing BLAKE2b-512("FROST_RedPallasR", m), interpreting the 64 bytes as a little-endian integer, and reducing the resulting integer modulo G.Order().
+
H2(m): Implemented by computing BLAKE2b-512("Zcash_RedPallasH", m), interpreting the 64 bytes as a little-endian integer, and reducing the resulting integer modulo G.Order(). (This is equivalent to
+ \(\mathsf{H}^\circledast(m)\)
+ , as defined by the
+ \(\mathsf{RedPallas}\)
+ scheme instantiated in 10.)
+
H3(m): Implemented by computing BLAKE2b-512("FROST_RedPallasN", m), interpreting the 64 bytes as a little-endian integer, and reducing the resulting integer modulo G.Order().
+
H4(m): Implemented by computing BLAKE2b-512("FROST_RedPallasM", m).
+
H5(m): Implemented by computing BLAKE2b-512("FROST_RedPallasC", m).
+
+
+
+
Signature verification is as specified in 11 for RedPallas.
+
+
+
+
Rationale
+
FROST is a threshold Schnorr signature scheme, and Zcash Spend Authorization are also Schnorr signatures, which allows the usage of FROST with Zcash. However, since there is no widespread standard for Schnorr signatures, it must be ensured that the signatures generated by the FROST variant specified in this ZIP can be verified successfully by a Zcash implementation following its specification. In practice this entails making sure that the generated signature can be verified by the
+ \(\mathsf{RedDSA.Validate}\)
+ function specified in 10:
+
+
The FROST signature, when split into R and S in the first step of
+ \(\mathsf{RedDSA.Validate}\)
+ , must yield the values expected by the function. This is ensured by defining SerializeElement and SerializeScalar in each ciphersuite to yield those values.
+
The challenge c used during FROST signing must be equal to the challenge c computed during
+ \(\mathsf{RedDSA.Validate}\)
+ . This requires defining the ciphersuite H2 function as the
+ \(\mathsf{H}^\circledast(m)\)
+ Zcash function in the ciphersuites, and making sure its input will be the same. Fortunately FROST and Zcash use the same input order (R, public key, message) so we just need to make sure that SerializeElement (used to compute the encoded public key before passing to the hash function) matches what
+ \(\mathsf{RedDSA.Validate}\)
+ expects; which is possible since both R and vk (the public key) are encoded in the same way in Zcash.
+
Note that r (and thus R) will not be generated as specified in RedDSA.Sign. This is not an issue however, since with Schnorr signatures it does not matter for the verifier how the r value was chosen, it just needs to be generated uniformly at random, which is true for FROST.
+
The above will ensure that the verification equation in
+ \(\mathsf{RedDSA.Validate}\)
+ will pass, since FROST ensures the exact same equation will be valid as described in 7.
+
+
The second step is adding the re-randomization functionality so that each FROST signing generates a re-randomized signature:
+
+
Anywhere the public key is used, the randomized public key must be used instead. This is exactly what is done in the functions defined above.
+
The re-randomization must be done in each signature share generation, such that the aggregated signature must be valid under verification with the randomized public key. The R value from the signature is not influenced by the randomizer so we just need to focus on the z value (using FROST notation). Recall that z must equal to r + (c * sk), and that each signature share is z_i = (hiding_nonce + (binding_nonce * binding_factor)) +
+(lambda_i * c * sk_i). The first terms are not influenced by the randomizer so we can only look into the second term of each top-level addition, i.e. c
+* sk must be equal to sum(lambda_i * c * sk_i) for each participant i. Under re-randomization these become c * (sk + randomizer) (see
+ \(\mathsf{RedDSA.RandomizedPrivate}\)
+ , which refers to the randomizer as
+ \(\alpha\)
+ ) and sum(lambda_i * c * (sk_i + randomizer)). The latter can be rewritten as c * (sum(lambda_i * sk_i) + randomizer *
+sum(lambda_i). Since sum(lambda_i * sk_i) == sk per the Shamir secret sharing mechanism used by FROST, and since sum(lambda_i) == 115, we arrive at c * (sk + randomizer) as required.
+
The re-randomization procedure must be exactly the same as in 10 to ensure that re-randomized keys are uniformly distributed and signatures are unlinkable. This is also true; observe that randomizer_generate is exactly the same as
+ \(\mathsf{RedDSA.GenRandom}\)
+ ; and signature generation is compatible with
+ \(\mathsf{RedDSA.RandomizedPrivate}\)
+ ,
+ \(\mathsf{RedDSA.RandomizedPublic}\)
+ ,
+ \(\mathsf{RedDSA.Sign}\)
+ and
+ \(\mathsf{RedDSA.Validate}\)
+ as explained in the previous item.
+
\ No newline at end of file
diff --git a/zip-0312.rst b/zip-0312.rst
index 76cf1527..89168bc1 100644
--- a/zip-0312.rst
+++ b/zip-0312.rst
@@ -98,7 +98,7 @@ With those considerations in mind, the threat model considered in this ZIP is:
- The Coordinator is trusted with the privacy of the transaction (which includes
the unlinkability property). A rogue Coordinator will be able to break
unlinkability and privacy, but should not be able to create signed transactions
- without the approval of `MIN_SIGNERS` participants, as specified in FROST.
+ without the approval of ``MIN_PARTICIPANTS`` participants, as specified in FROST.
- All key share holders are also trusted with the privacy of the transaction,
thus a rogue key share holder will be able to break its privacy and unlinkability.
@@ -110,7 +110,7 @@ Non-requirements
#[frost-removingcoordinator]_.
- This ZIP does not prevent key share holders from linking the signing operation to a
transaction in the blockchain.
-- Like the FROST specification [#FROST], this ZIP does not specify a key generation
+- Like the FROST specification [#FROST]_, this ZIP does not specify a key generation
procedure; but refer to that specification for guidelines.
- Network privacy is not in scope for this ZIP, and must be obtained with other
tools if desired.
@@ -125,7 +125,7 @@ fashion as the FROST specification [#FROST]_.
The types Scalar, Element, and G are defined in #[frost-primeordergroup]_, as
well as the notation for elliptic-curve arithmetic, which uses the additive
notation. Note that this notation differs from that used in the Zcash Protocol
-Specification. For example, `G.ScalarMult(P, k)` is used for scalar
+Specification. For example, ``G.ScalarMult(P, k)`` is used for scalar
multiplication, where the protocol spec would use :math:`[k] P` with the group
implied by :math:`P`.
@@ -145,8 +145,10 @@ it needs to be consistent with FROST, see [#frost-tdkg]_ for guidance. The
spend authorization private key :math:`\mathsf{ask}` [#protocol-spendauthsig]_
is the particular key that must be used in the context of this ZIP. Note that
the :math:`\mathsf{ask}` is usually derived from the spending key :math:`\mathsf{sk}`,
-though that is not required. Doing so might require a trusted dealer key generation
-process as detailed in [#frost-tdkg]_ (as opposed to distributed key generation).
+though that is not required. This allows using distributed key generation, since
+the key it generates is unpredictable. Note however that note deriving :math:`\mathsf{ask}`
+from :math:`\mathsf{sk}` prevents using seed phrases to recover the original
+secret (which may be something desirable in the context of FROST).
Randomizer Generation
@@ -168,41 +170,10 @@ A new helper function is defined, which computes :math:`\mathsf{RedDSA.GenRandom
return H3(randomizer_input)
-Binding Factor Computation
-''''''''''''''''''''''''''
-
-The `compute_binding_factors` function is changed to receive the `randomizer_commitment`
-as follows: ::
-
- Inputs:
- - commitment_list = [(i, hiding_nonce_commitment_i, binding_nonce_commitment_i), ...],
- a list of commitments issued by each participant, where each element in the list
- indicates a NonZeroScalar identifier i and two commitment Element values
- (hiding_nonce_commitment_i, binding_nonce_commitment_i). This list MUST be sorted
- in ascending order by identifier.
- - msg, the message to be signed.
- - randomizer_commitment, an element in G.
-
- Outputs:
- - binding_factor_list, a list of (NonZeroScalar, Scalar) tuples representing the binding factors.
-
- def compute_binding_factors(commitment_list, msg, randomizer_commitment):
- msg_hash = H4(msg)
- encoded_commitment_hash = H5(encode_group_commitment_list(commitment_list))
- rho_input_prefix = msg_hash || encoded_commitment_hash || G.SerializeElement(randomizer_commitment)
-
- binding_factor_list = []
- for (identifier, hiding_nonce_commitment, binding_nonce_commitment) in commitment_list:
- rho_input = rho_input_prefix || G.SerializeScalar(identifier)
- binding_factor = H1(rho_input)
- binding_factor_list.append((identifier, binding_factor))
- return binding_factor_list
-
-
Round One - Commitment
''''''''''''''''''''''
-Roune One is exactly the same as specified #[FROST]_. But for context, it
+Roune One is exactly the same as specified [#FROST]_. But for context, it
involves these steps:
- Each signer generates nonces and their corresponding public commitments.
@@ -214,11 +185,11 @@ involves these steps:
Round Two - Signature Share Generation
''''''''''''''''''''''''''''''''''''''
-In Round Two, the Coordinator generates a random scalar `randomizer` by calling
-`randomizer_generate`. Then it computes `randomizer_commitment = G.ScalarBaseMult(randomizer)`
-and sends it to each signer, over a confidential and authenticated channel,
-along with the message and the set of signing commitments. (Note that this differs
-from regular FROST which just requires an authenticated channel.)
+In Round Two, the Coordinator generates a random scalar ``randomizer`` by calling
+``randomizer_generate`` and sends it to each signer, over a confidential and
+authenticated channel, along with the message and the set of signing
+commitments. (Note that this differs from regular FROST which just requires an
+authenticated channel.)
In Zcash, the message that needs to be signed is actually the SIGHASH
transaction hash, which does not convey enough information for the signers to
@@ -230,149 +201,26 @@ that the given SIGHASH matches the data sent from the Coordinator, or compute th
SIGHASH themselves from that data. However, the specific mechanism for that process
is outside the scope of this ZIP.
-The `sign` function is changed to receive `randomizer_commitment` and incorporate it
-into the computation of the binding factor. It is specified as the following: ::
+The ``sign`` function remains unchanged, but its inputs must be modified relative
+to the ``randomizer`` as following:
- Inputs:
- - identifier, identifier i of the participant, a NonZeroScalar.
- - sk_i, Signer secret key share, a Scalar.
- - group_public_key, public key corresponding to the group signing key,
- an Element.
- - nonce_i, pair of Scalar values (hiding_nonce, binding_nonce) generated in
- round one.
- - msg, the message to be signed, a byte string.
- - commitment_list =
- [(j, hiding_nonce_commitment_j, binding_nonce_commitment_j), ...], a
- list of commitments issued in Round 1 by each participant and sent by the Coordinator.
- Each element in the list indicates a NonZeroScalar identifier j and two commitment
- Element values (hiding_nonce_commitment_j, binding_nonce_commitment_j).
- This list MUST be sorted in ascending order by identifier.
- - randomizer_commitment, an element in G (sent by the Coordinator).
-
- Outputs:
- - sig_share, a signature share, a Scalar.
-
- def sign(identifier, sk_i, group_public_key, nonce_i, msg, commitment_list):
- # Compute the randomized group public key
- randomized_group_public_key = group_public_key + randomizer_commitment
-
- # Compute the binding factor(s)
- binding_factor_list = compute_binding_factors(commitment_list, msg, randomizer_commitment)
- binding_factor = binding_factor_for_participant(binding_factor_list, identifier)
-
- # Compute the group commitment
- group_commitment = compute_group_commitment(commitment_list, binding_factor_list)
-
- # Compute the interpolating value
- participant_list = participants_from_commitment_list(commitment_list)
- lambda_i = derive_interpolating_value(identifier, participant_list)
-
- # Compute the per-message challenge
- challenge = compute_challenge(group_commitment, randomized_group_public_key, msg)
-
- # Compute the signature share
- (hiding_nonce, binding_nonce) = nonce_i
- sig_share = hiding_nonce + (binding_nonce * binding_factor) + (lambda_i * sk_i * challenge)
-
- return sig_share
+- ``sk_i = sk_i + randomizer``
+- ``group_public_key = group_public_key + G.ScalarBaseMult(randomizer)``
Signature Share Verification and Aggregation
''''''''''''''''''''''''''''''''''''''''''''
-The `aggregate` function is changed to incorporate the randomizer as follows: ::
+The ``aggregate`` function remains unchanged, but its inputs must be modified
+relative to the ``randomizer`` as following:
- Inputs:
- - commitment_list =
- [(j, hiding_nonce_commitment_j, binding_nonce_commitment_j), ...], a
- list of commitments issued in Round 1 by each participant, where each element
- in the list indicates a NonZeroScalar identifier j and two commitment
- Element values (hiding_nonce_commitment_j, binding_nonce_commitment_j).
- This list MUST be sorted in ascending order by identifier.
- - msg, the message to be signed, a byte string.
- - sig_shares, a set of signature shares z_i, Scalar values, for each participant,
- of length NUM_PARTICIPANTS, where MIN_PARTICIPANTS <= NUM_PARTICIPANTS <= MAX_PARTICIPANTS.
- - group_public_key, public key corresponding to the group signing key,
- - randomizer, the randomizer Scalar.
+- ``group_public_key = group_public_key + G.ScalarBaseMult(randomizer)``
- Outputs:
- - (R, z), a Schnorr signature consisting of an Element R and Scalar z.
- - randomized_group_public_key, the randomized group public key
+The ``verify_signature_share`` function remains unchanged, but its inputs must be modified
+relative to the ``randomizer`` as following:
- def aggregate(commitment_list, msg, sig_shares, group_public_key, randomizer):
- # Compute the randomized group public key
- randomizer_commitment = G.ScalarBaseMult(randomizer)
- randomized_group_public_key = group_public_key + randomizer_commitment
-
- # Compute the binding factors
- binding_factor_list = compute_binding_factors(commitment_list, msg, randomizer_commitment)
-
- # Compute the group commitment
- group_commitment = compute_group_commitment(commitment_list, binding_factor_list)
-
- # Compute the challenge
- challenge = compute_challenge(group_commitment, randomized_group_public_key, msg)
-
- # Compute aggregated signature
- z = Scalar(0)
- for z_i in sig_shares:
- z = z + z_i
- return (group_commitment, z + randomizer * challenge), randomized_group_public_key
-
-
-The `verify_signature_share` is changed to incorporate the randomizer point,
-as follows: ::
-
- Inputs:
- - identifier, identifier i of the participant, a NonZeroScalar.
- - PK_i, the public key for the i-th participant, where PK_i = G.ScalarBaseMult(sk_i),
- an Element.
- - comm_i, pair of Element values in G (hiding_nonce_commitment, binding_nonce_commitment)
- generated in round one from the i-th participant.
- - sig_share_i, a Scalar value indicating the signature share as produced in
- round two from the i-th participant.
- - commitment_list =
- [(j, hiding_nonce_commitment_j, binding_nonce_commitment_j), ...], a
- list of commitments issued in Round 1 by each participant, where each element
- in the list indicates a NonZeroScalar identifier j and two commitment
- Element values (hiding_nonce_commitment_j, binding_nonce_commitment_j).
- This list MUST be sorted in ascending order by identifier.
- - group_public_key, public key corresponding to the group signing key,
- an Element.
- - msg, the message to be signed, a byte string.
- - randomizer_commitment, an element in G.
-
- Outputs:
- - True if the signature share is valid, and False otherwise.
-
- def verify_signature_share(identifier, PK_i, comm_i, sig_share_i, commitment_list,
- group_public_key, msg, randomizer_commitment):
- # Compute the randomized group public key
- randomized_group_public_key = group_public_key + randomizer_commitment
-
- # Compute the binding factors
- binding_factor_list = compute_binding_factors(commitment_list, msg, randomizer_commitment)
- binding_factor = binding_factor_for_participant(binding_factor_list, identifier)
-
- # Compute the group commitment
- group_commitment = compute_group_commitment(commitment_list, binding_factor_list)
-
- # Compute the commitment share
- (hiding_nonce_commitment, binding_nonce_commitment) = comm_i
- comm_share = hiding_nonce_commitment + G.ScalarMult(binding_nonce_commitment, binding_factor)
-
- # Compute the challenge
- challenge = compute_challenge(group_commitment, randomized_group_public_key, msg)
-
- # Compute the interpolating value
- participant_list = participants_from_commitment_list(commitment_list)
- lambda_i = derive_interpolating_value(identifier, participant_list)
-
- # Compute relation values
- l = G.ScalarBaseMult(sig_share_i)
- r = comm_share + G.ScalarMult(PK_i, challenge * lambda_i)
-
- return l == r
+- ``PK_i = PK_i + G.ScalarBaseMult(randomizer)``
+- ``group_public_key = group_public_key + G.ScalarBaseMult(randomizer)``
@@ -384,17 +232,17 @@ Ciphersuites
FROST(Jubjub, BLAKE2b-512)
''''''''''''''''''''''''''
-This ciphersuite uses Jubjub for the Group and BLAKE2b-512 for the Hash function `H`
+This ciphersuite uses Jubjub for the Group and BLAKE2b-512 for the Hash function ``H``
meant to produce signatures indistinguishable from RedJubjub Sapling Spend
Authorization Signatures as specified in [#protocol-concretespendauthsig]_.
-- Group: Jubjub [#protocol-jubjub]_ with base point :math:``\mathcal{G}^{\mathsf{Sapling}}`
+- Group: Jubjub [#protocol-jubjub]_ with base point :math:`\mathcal{G}^{\mathsf{Sapling}}`
as defined in [#protocol-concretespendauthsig]_.
- Order: :math:`r_\mathbb{J}` as defined in [#protocol-jubjub]_.
- Identity: as defined in [#protocol-jubjub]_.
- RandomScalar(): Implemented by returning a uniformly random Scalar in the range
- \[0, `G.Order()` - 1\]. Refer to {{frost-randomscalar}} for implementation guidance.
+ \[0, ``G.Order()`` - 1\]. Refer to {{frost-randomscalar}} for implementation guidance.
- SerializeElement(P): Implemented as :math:`\mathsf{repr}_\mathbb{J}(P)` as defined in [#protocol-jubjub]_
- DeserializeElement(P): Implemented as :math:`\mathsf{abst}_\mathbb{J}(P)` as defined in [#protocol-jubjub]_,
returning an error if :math:`\bot` is returned. Additionally, this function
@@ -404,22 +252,22 @@ Authorization Signatures as specified in [#protocol-concretespendauthsig]_.
of the Scalar value.
- DeserializeScalar: Implemented by attempting to deserialize a Scalar from a
little-endian 32-byte string. This function can fail if the input does not
- represent a Scalar in the range \[0, `G.Order()` - 1\].
+ represent a Scalar in the range \[0, ``G.Order()`` - 1\].
-- Hash (`H`): BLAKE2b-512 [#BLAKE]_ (BLAKE2b with 512-bit output and 16-byte personalization string),
+- Hash (``H``): BLAKE2b-512 [#BLAKE]_ (BLAKE2b with 512-bit output and 16-byte personalization string),
and Nh = 64.
- H1(m): Implemented by computing BLAKE2b-512("FROST_RedJubjubR", m), interpreting
the 64 bytes as a little-endian integer, and reducing the resulting integer
- modulo `G.Order()`.
+ modulo ``G.Order()``.
- H2(m): Implemented by computing BLAKE2b-512("Zcash_RedJubjubH", m), interpreting
the 64 bytes as a little-endian integer, and reducing the resulting integer
- modulo `G.Order()`.
+ modulo ``G.Order()``.
(This is equivalent to :math:`\mathsf{H}^\circledast(m)`, as defined by
the :math:`\mathsf{RedJubjub}` scheme instantiated in [#protocol-concretereddsa]_.)
- H3(m): Implemented by computing BLAKE2b-512("FROST_RedJubjubN", m), interpreting
the 64 bytes as a little-endian integer, and reducing the resulting integer
- modulo `G.Order()`.
+ modulo ``G.Order()``.
- H4(m): Implemented by computing BLAKE2b-512("FROST_RedJubjubM", m).
- H5(m): Implemented by computing BLAKE2b-512("FROST_RedJubjubC", m).
@@ -430,17 +278,17 @@ for RedJubjub.
FROST(Pallas, BLAKE2b-512)
''''''''''''''''''''''''''
-This ciphersuite uses Pallas for the Group and BLAKE2b-512 for the Hash function `H`
+This ciphersuite uses Pallas for the Group and BLAKE2b-512 for the Hash function ``H``
meant to produce signatures indistinguishable from RedPallas Orchard Spend
Authorization Signatures as specified in [#protocol-concretespendauthsig]_.
-- Group: Pallas [#protocol-pallasandvesta]_ with base point :math:``\mathcal{G}^{\mathsf{Orchard}}`
+- Group: Pallas [#protocol-pallasandvesta]_ with base point :math:`\mathcal{G}^{\mathsf{Orchard}}`
as defined in [#protocol-concretespendauthsig]_.
- Order: :math:`r_\mathbb{P}` as defined in [#protocol-pallasandvesta]_.
- Identity: as defined in [#protocol-pallasandvesta]_.
- RandomScalar(): Implemented by returning a uniformly random Scalar in the range
- \[0, `G.Order()` - 1\]. Refer to {{frost-randomscalar}} for implementation guidance.
+ \[0, ``G.Order()`` - 1\]. Refer to {{frost-randomscalar}} for implementation guidance.
- SerializeElement(P): Implemented as :math:`\mathsf{repr}_\mathbb{P}(P)` as defined in [#protocol-pallasandvesta]_.
- DeserializeElement(P): Implemented as :math:`\mathsf{abst}_\mathbb{P}(P)` as defined in [#protocol-pallasandvesta]_,
failing if :math:`\bot` is returned. Additionally, this function validates that the resulting
@@ -449,22 +297,22 @@ Authorization Signatures as specified in [#protocol-concretespendauthsig]_.
of the Scalar value.
- DeserializeScalar: Implemented by attempting to deserialize a Scalar from a
little-endian 32-byte string. This function can fail if the input does not
- represent a Scalar in the range \[0, `G.Order()` - 1\].
+ represent a Scalar in the range \[0, ``G.Order()`` - 1\].
-- Hash (`H`): BLAKE2b-512 [#BLAKE]_ (BLAKE2b with 512-bit output and 16-byte personalization string),
+- Hash (``H``): BLAKE2b-512 [#BLAKE]_ (BLAKE2b with 512-bit output and 16-byte personalization string),
and Nh = 64.
- H1(m): Implemented by computing BLAKE2b-512("FROST_RedPallasR", m), interpreting
the 64 bytes as a little-endian integer, and reducing the resulting integer
- modulo `G.Order()`.
+ modulo ``G.Order()``.
- H2(m): Implemented by computing BLAKE2b-512("Zcash_RedPallasH", m), interpreting
the 64 bytes as a little-endian integer, and reducing the resulting integer
- modulo `G.Order()`.
+ modulo ``G.Order()``.
(This is equivalent to :math:`\mathsf{H}^\circledast(m)`, as defined by
the :math:`\mathsf{RedPallas}` scheme instantiated in [#protocol-concretereddsa]_.)
- H3(m): Implemented by computing BLAKE2b-512("FROST_RedPallasN", m), interpreting
the 64 bytes as a little-endian integer, and reducing the resulting integer
- modulo `G.Order()`.
+ modulo ``G.Order()``.
- H4(m): Implemented by computing BLAKE2b-512("FROST_RedPallasM", m).
- H5(m): Implemented by computing BLAKE2b-512("FROST_RedPallasC", m).
@@ -487,6 +335,7 @@ by the :math:`\mathsf{RedDSA.Validate}` function specified in
:math:`\mathsf{RedDSA.Validate}`, must yield the values expected by the
function. This is ensured by defining SerializeElement and SerializeScalar in
each ciphersuite to yield those values.
+
- The challenge c used during FROST signing must be equal to the challenge c
computed during :math:`\mathsf{RedDSA.Validate}`. This requires defining the
ciphersuite H2 function as the :math:`\mathsf{H}^\circledast(m)` Zcash
@@ -494,13 +343,14 @@ by the :math:`\mathsf{RedDSA.Validate}` function specified in
Fortunately FROST and Zcash use the same input order (R, public key, message)
so we just need to make sure that SerializeElement (used to compute the
encoded public key before passing to the hash function) matches what
- :math:`\mathsf{RedDSA.Validate}` expects; which is possible since both
- :underline:`R` and :underline:`vk` (the public key) are encoded in the same
- way in Zcash.
-- Note that `r` (and thus `R`) will not be generated as specified in RedDSA.Sign.
+ :math:`\mathsf{RedDSA.Validate}` expects; which is possible since both `R` and
+ `vk` (the public key) are encoded in the same way in Zcash.
+
+- Note that ``r`` (and thus ``R``) will not be generated as specified in RedDSA.Sign.
This is not an issue however, since with Schnorr signatures it does not matter
- for the verifier how the `r` value was chosen, it just needs to be generated
+ for the verifier how the ``r`` value was chosen, it just needs to be generated
uniformly at random, which is true for FROST.
+
- The above will ensure that the verification equation in
:math:`\mathsf{RedDSA.Validate}` will pass, since FROST ensures the exact same
equation will be valid as described in [#frost-primeorderverify]_.
@@ -512,19 +362,25 @@ signing generates a re-randomized signature:
This is exactly what is done in the functions defined above.
- The re-randomization must be done in each signature share generation, such
that the aggregated signature must be valid under verification with the
- randomized public key. The `R` value from the signature is not influenced by
- the randomizer so we just need to focus on the `z` value (using FROST
- notation). Recall that `z` must equal to `r + (c * sk)`. FROST generates
- signature shares so that when they are all add up to this value. Under
- re-randomization it must be equal to `r + (c * (sk + randomizer))` (see
+ randomized public key. The ``R`` value from the signature is not influenced by
+ the randomizer so we just need to focus on the ``z`` value (using FROST
+ notation). Recall that ``z`` must equal to ``r + (c * sk)``, and that each
+ signature share is ``z_i = (hiding_nonce + (binding_nonce * binding_factor)) +
+ (lambda_i * c * sk_i)``. The first terms are not influenced by the randomizer
+ so we can only look into the second term of each top-level addition, i.e. ``c
+ * sk`` must be equal to ``sum(lambda_i * c * sk_i)`` for each participant
+ ``i``. Under re-randomization these become ``c * (sk + randomizer)`` (see
:math:`\mathsf{RedDSA.RandomizedPrivate}`, which refers to the randomizer as
- :math:`\alpha`). This can be rewritten as `r + (c * sk) + (c * randomizer)`.
- In other words, we can simply generate the signature shares using the original
- FROST procedure, and then add `(c * randomizer)` to `z` in the aggregate step.
+ :math:`\alpha`) and ``sum(lambda_i * c * (sk_i + randomizer))``. The latter
+ can be rewritten as ``c * (sum(lambda_i * sk_i) + randomizer *
+ sum(lambda_i)``. Since ``sum(lambda_i * sk_i) == sk`` per the Shamir secret
+ sharing mechanism used by FROST, and since ``sum(lambda_i) == 1``
+ [#sum-lambda-proof]_, we arrive at ``c * (sk + randomizer)`` as required.
+
- The re-randomization procedure must be exactly the same as in
[#protocol-concretereddsa]_ to ensure that re-randomized keys are uniformly
distributed and signatures are unlinkable. This is also true; observe that
- `randomizer_generate` is exactly the same as
+ ``randomizer_generate`` is exactly the same as
:math:`\mathsf{RedDSA.GenRandom}`; and signature generation is compatible with
:math:`\mathsf{RedDSA.RandomizedPrivate}`,
:math:`\mathsf{RedDSA.RandomizedPublic}`, :math:`\mathsf{RedDSA.Sign}` and
@@ -542,15 +398,16 @@ References
.. [#BLAKE] `BLAKE2: simpler, smaller, fast as MD5 `_
.. [#RFC2119] `RFC 2119: Key words for use in RFCs to Indicate Requirement Levels `_
-.. [#FROST] `Draft RFC: Two-Round Threshold Schnorr Signatures with FROST `_
-.. [#frost-protocol] `Draft RFC: Two-Round Threshold Schnorr Signatures with FROST. Section 5: Two-Round FROST Signing Protocol `_
-.. [#frost-removingcoordinator] `Draft RFC: Two-Round Threshold Schnorr Signatures with FROST. Section 7.3: Removing the Coordinator Role `_
-.. [#frost-primeordergroup] `Draft RFC: Two-Round Threshold Schnorr Signatures with FROST. Section 3.1: Prime-Order Group `_
-.. [#frost-primeorderverify] `Draft RFC: Two-Round Threshold Schnorr Signatures with FROST. Appendix B: Schnorr Signature Generation and Verification for Prime-Order Groups `_
-.. [#frost-tdkg] `Draft RFC: Two-Round Threshold Schnorr Signatures with FROST. Appendix B: Trusted Dealer Key Generation `_
-.. [#frost-randomscalar] `Draft RFC: Two-Round Threshold Schnorr Signatures with FROST. Appendix C: Random Scalar Generation `_
-.. [#protocol-concretereddsa] `Zcash Protocol Specification, Version 2022.3.4 [NU5]. Section 5.4.7: RedDSA, RedJubjub, and RedPallas `_
+.. [#FROST] `Draft RFC: Two-Round Threshold Schnorr Signatures with FROST `_
+.. [#frost-protocol] `Draft RFC: Two-Round Threshold Schnorr Signatures with FROST. Section 5: Two-Round FROST Signing Protocol `_
+.. [#frost-removingcoordinator] `Draft RFC: Two-Round Threshold Schnorr Signatures with FROST. Section 7.3: Removing the Coordinator Role `_
+.. [#frost-primeordergroup] `Draft RFC: Two-Round Threshold Schnorr Signatures with FROST. Section 3.1: Prime-Order Group `_
+.. [#frost-primeorderverify] `Draft RFC: Two-Round Threshold Schnorr Signatures with FROST. Appendix B: Schnorr Signature Generation and Verification for Prime-Order Groups `_
+.. [#frost-tdkg] `Draft RFC: Two-Round Threshold Schnorr Signatures with FROST. Appendix B: Trusted Dealer Key Generation `_
+.. [#frost-randomscalar] `Draft RFC: Two-Round Threshold Schnorr Signatures with FROST. Appendix C: Random Scalar Generation `_
+.. [#protocol-concretereddsa] `Zcash Protocol Specification, Version 2022.3.4 [NU5]. Section 5.4.7: RedDSA, RedJubjub, and RedPallas `_
.. [#protocol-concretespendauthsig] `Zcash Protocol Specification, Version 2022.3.4 [NU5]. Section 5.4.7.1: Spend Authorization Signature (Sapling and Orchard) `_
.. [#protocol-spendauthsig] `Zcash Protocol Specification, Version 2022.3.4 [NU5]. Section 4.15: Spend Authorization Signature (Sapling and Orchard) `_
.. [#protocol-jubjub] `Zcash Protocol Specification, Version 2022.3.4 [NU5]. Section 5.4.9.3: Jubjub `_
-.. [#protocol-pallasandvesta] `Zcash Protocol Specification, Version 2022.3.4 [NU5]. Section 5.4.9.6: Pallas and Vesta `_
+.. [#protocol-pallasandvesta] `Zcash Protocol Specification, Version 2022.3.4 [NU5]. Section 5.4.9.6: Pallas and Vesta `_
+.. [#sum-lambda-proof] `Prove that the sum of the Lagrange (interpolation) coefficients is equal to 1 `_
From b441df0745dd0840c5b02bf5dde3955c7640b434 Mon Sep 17 00:00:00 2001
From: Conrado Gouvea
Date: Mon, 18 Sep 2023 18:47:17 -0300
Subject: [PATCH 32/37] hash signing package in randomizer generator; overall
adjustments
---
zip-0312.html | 134 ++++++++++++++++++++++++++++++--------------------
zip-0312.rst | 116 ++++++++++++++++++++++++++++---------------
2 files changed, 155 insertions(+), 95 deletions(-)
diff --git a/zip-0312.html b/zip-0312.html
index 85f1793e..9d82ac99 100644
--- a/zip-0312.html
+++ b/zip-0312.html
@@ -31,13 +31,13 @@ Pull-Request: <https://githu
In the Zcash protocol, Spend Authorization Signatures are employed to authorize a transaction. The ability to generate these signatures with the user's private key is what effectively allows the user to spend funds.
-
This is a security-critical step, since anyone who obtains access to the private key will be able to spend the user's funds. For this reason, one interesting possibility is to require multiple parties to allow the transaction to go through. This can be accomplished with threshold signatures, where the private key is split between parties in a way that a threshold (e.g. 2 out of 3) of them must sign the transaction in order to create the final signature. This enables scenarios such as users and exchanges sharing custody of a wallet, for example.
+
This is a security-critical step, since anyone who obtains access to the private key will be able to spend the user's funds. For this reason, one interesting possibility is to require multiple parties to allow the transaction to go through. This can be accomplished with threshold signatures, where the private key is split between parties (or generated already split using a distributed protocol) in a way that a threshold (e.g. 2 out of 3) of them must sign the transaction in order to create the final signature. This enables scenarios such as users and third-party services sharing custody of a wallet, or a group of people managing shared funds, for example.
FROST is one of such threshold signature protocols. However, it can't be used as-is since the Zcash protocol also requires re-randomizing public and private keys to ensure unlinkability between transactions. This ZIP specifies a variant of FROST with re-randomization support.
Requirements
All signatures generated by following this ZIP must be verified successfully as Sapling or Orchard spend authorization signatures using the appropriate validating key.
-
The signatures generated by following this ZIP should meet the security criteria for Signature with Re-Randomizable Keys as specified in the Zcash protocol 10.
+
The signatures generated by following this ZIP should meet the security criteria for Signature with Re-Randomizable Keys as specified in the Zcash protocol 11.
The threat model described below must be taken into account.
This ZIP does not support removing the Coordinator role, as described in #[frost-removingcoordinator]_.
+
This ZIP does not support removing the Coordinator role, as described in 5.
This ZIP does not prevent key share holders from linking the signing operation to a transaction in the blockchain.
-
Like the FROST specification 3, this ZIP does not specify a key generation procedure; but refer to that specification for guidelines.
+
Like the FROST specification 3, this ZIP does not specify a key generation procedure; but refer to that specification for guidelines.
Network privacy is not in scope for this ZIP, and must be obtained with other tools if desired.
Specification
-
Algorithms in this section are specified using Python pseudo-code, in the same fashion as the FROST specification 3.
-
The types Scalar, Element, and G are defined in #[frost-primeordergroup]_, as well as the notation for elliptic-curve arithmetic, which uses the additive notation. Note that this notation differs from that used in the Zcash Protocol Specification. For example, G.ScalarMult(P, k) is used for scalar multiplication, where the protocol spec would use
+
Algorithms in this section are specified using Python pseudo-code, in the same fashion as the FROST specification 3.
+
The types Scalar, Element, and G are defined in 6, as well as the notation for elliptic-curve arithmetic, which uses the additive notation. Note that this notation differs from that used in the Zcash Protocol Specification. For example, G.ScalarMult(P, k) is used for scalar multiplication, where the protocol spec would use
\([k] P\)
with the group implied by
\(P\)
.
+
An additional per-ciphersuite hash function is used, denote HR(m), which receives an arbitrary-sized byte string and returns a Scalar. It is defined concretely in the Ciphersuites section.
Re-randomizable FROST
-
To add re-randomization to FROST, follow the specification 3 with the following modifications.
+
To add re-randomization to FROST, follow the specification 3 with the following modifications.
Key Generation
-
While key generation is out of scope for this ZIP and the FROST spec 3, it needs to be consistent with FROST, see 8 for guidance. The spend authorization private key
+
While key generation is out of scope for this ZIP and the FROST spec 3, it needs to be consistent with FROST, see 8 for guidance. The spend authorization private key
\(\mathsf{ask}\)
- 12 is the particular key that must be used in the context of this ZIP. Note that the
+ 13 is the particular key that must be used in the context of this ZIP. Note that the
\(\mathsf{ask}\)
is usually derived from the spending key
\(\mathsf{sk}\)
- , though that is not required. This allows using distributed key generation, since the key it generates is unpredictable. Note however that note deriving
+ , though that is not required. Not doing so allows using distributed key generation, since the key it generates is unpredictable. Note however that not deriving
\(\mathsf{ask}\)
from
\(\mathsf{sk}\)
prevents using seed phrases to recover the original secret (which may be something desirable in the context of FROST).
Randomizer Generation
-
A new helper function is defined, which computes
- \(\mathsf{RedDSA.GenRandom}\)
- :
+
A new helper function is defined, which generates a randomizer. The encode_signing_package is defined as the byte serialization of the msg, commitment_list values as described in 10. Implementations MAY choose another encoding as long as all values (the message, and the identifier, binding nonce and hiding nonce for each participant) are encoded.
randomizer_generate():
Inputs:
-- None
+- msg, the message being signed in the current FROST signing run
+- commitment_list = [(i, hiding_nonce_commitment_i,
+ binding_nonce_commitment_i), ...], a list of commitments issued by
+ each participant, where each element in the list indicates a
+ NonZeroScalar identifier i and two commitment Element values
+ (hiding_nonce_commitment_i, binding_nonce_commitment_i). This list
+ MUST be sorted in ascending order by identifier.
Outputs: randomizer, a Scalar
-def randomizer_generate():
- randomizer_input = random_bytes(64)
- return H3(randomizer_input)
Roune One is exactly the same as specified 3. But for context, it involves these steps:
+
Roune One is exactly the same as specified 3. But for context, it involves these steps:
Each signer generates nonces and their corresponding public commitments. A nonce is a pair of Scalar values, and a commitment is a pair of Element values.
The nonces are stored locally by the signer and kept private for use in the second round.
@@ -113,19 +121,19 @@ def randomizer_generate():
Round Two - Signature Share Generation
In Round Two, the Coordinator generates a random scalar randomizer by calling randomizer_generate and sends it to each signer, over a confidential and authenticated channel, along with the message and the set of signing commitments. (Note that this differs from regular FROST which just requires an authenticated channel.)
-
In Zcash, the message that needs to be signed is actually the SIGHASH transaction hash, which does not convey enough information for the signers to decide if they want to authorize the transaction or not. Therefore, in practice, more data is needed to be sent (over the same encrypted, authenticated channel) from the Coordinator to the signers, possibly the transaction itself, openings of value commitments, decryption of note ciphertexts, etc.; and the signers must check that the given SIGHASH matches the data sent from the Coordinator, or compute the SIGHASH themselves from that data. However, the specific mechanism for that process is outside the scope of this ZIP.
-
The sign function remains unchanged, but its inputs must be modified relative to the randomizer as following:
+
In Zcash, the message that needs to be signed is actually the SIGHASH transaction hash, which does not convey enough information for the signers to decide if they want to authorize the transaction or not. Therefore, in practice, more data is needed to be sent (over the same encrypted, authenticated channel) from the Coordinator to the signers, possibly the transaction itself, openings of value commitments, decryption of note ciphertexts, etc.; and the signers MUST check that the given SIGHASH matches the data sent from the Coordinator, or compute the SIGHASH themselves from that data. However, the specific mechanism for that process is outside the scope of this ZIP.
+
The randomized sign function is defined as the regular FROST sign function, but with its inputs modified relative to the randomizer as following:
The aggregate function remains unchanged, but its inputs must be modified relative to the randomizer as following:
+
The randomized aggregate function is defined as the regular FROST aggregate function, but with its inputs modified relative to the randomizer as following:
The verify_signature_share function remains unchanged, but its inputs must be modified relative to the randomizer as following:
+
The randomized verify_signature_share function is defined as the regular FROST verify_signature_share function, but with its inputs modified relative to the randomizer as following:
This ciphersuite uses Jubjub for the Group and BLAKE2b-512 for the Hash function H meant to produce signatures indistinguishable from RedJubjub Sapling Spend Authorization Signatures as specified in 11.
+
This ciphersuite uses Jubjub for the Group and BLAKE2b-512 for the Hash function H meant to produce signatures indistinguishable from RedJubjub Sapling Spend Authorization Signatures as specified in 12.
RandomScalar(): Implemented by returning a uniformly random Scalar in the range [0, G.Order() - 1]. Refer to {{frost-randomscalar}} for implementation guidance.
SerializeElement(P): Implemented as
\(\mathsf{repr}_\mathbb{J}(P)\)
- as defined in 13
DeserializeElement(P): Implemented as
\(\mathsf{abst}_\mathbb{J}(P)\)
- as defined in 13, returning an error if
+ as defined in 14, returning an error if
\(\bot\)
is returned. Additionally, this function validates that the resulting element is not the group identity element, returning an error if the check fails.
SerializeScalar: Implemented by outputting the little-endian 32-byte encoding of the Scalar value.
DeserializeScalar: Implemented by attempting to deserialize a Scalar from a little-endian 32-byte string. This function can fail if the input does not represent a Scalar in the range [0, G.Order() - 1].
-
Hash (H): BLAKE2b-512 1 (BLAKE2b with 512-bit output and 16-byte personalization string), and Nh = 64.
+
Hash (H): BLAKE2b-512 1 (BLAKE2b with 512-bit output and 16-byte personalization string), and Nh = 64.
H1(m): Implemented by computing BLAKE2b-512("FROST_RedJubjubR", m), interpreting the 64 bytes as a little-endian integer, and reducing the resulting integer modulo G.Order().
H2(m): Implemented by computing BLAKE2b-512("Zcash_RedJubjubH", m), interpreting the 64 bytes as a little-endian integer, and reducing the resulting integer modulo G.Order(). (This is equivalent to
\(\mathsf{H}^\circledast(m)\)
, as defined by the
\(\mathsf{RedJubjub}\)
- scheme instantiated in 10.)
H3(m): Implemented by computing BLAKE2b-512("FROST_RedJubjubN", m), interpreting the 64 bytes as a little-endian integer, and reducing the resulting integer modulo G.Order().
H4(m): Implemented by computing BLAKE2b-512("FROST_RedJubjubM", m).
H5(m): Implemented by computing BLAKE2b-512("FROST_RedJubjubC", m).
+
HR(m): Implemented by computing BLAKE2b-512("FROST_RedJubjubA", m), interpreting the 64 bytes as a little-endian integer, and reducing the resulting integer modulo G.Order().
-
Signature verification is as specified in 11 for RedJubjub.
+
Signature verification is as specified in 12 for RedJubjub.
FROST(Pallas, BLAKE2b-512)
-
This ciphersuite uses Pallas for the Group and BLAKE2b-512 for the Hash function H meant to produce signatures indistinguishable from RedPallas Orchard Spend Authorization Signatures as specified in 11.
+
This ciphersuite uses Pallas for the Group and BLAKE2b-512 for the Hash function H meant to produce signatures indistinguishable from RedPallas Orchard Spend Authorization Signatures as specified in 12.
RandomScalar(): Implemented by returning a uniformly random Scalar in the range [0, G.Order() - 1]. Refer to {{frost-randomscalar}} for implementation guidance.
SerializeElement(P): Implemented as
\(\mathsf{repr}_\mathbb{P}(P)\)
- as defined in 14.
DeserializeElement(P): Implemented as
\(\mathsf{abst}_\mathbb{P}(P)\)
- as defined in 14, failing if
+ as defined in 15, failing if
\(\bot\)
is returned. Additionally, this function validates that the resulting element is not the group identity element, returning an error if the check fails.
SerializeScalar: Implemented by outputting the little-endian 32-byte encoding of the Scalar value.
DeserializeScalar: Implemented by attempting to deserialize a Scalar from a little-endian 32-byte string. This function can fail if the input does not represent a Scalar in the range [0, G.Order() - 1].
-
Hash (H): BLAKE2b-512 1 (BLAKE2b with 512-bit output and 16-byte personalization string), and Nh = 64.
+
Hash (H): BLAKE2b-512 1 (BLAKE2b with 512-bit output and 16-byte personalization string), and Nh = 64.
H1(m): Implemented by computing BLAKE2b-512("FROST_RedPallasR", m), interpreting the 64 bytes as a little-endian integer, and reducing the resulting integer modulo G.Order().
H2(m): Implemented by computing BLAKE2b-512("Zcash_RedPallasH", m), interpreting the 64 bytes as a little-endian integer, and reducing the resulting integer modulo G.Order(). (This is equivalent to
\(\mathsf{H}^\circledast(m)\)
, as defined by the
\(\mathsf{RedPallas}\)
- scheme instantiated in 10.)
H3(m): Implemented by computing BLAKE2b-512("FROST_RedPallasN", m), interpreting the 64 bytes as a little-endian integer, and reducing the resulting integer modulo G.Order().
H4(m): Implemented by computing BLAKE2b-512("FROST_RedPallasM", m).
H5(m): Implemented by computing BLAKE2b-512("FROST_RedPallasC", m).
+
HR(m): Implemented by computing BLAKE2b-512("FROST_RedPallasA", m), interpreting the 64 bytes as a little-endian integer, and reducing the resulting integer modulo G.Order().
-
Signature verification is as specified in 11 for RedPallas.
+
Signature verification is as specified in 12 for RedPallas.
Rationale
FROST is a threshold Schnorr signature scheme, and Zcash Spend Authorization are also Schnorr signatures, which allows the usage of FROST with Zcash. However, since there is no widespread standard for Schnorr signatures, it must be ensured that the signatures generated by the FROST variant specified in this ZIP can be verified successfully by a Zcash implementation following its specification. In practice this entails making sure that the generated signature can be verified by the
\(\mathsf{RedDSA.Validate}\)
- function specified in 10:
The FROST signature, when split into R and S in the first step of
\(\mathsf{RedDSA.Validate}\)
@@ -229,11 +239,11 @@ def randomizer_generate():
\(\mathsf{H}^\circledast(m)\)
Zcash function in the ciphersuites, and making sure its input will be the same. Fortunately FROST and Zcash use the same input order (R, public key, message) so we just need to make sure that SerializeElement (used to compute the encoded public key before passing to the hash function) matches what
\(\mathsf{RedDSA.Validate}\)
- expects; which is possible since both R and vk (the public key) are encoded in the same way in Zcash.
+ expects; which is possible since both R and vk (the public key) are encoded in the same way as in Zcash.
Note that r (and thus R) will not be generated as specified in RedDSA.Sign. This is not an issue however, since with Schnorr signatures it does not matter for the verifier how the r value was chosen, it just needs to be generated uniformly at random, which is true for FROST.
The above will ensure that the verification equation in
\(\mathsf{RedDSA.Validate}\)
- will pass, since FROST ensures the exact same equation will be valid as described in 7.
+ will pass, since FROST ensures the exact same equation will be valid as described in 7.
The second step is adding the re-randomization functionality so that each FROST signing generates a re-randomized signature:
@@ -245,8 +255,8 @@ def randomizer_generate():
, which refers to the randomizer as
\(\alpha\)
) and sum(lambda_i * c * (sk_i + randomizer)). The latter can be rewritten as c * (sum(lambda_i * sk_i) + randomizer *
-sum(lambda_i). Since sum(lambda_i * sk_i) == sk per the Shamir secret sharing mechanism used by FROST, and since sum(lambda_i) == 115, we arrive at c * (sk + randomizer) as required.
-
The re-randomization procedure must be exactly the same as in 10 to ensure that re-randomized keys are uniformly distributed and signatures are unlinkable. This is also true; observe that randomizer_generate is exactly the same as
+sum(lambda_i). Since sum(lambda_i * sk_i) == sk per the Shamir secret sharing mechanism used by FROST, and since sum(lambda_i) == 117, we arrive at c * (sk + randomizer) as required.
+
The re-randomization procedure must be exactly the same as in 11 to ensure that re-randomized keys are uniformly distributed and signatures are unlinkable. This is also true; observe that randomizer_generate generates randomizer uniformly at random as required by
\(\mathsf{RedDSA.GenRandom}\)
; and signature generation is compatible with
\(\mathsf{RedDSA.RandomizedPrivate}\)
@@ -260,7 +270,7 @@ sum(lambda_i). Since sum(lambda_i * sk_i) == sk per the Sham
Reference implementation
-
TODO: add links to implementation
+
The reddsa crate 16 contains a re-randomized FROST implementation of both ciphersuites.
References
@@ -335,10 +345,18 @@ sum(lambda_i). Since sum(lambda_i * sk_i) == sk per the Sham
diff --git a/zip-0312.rst b/zip-0312.rst
index 89168bc1..fe6920cb 100644
--- a/zip-0312.rst
+++ b/zip-0312.rst
@@ -48,16 +48,18 @@ private key is what effectively allows the user to spend funds.
This is a security-critical step, since anyone who obtains access to the private
key will be able to spend the user's funds. For this reason, one interesting
-possibility is to require multiple parties to allow the transaction to go through.
-This can be accomplished with threshold signatures, where the private key is
-split between parties in a way that a threshold (e.g. 2 out of 3) of them must
-sign the transaction in order to create the final signature. This enables scenarios
-such as users and exchanges sharing custody of a wallet, for example.
+possibility is to require multiple parties to allow the transaction to go
+through. This can be accomplished with threshold signatures, where the private
+key is split between parties (or generated already split using a distributed
+protocol) in a way that a threshold (e.g. 2 out of 3) of them must sign the
+transaction in order to create the final signature. This enables scenarios such
+as users and third-party services sharing custody of a wallet, or a group of
+people managing shared funds, for example.
-FROST is one of such threshold signature protocols. However, it can't be used as-is
-since the Zcash protocol also requires re-randomizing public and private keys
-to ensure unlinkability between transactions. This ZIP specifies a variant of
-FROST with re-randomization support.
+FROST is one of such threshold signature protocols. However, it can't be used
+as-is since the Zcash protocol also requires re-randomizing public and private
+keys to ensure unlinkability between transactions. This ZIP specifies a variant
+of FROST with re-randomization support.
Requirements
@@ -66,8 +68,9 @@ Requirements
- All signatures generated by following this ZIP must be verified successfully
as Sapling or Orchard spend authorization signatures using the appropriate
validating key.
-- The signatures generated by following this ZIP should meet the security criteria
- for Signature with Re-Randomizable Keys as specified in the Zcash protocol [#protocol-concretereddsa]_.
+- The signatures generated by following this ZIP should meet the security
+ criteria for Signature with Re-Randomizable Keys as specified in the Zcash
+ protocol [#protocol-concretereddsa]_.
- The threat model described below must be taken into account.
Threat Model
@@ -89,9 +92,9 @@ by a single participant, likely the one that created the transaction in the firs
place. Note that this user already controls the privacy of the transaction since
they are responsible for creating the proof.
-This fits well into the "Coordinator" role from the FROST specification [#frost-protocol]_.
-The Coordinator is responsible for sending the message to be signed to all participants,
-and to aggregate the signature shares.
+This fits well into the "Coordinator" role from the FROST specification
+[#frost-protocol]_. The Coordinator is responsible for sending the message to be
+signed to all participants, and to aggregate the signature shares.
With those considerations in mind, the threat model considered in this ZIP is:
@@ -107,7 +110,7 @@ Non-requirements
================
- This ZIP does not support removing the Coordinator role, as described in
- #[frost-removingcoordinator]_.
+ [#frost-removingcoordinator]_.
- This ZIP does not prevent key share holders from linking the signing operation to a
transaction in the blockchain.
- Like the FROST specification [#FROST]_, this ZIP does not specify a key generation
@@ -122,13 +125,17 @@ Specification
Algorithms in this section are specified using Python pseudo-code, in the same
fashion as the FROST specification [#FROST]_.
-The types Scalar, Element, and G are defined in #[frost-primeordergroup]_, as
+The types Scalar, Element, and G are defined in [#frost-primeordergroup]_, as
well as the notation for elliptic-curve arithmetic, which uses the additive
notation. Note that this notation differs from that used in the Zcash Protocol
Specification. For example, ``G.ScalarMult(P, k)`` is used for scalar
multiplication, where the protocol spec would use :math:`[k] P` with the group
implied by :math:`P`.
+An additional per-ciphersuite hash function is used, denote ``HR(m)``, which
+receives an arbitrary-sized byte string and returns a Scalar. It is defined
+concretely in the Ciphersuites section.
+
Re-randomizable FROST
---------------------
@@ -141,33 +148,48 @@ Key Generation
''''''''''''''
While key generation is out of scope for this ZIP and the FROST spec [#FROST]_,
-it needs to be consistent with FROST, see [#frost-tdkg]_ for guidance. The
-spend authorization private key :math:`\mathsf{ask}` [#protocol-spendauthsig]_
-is the particular key that must be used in the context of this ZIP. Note that
-the :math:`\mathsf{ask}` is usually derived from the spending key :math:`\mathsf{sk}`,
-though that is not required. This allows using distributed key generation, since
-the key it generates is unpredictable. Note however that note deriving :math:`\mathsf{ask}`
-from :math:`\mathsf{sk}` prevents using seed phrases to recover the original
-secret (which may be something desirable in the context of FROST).
+it needs to be consistent with FROST, see [#frost-tdkg]_ for guidance. The spend
+authorization private key :math:`\mathsf{ask}` [#protocol-spendauthsig]_ is the
+particular key that must be used in the context of this ZIP. Note that the
+:math:`\mathsf{ask}` is usually derived from the spending key
+:math:`\mathsf{sk}`, though that is not required. Not doing so allows using
+distributed key generation, since the key it generates is unpredictable. Note
+however that not deriving :math:`\mathsf{ask}` from :math:`\mathsf{sk}` prevents
+using seed phrases to recover the original secret (which may be something
+desirable in the context of FROST).
Randomizer Generation
'''''''''''''''''''''
-A new helper function is defined, which computes :math:`\mathsf{RedDSA.GenRandom}`:
+A new helper function is defined, which generates a randomizer. The
+`encode_signing_package` is defined as the byte serialization of the `msg`,
+`commitment_list` values as described in [#frost-serialization]_.
+Implementations MAY choose another encoding as long as all values (the message,
+and the identifier, binding nonce and hiding nonce for each participant) are
+encoded.
::
randomizer_generate():
Inputs:
- - None
+ - msg, the message being signed in the current FROST signing run
+ - commitment_list = [(i, hiding_nonce_commitment_i,
+ binding_nonce_commitment_i), ...], a list of commitments issued by
+ each participant, where each element in the list indicates a
+ NonZeroScalar identifier i and two commitment Element values
+ (hiding_nonce_commitment_i, binding_nonce_commitment_i). This list
+ MUST be sorted in ascending order by identifier.
Outputs: randomizer, a Scalar
- def randomizer_generate():
- randomizer_input = random_bytes(64)
- return H3(randomizer_input)
+ def randomizer_generate(msg, commitment_list):
+ rng_randomizer = G.RandomScalar()
+ rng_randomizer_enc = G.SerializeScalar(rng_randomizer)
+ signing_package_enc = encode_signing_package(commitment_list, msg)
+ randomizer_input = random_bytes(rng_randomizer_enc || signing_package_enc)
+ return HR(randomizer_input)
Round One - Commitment
@@ -196,13 +218,14 @@ transaction hash, which does not convey enough information for the signers to
decide if they want to authorize the transaction or not. Therefore, in practice,
more data is needed to be sent (over the same encrypted, authenticated channel)
from the Coordinator to the signers, possibly the transaction itself, openings of
-value commitments, decryption of note ciphertexts, etc.; and the signers must check
+value commitments, decryption of note ciphertexts, etc.; and the signers MUST check
that the given SIGHASH matches the data sent from the Coordinator, or compute the
SIGHASH themselves from that data. However, the specific mechanism for that process
is outside the scope of this ZIP.
-The ``sign`` function remains unchanged, but its inputs must be modified relative
-to the ``randomizer`` as following:
+The randomized ``sign`` function is defined as the regular FROST ``sign``
+function, but with its inputs modified relative to the ``randomizer`` as
+following:
- ``sk_i = sk_i + randomizer``
- ``group_public_key = group_public_key + G.ScalarBaseMult(randomizer)``
@@ -211,13 +234,15 @@ to the ``randomizer`` as following:
Signature Share Verification and Aggregation
''''''''''''''''''''''''''''''''''''''''''''
-The ``aggregate`` function remains unchanged, but its inputs must be modified
-relative to the ``randomizer`` as following:
+The randomized ``aggregate`` function is defined as the regular FROST
+``aggregate`` function, but with its inputs modified relative to the
+``randomizer`` as following:
- ``group_public_key = group_public_key + G.ScalarBaseMult(randomizer)``
-The ``verify_signature_share`` function remains unchanged, but its inputs must be modified
-relative to the ``randomizer`` as following:
+The randomized ``verify_signature_share`` function is defined as the regular
+FROST ``verify_signature_share`` function, but with its inputs modified relative
+to the ``randomizer`` as following:
- ``PK_i = PK_i + G.ScalarBaseMult(randomizer)``
- ``group_public_key = group_public_key + G.ScalarBaseMult(randomizer)``
@@ -270,6 +295,9 @@ Authorization Signatures as specified in [#protocol-concretespendauthsig]_.
modulo ``G.Order()``.
- H4(m): Implemented by computing BLAKE2b-512("FROST_RedJubjubM", m).
- H5(m): Implemented by computing BLAKE2b-512("FROST_RedJubjubC", m).
+ - HR(m): Implemented by computing BLAKE2b-512("FROST_RedJubjubA", m), interpreting
+ the 64 bytes as a little-endian integer, and reducing the resulting integer
+ modulo ``G.Order()``.
Signature verification is as specified in [#protocol-concretespendauthsig]_
for RedJubjub.
@@ -315,6 +343,9 @@ Authorization Signatures as specified in [#protocol-concretespendauthsig]_.
modulo ``G.Order()``.
- H4(m): Implemented by computing BLAKE2b-512("FROST_RedPallasM", m).
- H5(m): Implemented by computing BLAKE2b-512("FROST_RedPallasC", m).
+ - HR(m): Implemented by computing BLAKE2b-512("FROST_RedPallasA", m), interpreting
+ the 64 bytes as a little-endian integer, and reducing the resulting integer
+ modulo ``G.Order()``.
Signature verification is as specified in [#protocol-concretespendauthsig]_
for RedPallas.
@@ -344,7 +375,7 @@ by the :math:`\mathsf{RedDSA.Validate}` function specified in
so we just need to make sure that SerializeElement (used to compute the
encoded public key before passing to the hash function) matches what
:math:`\mathsf{RedDSA.Validate}` expects; which is possible since both `R` and
- `vk` (the public key) are encoded in the same way in Zcash.
+ `vk` (the public key) are encoded in the same way as in Zcash.
- Note that ``r`` (and thus ``R``) will not be generated as specified in RedDSA.Sign.
This is not an issue however, since with Schnorr signatures it does not matter
@@ -380,9 +411,9 @@ signing generates a re-randomized signature:
- The re-randomization procedure must be exactly the same as in
[#protocol-concretereddsa]_ to ensure that re-randomized keys are uniformly
distributed and signatures are unlinkable. This is also true; observe that
- ``randomizer_generate`` is exactly the same as
- :math:`\mathsf{RedDSA.GenRandom}`; and signature generation is compatible with
- :math:`\mathsf{RedDSA.RandomizedPrivate}`,
+ ``randomizer_generate`` generates randomizer uniformly at random as required
+ by :math:`\mathsf{RedDSA.GenRandom}`; and signature generation is compatible
+ with :math:`\mathsf{RedDSA.RandomizedPrivate}`,
:math:`\mathsf{RedDSA.RandomizedPublic}`, :math:`\mathsf{RedDSA.Sign}` and
:math:`\mathsf{RedDSA.Validate}` as explained in the previous item.
@@ -390,7 +421,8 @@ signing generates a re-randomized signature:
Reference implementation
========================
-TODO: add links to implementation
+The `reddsa` crate [#crate-reddsa]_ contains a re-randomized FROST implementation of
+both ciphersuites.
References
@@ -405,9 +437,11 @@ References
.. [#frost-primeorderverify] `Draft RFC: Two-Round Threshold Schnorr Signatures with FROST. Appendix B: Schnorr Signature Generation and Verification for Prime-Order Groups `_
.. [#frost-tdkg] `Draft RFC: Two-Round Threshold Schnorr Signatures with FROST. Appendix B: Trusted Dealer Key Generation `_
.. [#frost-randomscalar] `Draft RFC: Two-Round Threshold Schnorr Signatures with FROST. Appendix C: Random Scalar Generation `_
+.. [#frost-serialization] `The ZF FROST Book, Serialization Format `_
.. [#protocol-concretereddsa] `Zcash Protocol Specification, Version 2022.3.4 [NU5]. Section 5.4.7: RedDSA, RedJubjub, and RedPallas `_
.. [#protocol-concretespendauthsig] `Zcash Protocol Specification, Version 2022.3.4 [NU5]. Section 5.4.7.1: Spend Authorization Signature (Sapling and Orchard) `_
.. [#protocol-spendauthsig] `Zcash Protocol Specification, Version 2022.3.4 [NU5]. Section 4.15: Spend Authorization Signature (Sapling and Orchard) `_
.. [#protocol-jubjub] `Zcash Protocol Specification, Version 2022.3.4 [NU5]. Section 5.4.9.3: Jubjub `_
.. [#protocol-pallasandvesta] `Zcash Protocol Specification, Version 2022.3.4 [NU5]. Section 5.4.9.6: Pallas and Vesta `_
+.. [#crate-reddsa] `reddsa `_
.. [#sum-lambda-proof] `Prove that the sum of the Lagrange (interpolation) coefficients is equal to 1 `_
From eecee7c9d9e5d8ca7b449614ac4b9f9983735863 Mon Sep 17 00:00:00 2001
From: Conrado Gouvea
Date: Thu, 7 Dec 2023 19:03:51 -0300
Subject: [PATCH 33/37] Apply suggestions from code review
Co-authored-by: Daira Emma Hopwood
---
zip-0312.rst | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/zip-0312.rst b/zip-0312.rst
index fe6920cb..75586769 100644
--- a/zip-0312.rst
+++ b/zip-0312.rst
@@ -1,7 +1,7 @@
::
ZIP: 312
- Title: FROST for Spend Authorization Signatures
+ Title: FROST for Spend Authorization Multisignatures
Owners: Conrado Gouvea
Chelsea Komlo
Deirdre Connolly
@@ -167,7 +167,7 @@ A new helper function is defined, which generates a randomizer. The
`commitment_list` values as described in [#frost-serialization]_.
Implementations MAY choose another encoding as long as all values (the message,
and the identifier, binding nonce and hiding nonce for each participant) are
-encoded.
+unambiguously encoded.
::
@@ -188,7 +188,7 @@ encoded.
rng_randomizer = G.RandomScalar()
rng_randomizer_enc = G.SerializeScalar(rng_randomizer)
signing_package_enc = encode_signing_package(commitment_list, msg)
- randomizer_input = random_bytes(rng_randomizer_enc || signing_package_enc)
+ randomizer_input = rng_randomizer_enc || signing_package_enc
return HR(randomizer_input)
From 201857978dabb4debbd28a74f7c45944448acb96 Mon Sep 17 00:00:00 2001
From: Conrado Gouvea
Date: Wed, 20 Dec 2023 18:13:37 -0300
Subject: [PATCH 34/37] generate a random buffer instead of serializing a
random scalar
---
zip-0312.rst | 10 +++++++---
1 file changed, 7 insertions(+), 3 deletions(-)
diff --git a/zip-0312.rst b/zip-0312.rst
index 75586769..bb3212ae 100644
--- a/zip-0312.rst
+++ b/zip-0312.rst
@@ -169,6 +169,10 @@ Implementations MAY choose another encoding as long as all values (the message,
and the identifier, binding nonce and hiding nonce for each participant) are
unambiguously encoded.
+The function `random_bytes(n)` is defined in [#FROST]_ and it returns a buffer
+with `n` bytes sampled uniformly at random. The constant `Ns` is also specified
+in [#FROST]_ and is the size of a serialized scalar.
+
::
randomizer_generate():
@@ -185,10 +189,10 @@ unambiguously encoded.
Outputs: randomizer, a Scalar
def randomizer_generate(msg, commitment_list):
- rng_randomizer = G.RandomScalar()
- rng_randomizer_enc = G.SerializeScalar(rng_randomizer)
+ # Generate a random byte buffer with the size of a serialized scalar
+ rng_randomizer = random_bytes(Ns)
signing_package_enc = encode_signing_package(commitment_list, msg)
- randomizer_input = rng_randomizer_enc || signing_package_enc
+ randomizer_input = rng_randomizer || signing_package_enc
return HR(randomizer_input)
From c9e2a854d34e02d7197f1e89fc1daca02f546879 Mon Sep 17 00:00:00 2001
From: Conrado Gouvea
Date: Wed, 20 Dec 2023 18:16:42 -0300
Subject: [PATCH 35/37] update HTML
---
zip-0312.html | 65 ++++++++++++++++++++++++++-------------------------
1 file changed, 33 insertions(+), 32 deletions(-)
diff --git a/zip-0312.html b/zip-0312.html
index 9d82ac99..86609dd4 100644
--- a/zip-0312.html
+++ b/zip-0312.html
@@ -1,14 +1,14 @@
- ZIP 312: FROST for Spend Authorization Signatures
+ ZIP 312: FROST for Spend Authorization Multisignatures
A new helper function is defined, which generates a randomizer. The encode_signing_package is defined as the byte serialization of the msg, commitment_list values as described in 10. Implementations MAY choose another encoding as long as all values (the message, and the identifier, binding nonce and hiding nonce for each participant) are encoded.
+
A new helper function is defined, which generates a randomizer. The encode_signing_package is defined as the byte serialization of the msg, commitment_list values as described in 10. Implementations MAY choose another encoding as long as all values (the message, and the identifier, binding nonce and hiding nonce for each participant) are unambiguously encoded.
+
The function random_bytes(n) is defined in 3 and it returns a buffer with n bytes sampled uniformly at random. The constant Ns is also specified in 3 and is the size of a serialized scalar.
randomizer_generate():
Inputs:
@@ -105,14 +106,14 @@ Inputs:
Outputs: randomizer, a Scalar
def randomizer_generate(msg, commitment_list):
- rng_randomizer = G.RandomScalar()
- rng_randomizer_enc = G.SerializeScalar(rng_randomizer)
+ # Generate a random byte buffer with the size of a serialized scalar
+ rng_randomizer = random_bytes(Ns)
signing_package_enc = encode_signing_package(commitment_list, msg)
- randomizer_input = random_bytes(rng_randomizer_enc || signing_package_enc)
+ randomizer_input = rng_randomizer || signing_package_enc
return HR(randomizer_input)
Round One - Commitment
-
Roune One is exactly the same as specified 3. But for context, it involves these steps:
+
Roune One is exactly the same as specified 3. But for context, it involves these steps:
Each signer generates nonces and their corresponding public commitments. A nonce is a pair of Scalar values, and a commitment is a pair of Element values.
The nonces are stored locally by the signer and kept private for use in the second round.
This ciphersuite uses Jubjub for the Group and BLAKE2b-512 for the Hash function H meant to produce signatures indistinguishable from RedJubjub Sapling Spend Authorization Signatures as specified in 12.
+
This ciphersuite uses Jubjub for the Group and BLAKE2b-512 for the Hash function H meant to produce signatures indistinguishable from RedJubjub Sapling Spend Authorization Signatures as specified in 12.
RandomScalar(): Implemented by returning a uniformly random Scalar in the range [0, G.Order() - 1]. Refer to {{frost-randomscalar}} for implementation guidance.
SerializeElement(P): Implemented as
\(\mathsf{repr}_\mathbb{J}(P)\)
- as defined in 14
DeserializeElement(P): Implemented as
\(\mathsf{abst}_\mathbb{J}(P)\)
- as defined in 14, returning an error if
+ as defined in 14, returning an error if
\(\bot\)
is returned. Additionally, this function validates that the resulting element is not the group identity element, returning an error if the check fails.
SerializeScalar: Implemented by outputting the little-endian 32-byte encoding of the Scalar value.
DeserializeScalar: Implemented by attempting to deserialize a Scalar from a little-endian 32-byte string. This function can fail if the input does not represent a Scalar in the range [0, G.Order() - 1].
-
Hash (H): BLAKE2b-512 1 (BLAKE2b with 512-bit output and 16-byte personalization string), and Nh = 64.
+
Hash (H): BLAKE2b-512 1 (BLAKE2b with 512-bit output and 16-byte personalization string), and Nh = 64.
H1(m): Implemented by computing BLAKE2b-512("FROST_RedJubjubR", m), interpreting the 64 bytes as a little-endian integer, and reducing the resulting integer modulo G.Order().
H2(m): Implemented by computing BLAKE2b-512("Zcash_RedJubjubH", m), interpreting the 64 bytes as a little-endian integer, and reducing the resulting integer modulo G.Order(). (This is equivalent to
\(\mathsf{H}^\circledast(m)\)
, as defined by the
\(\mathsf{RedJubjub}\)
- scheme instantiated in 11.)
H3(m): Implemented by computing BLAKE2b-512("FROST_RedJubjubN", m), interpreting the 64 bytes as a little-endian integer, and reducing the resulting integer modulo G.Order().
H4(m): Implemented by computing BLAKE2b-512("FROST_RedJubjubM", m).
H5(m): Implemented by computing BLAKE2b-512("FROST_RedJubjubC", m).
Signature verification is as specified in 12 for RedJubjub.
+
Signature verification is as specified in 12 for RedJubjub.
FROST(Pallas, BLAKE2b-512)
-
This ciphersuite uses Pallas for the Group and BLAKE2b-512 for the Hash function H meant to produce signatures indistinguishable from RedPallas Orchard Spend Authorization Signatures as specified in 12.
+
This ciphersuite uses Pallas for the Group and BLAKE2b-512 for the Hash function H meant to produce signatures indistinguishable from RedPallas Orchard Spend Authorization Signatures as specified in 12.
RandomScalar(): Implemented by returning a uniformly random Scalar in the range [0, G.Order() - 1]. Refer to {{frost-randomscalar}} for implementation guidance.
SerializeElement(P): Implemented as
\(\mathsf{repr}_\mathbb{P}(P)\)
- as defined in 15.
DeserializeElement(P): Implemented as
\(\mathsf{abst}_\mathbb{P}(P)\)
- as defined in 15, failing if
+ as defined in 15, failing if
\(\bot\)
is returned. Additionally, this function validates that the resulting element is not the group identity element, returning an error if the check fails.
SerializeScalar: Implemented by outputting the little-endian 32-byte encoding of the Scalar value.
DeserializeScalar: Implemented by attempting to deserialize a Scalar from a little-endian 32-byte string. This function can fail if the input does not represent a Scalar in the range [0, G.Order() - 1].
-
Hash (H): BLAKE2b-512 1 (BLAKE2b with 512-bit output and 16-byte personalization string), and Nh = 64.
+
Hash (H): BLAKE2b-512 1 (BLAKE2b with 512-bit output and 16-byte personalization string), and Nh = 64.
H1(m): Implemented by computing BLAKE2b-512("FROST_RedPallasR", m), interpreting the 64 bytes as a little-endian integer, and reducing the resulting integer modulo G.Order().
H2(m): Implemented by computing BLAKE2b-512("Zcash_RedPallasH", m), interpreting the 64 bytes as a little-endian integer, and reducing the resulting integer modulo G.Order(). (This is equivalent to
\(\mathsf{H}^\circledast(m)\)
, as defined by the
\(\mathsf{RedPallas}\)
- scheme instantiated in 11.)
H3(m): Implemented by computing BLAKE2b-512("FROST_RedPallasN", m), interpreting the 64 bytes as a little-endian integer, and reducing the resulting integer modulo G.Order().
H4(m): Implemented by computing BLAKE2b-512("FROST_RedPallasM", m).
H5(m): Implemented by computing BLAKE2b-512("FROST_RedPallasC", m).
Signature verification is as specified in 12 for RedPallas.
+
Signature verification is as specified in 12 for RedPallas.
Rationale
FROST is a threshold Schnorr signature scheme, and Zcash Spend Authorization are also Schnorr signatures, which allows the usage of FROST with Zcash. However, since there is no widespread standard for Schnorr signatures, it must be ensured that the signatures generated by the FROST variant specified in this ZIP can be verified successfully by a Zcash implementation following its specification. In practice this entails making sure that the generated signature can be verified by the
\(\mathsf{RedDSA.Validate}\)
- function specified in 11:
The FROST signature, when split into R and S in the first step of
\(\mathsf{RedDSA.Validate}\)
@@ -243,7 +244,7 @@ def randomizer_generate(msg, commitment_list):
Note that r (and thus R) will not be generated as specified in RedDSA.Sign. This is not an issue however, since with Schnorr signatures it does not matter for the verifier how the r value was chosen, it just needs to be generated uniformly at random, which is true for FROST.
The above will ensure that the verification equation in
\(\mathsf{RedDSA.Validate}\)
- will pass, since FROST ensures the exact same equation will be valid as described in 7.
+ will pass, since FROST ensures the exact same equation will be valid as described in 7.
The second step is adding the re-randomization functionality so that each FROST signing generates a re-randomized signature:
@@ -255,8 +256,8 @@ def randomizer_generate(msg, commitment_list):
, which refers to the randomizer as
\(\alpha\)
) and sum(lambda_i * c * (sk_i + randomizer)). The latter can be rewritten as c * (sum(lambda_i * sk_i) + randomizer *
-sum(lambda_i). Since sum(lambda_i * sk_i) == sk per the Shamir secret sharing mechanism used by FROST, and since sum(lambda_i) == 117, we arrive at c * (sk + randomizer) as required.
-
The re-randomization procedure must be exactly the same as in 11 to ensure that re-randomized keys are uniformly distributed and signatures are unlinkable. This is also true; observe that randomizer_generate generates randomizer uniformly at random as required by
+sum(lambda_i). Since sum(lambda_i * sk_i) == sk per the Shamir secret sharing mechanism used by FROST, and since sum(lambda_i) == 117, we arrive at c * (sk + randomizer) as required.
+
The re-randomization procedure must be exactly the same as in 11 to ensure that re-randomized keys are uniformly distributed and signatures are unlinkable. This is also true; observe that randomizer_generate generates randomizer uniformly at random as required by
\(\mathsf{RedDSA.GenRandom}\)
; and signature generation is compatible with
\(\mathsf{RedDSA.RandomizedPrivate}\)
@@ -270,7 +271,7 @@ sum(lambda_i). Since sum(lambda_i * sk_i) == sk per the Sham
Reference implementation
-
The reddsa crate 16 contains a re-randomized FROST implementation of both ciphersuites.
+
The reddsa crate 16 contains a re-randomized FROST implementation of both ciphersuites.
This proposal adapts FROST 3, a threshold signature scheme, to make it unlinkable, which is a requirement for its use in the Zcash protocol. The adapted scheme generates signatures compatible with spend authorization signatures in the Zcash protocol, for the Sapling and Orchard network upgrades.
+
This proposal adapts FROST 3, a threshold signature scheme, to make it unlinkable, which is a requirement for its use in the Zcash protocol. The adapted scheme generates signatures compatible with spend authorization signatures in the Zcash protocol, for the Sapling and Orchard shielded pools.
Motivation
In the Zcash protocol, Spend Authorization Signatures are employed to authorize a transaction. The ability to generate these signatures with the user's private key is what effectively allows the user to spend funds.
diff --git a/zip-0312.rst b/zip-0312.rst
index bb3212ae..ea018af1 100644
--- a/zip-0312.rst
+++ b/zip-0312.rst
@@ -36,7 +36,7 @@ Abstract
This proposal adapts FROST [#FROST]_, a threshold signature scheme,
to make it unlinkable, which is a requirement for its use in the Zcash protocol.
The adapted scheme generates signatures compatible with spend authorization
-signatures in the Zcash protocol, for the Sapling and Orchard network upgrades.
+signatures in the Zcash protocol, for the Sapling and Orchard shielded pools.
Motivation
From 51dd721dd8fc1b8039c6bcc53681b9df48d1678b Mon Sep 17 00:00:00 2001
From: Conrado Gouvea
Date: Fri, 9 Feb 2024 14:19:42 -0300
Subject: [PATCH 37/37] Apply suggestions from code review
Co-authored-by: Daira-Emma Hopwood
---
zip-0312.rst | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/zip-0312.rst b/zip-0312.rst
index ea018af1..df1531b3 100644
--- a/zip-0312.rst
+++ b/zip-0312.rst
@@ -36,7 +36,7 @@ Abstract
This proposal adapts FROST [#FROST]_, a threshold signature scheme,
to make it unlinkable, which is a requirement for its use in the Zcash protocol.
The adapted scheme generates signatures compatible with spend authorization
-signatures in the Zcash protocol, for the Sapling and Orchard shielded pools.
+signatures in the Sapling and Orchard shielded protocols as deployed in Zcash.
Motivation