mirror of https://github.com/zcash/zips.git
399 lines
17 KiB
ReStructuredText
399 lines
17 KiB
ReStructuredText
::
|
|
|
|
ZIP: Unassigned {numbers are assigned by ZIP editors}
|
|
Title: FROST for Spend Authorization Signatures
|
|
Owners: Conrado Gouvea <conrado@zfnd.org>
|
|
Chelsea Komlo <ckomlo@uwaterloo.ca>
|
|
Deirdre Connolly <deirdre@zfnd.org>
|
|
Credits: First Credited
|
|
...
|
|
Status: Draft
|
|
Category: Wallet
|
|
Created: 2022-08-dd
|
|
License: {usually MIT}
|
|
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:
|
|
|
|
{Term to be defined}
|
|
{Definition.}
|
|
{Another term}
|
|
{Definition.}
|
|
|
|
|
|
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 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
|
|
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]_.
|
|
- 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 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.
|
|
|
|
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.
|
|
- 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
|
|
================
|
|
|
|
- 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
|
|
=============
|
|
|
|
Re-randomized FROST
|
|
-------------------
|
|
|
|
To add re-randomization to FROST, follow the specification [#FROST]_ with the
|
|
following modifications.
|
|
|
|
Randomizer Generation
|
|
'''''''''''''''''''''
|
|
|
|
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.
|
|
- 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.
|
|
- 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, randomizer):
|
|
# 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 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, 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 `verify_signature_share` is 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 = 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 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 = 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
|
|
------------
|
|
|
|
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 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 :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
|
|
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_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_RedJubjubDi", 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.
|
|
|
|
|
|
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 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 :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
|
|
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_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_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.
|
|
|
|
|
|
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-05.html>`_
|
|
.. [#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-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>`_
|