fix formulas; address comments

This commit is contained in:
Conrado Gouvea 2022-07-08 18:28:26 -03:00
parent 8cbfddaba5
commit cf19f23f4f
1 changed files with 183 additions and 23 deletions

View File

@ -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