rename to zip-312.rst

This commit is contained in:
Conrado Gouvea 2023-01-12 11:38:19 -03:00
parent bf9edbd0ef
commit 48f8c2c80e
2 changed files with 461 additions and 468 deletions

View File

@ -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 <conrado@zfnd.org>
Chelsea Komlo <ckomlo@uwaterloo.ca>
Deirdre Connolly <deirdre@zfnd.org>
Status: Draft
Category: Wallet
Created: 2022-08-dd
License: MIT
Discussions-To: <https://github.com/zcash/zips/issues/382>
Pull-Request: <https://github.com/zcash/zips/pull/TODO>
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 <https://blake2.net/#sp>`_
.. [#RFC2119] `RFC 2119: Key words for use in RFCs to Indicate Requirement Levels <https://www.rfc-editor.org/rfc/rfc2119.html>`_
.. [#FROST] `Draft RFC: Two-Round Threshold Schnorr Signatures with FROST <https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-08.html>`_
.. [#frost-protocol] `Draft RFC: Two-Round Threshold Schnorr Signatures with FROST. Section 5: Two-Round FROST Signing Protocol <https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-08.html#section-5>`_
.. [#frost-removingcoordinator] `Draft RFC: Two-Round Threshold Schnorr Signatures with FROST. Section 7.3: Removing the Coordinator Role <https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-08.html#section-7.3>`_
.. [#frost-primeordergroup] `Draft RFC: Two-Round Threshold Schnorr Signatures with FROST. Section 3.1: Prime-Order Group <https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-08.html#section-3.1>`_
.. [#frost-tdkg] `Draft RFC: Two-Round Threshold Schnorr Signatures with FROST. Appendix B: Trusted Dealer Key Generation <https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-08.html#appendix-B>`_
.. [#frost-randomscalar] `Draft RFC: Two-Round Threshold Schnorr Signatures with FROST. Appendix C: Random Scalar Generation <https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-08.html#appendix-C>`_
.. [#protocol-concretereddsa] `Zcash Protocol Specification, Version 2022.3.4 [NU5]. Section 5.4.7: RedDSA, RedJubjub, and RedPallas <https://protocol/protocol.pdf#concretereddsa>`_
.. [#protocol-concretespendauthsig] `Zcash Protocol Specification, Version 2022.3.4 [NU5]. Section 5.4.7.1: Spend Authorization Signature (Sapling and Orchard) <protocol/protocol.pdf#concretespendauthsig>`_
.. [#protocol-spendauthsig] `Zcash Protocol Specification, Version 2022.3.4 [NU5]. Section 4.15: Spend Authorization Signature (Sapling and Orchard) <protocol/protocol.pdf#spendauthsig>`_
.. [#protocol-jubjub] `Zcash Protocol Specification, Version 2022.3.4 [NU5]. Section 5.4.9.3: Jubjub <protocol/protocol.pdf#jubjub>`_
.. [#protocol-pallasandvesta] `Zcash Protocol Specification, Version 2022.3.4 [NU5]. Section 5.4.9.6: Pallas and Vesta <https://protocol/protocol.pdf#pallasandvesta>`_

View File

@ -1,465 +0,0 @@
::
ZIP: 312
Title: FROST for Spend Authorization Signatures
Owners: Conrado Gouvea <conrado@zfnd.org>
Chelsea Komlo <ckomlo@uwaterloo.ca>
Deirdre Connolly <deirdre@zfnd.org>
Status: Draft
Category: Wallet
Created: 2022-08-dd
License: MIT
Discussions-To: <https://github.com/zcash/zips/issues/382>
Pull-Request: <https://github.com/zcash/zips/pull/TODO>
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 <https://blake2.net/#sp>`_
.. [#RFC2119] `RFC 2119: Key words for use in RFCs to Indicate Requirement Levels <https://www.rfc-editor.org/rfc/rfc2119.html>`_
.. [#FROST] `Draft RFC: Two-Round Threshold Schnorr Signatures with FROST <https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-08.html>`_
.. [#frost-protocol] `Draft RFC: Two-Round Threshold Schnorr Signatures with FROST. Section 5: Two-Round FROST Signing Protocol <https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-08.html#section-5>`_
.. [#frost-removingcoordinator] `Draft RFC: Two-Round Threshold Schnorr Signatures with FROST. Section 7.3: Removing the Coordinator Role <https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-08.html#section-7.3>`_
.. [#frost-primeordergroup] `Draft RFC: Two-Round Threshold Schnorr Signatures with FROST. Section 3.1: Prime-Order Group <https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-08.html#section-3.1>`_
.. [#frost-tdkg] `Draft RFC: Two-Round Threshold Schnorr Signatures with FROST. Appendix B: Trusted Dealer Key Generation <https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-08.html#appendix-B>`_
.. [#frost-randomscalar] `Draft RFC: Two-Round Threshold Schnorr Signatures with FROST. Appendix C: Random Scalar Generation <https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-08.html#appendix-C>`_
.. [#protocol-concretereddsa] `Zcash Protocol Specification, Version 2022.3.4 [NU5]. Section 5.4.7: RedDSA, RedJubjub, and RedPallas <https://protocol/protocol.pdf#concretereddsa>`_
.. [#protocol-concretespendauthsig] `Zcash Protocol Specification, Version 2022.3.4 [NU5]. Section 5.4.7.1: Spend Authorization Signature (Sapling and Orchard) <protocol/protocol.pdf#concretespendauthsig>`_
.. [#protocol-spendauthsig] `Zcash Protocol Specification, Version 2022.3.4 [NU5]. Section 4.15: Spend Authorization Signature (Sapling and Orchard) <protocol/protocol.pdf#spendauthsig>`_
.. [#protocol-jubjub] `Zcash Protocol Specification, Version 2022.3.4 [NU5]. Section 5.4.9.3: Jubjub <protocol/protocol.pdf#jubjub>`_
.. [#protocol-pallasandvesta] `Zcash Protocol Specification, Version 2022.3.4 [NU5]. Section 5.4.9.6: Pallas and Vesta <https://protocol/protocol.pdf#pallasandvesta>`_