mirror of https://github.com/zcash/zips.git
fix formulas; address comments
This commit is contained in:
parent
8cbfddaba5
commit
cf19f23f4f
206
zip-frost.rst
206
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
|
||||
|
|
Loading…
Reference in New Issue