Transaction Signing

Transactions are signed used the decaf377-rdsa construction. As described briefly in that section, there are two signature domains used in Penumbra: SpendAuth signatures and Binding signatures.

SpendAuth Signatures

SpendAuth signatures are included on each Spend and DelegatorVote action (see Multi-Asset Shielded Pool and Governance for more details on Spend and DelegatorVote actions respectively).

The SpendAuth signatures are created using a randomized signing key and the corresponding randomized verification key provided on the action. The purpose of the randomization is to prevent linkage of verification keys across actions.

The SpendAuth signature is computed using the decaf377-rdsa Sign algorithm where the message to be signed is the effect hash of the entire transaction (described below), and the decaf377-rdsa domain is SpendAuth.

Effect Hash

The effect hash is computed over the effecting data of the transaction, which following the terminology used in Zcash1:

“Effecting data” is any data within a transaction that contributes to the effects of applying the transaction to the global state (results in previously-spendable coins or notes becoming spent, creates newly-spendable coins or notes, causes the root of a commitment tree to change, etc.).

The data that is not effecting data is authorizing data:

“Authorizing data” is the rest of the data within a transaction. It does not contribute to the effects of the transaction on global state, but allows those effects to take place. This data can be changed arbitrarily without resulting in a different transaction (but the changes may alter whether the transaction is allowed to be applied or not).

For example, the nullifier on a Spend is effecting data, whereas the proofs or signatures associated with the Spend are authorizing data.

In Penumbra, the effect hash of each transaction is computed using the BLAKE2b-512 hash function. The effect hash is derived from the proto-encoding of the action - in cases where the effecting data and authorizing data are the same, or the body of the action - in cases where the effecting data and authorizing data are different. Each proto has a unique string associated with it which we call its Type URL, which is included in the inputs to BLAKE2b-512. Type URLs are variable length, so a fixed-length field (8 bytes) is first included in the hash to denote the length of the Type URL field.

Summarizing the above, the effect hash for each action is computed as:

effect_hash = BLAKE2b-512(len(type_url) || type_url || proto_encode(proto))

where type_url is the bytes of the variable-length type URL, len(type_url) is the length of the type URL encoded as 8 bytes in little-endian byte order, proto represents the proto used to represent the effecting data, and proto_encode represents encoding the proto message as a vector of bytes.

Per-Action Effect Hashes

On a per-action basis, the effect hash is computed using the following labels and protos representing the effecting data in Penumbra:

ActionType URLProto
Spendb"/penumbra.core.transaction.v1alpha1.SpendBody"SpendBody
Outputb"/penumbra.core.transaction.v1alpha1.OutputBody"OutputBody
Ics20Withdrawalb"/penumbra.core.ibc.v1alpha1.Ics20Withdrawal"Ics20Withdrawal
Swapb"/penumbra.core.dex.v1alpha1.SwapBody"SwapBody
SwapClaimb"/penumbra.core.dex.v1alpha1.SwapClaimBody"SwapClaimBody
Delegateb"/penumbra.core.stake.v1alpha1.Delegate"Delegate
Undelegateb"/penumbra.core.stake.v1alpha1.Undelegate"Undelegate
UndelegateClaimb"/penumbra.core.stake.v1alpha1.UndelegateClaimBody"UndelegateClaimBody
Proposalb"/penumbra.core.governance.v1alpha1.Proposal"Proposal
ProposalSubmitb"/penumbra.core.governance.v1alpha1.ProposalSubmit"ProposalSubmit
ProposalWithdrawb"/penumbra.core.governance.v1alpha1.ProposalWithdraw"ProposalWithdraw
ProposalDepositClaimb"/penumbra.core.governance.v1alpha1.ProposalDepositClaim"ProposalDepositClaim
Voteb"/penumbra.core.governance.v1alpha1.Vote"Vote
ValidatorVoteb"/penumbra.core.governance.v1alpha1.ValidatorVoteBody"ValidatorVoteBody
DelegatorVoteb"/penumbra.core.governance.v1alpha1.DelegatorVoteBody"DelegatorVoteBody
DaoDepositb"/penumbra.core.governance.v1alpha1.DaoDepositt"DaoDeposit
DaoOutputb"/penumbra.core.governance.v1alpha1.DaoOutput"DaoOutput
DaoSpendb"/penumbra.core.governance.v1alpha1.DaoSpend"DaoSpend
PositionOpenb"/penumbra.core.dex.v1alpha1.PositionOpen"PositionOpen
PositionCloseb"/penumbra.core.dex.v1alpha1.PositionClose"PositionClose
PositionWithdrawb"/penumbra.core.dex.v1alpha1.PositionWithdraw"PositionWithdraw
PositionRewardClaimb"/penumbra.core.dex.v1alpha1.PositionRewardClaim"PositionRewardClaim

Transaction Data Field Effect Hashes

We compute the transaction data field effect hashes in the same manner as actions:

FieldType URLProto
TransactionParametersb"/penumbra.core.transaction.v1alpha1.TransactionParameters"TransactionParameters
Feeb"/penumbra.core.crypto.v1alpha1.Fee"Fee
MemoCiphertextb"/penumbra.core.crypto.v1alpha1.MemoCiphertext"MemoCiphertext
DetectionDatab"/penumbra.core.transaction.v1alpha1.DetectionData"DetectionData

Transaction Effect Hash

To compute the effect hash of the entire transaction, we combine the hashes of the individual fields in the transaction body. First we include the fixed-sized effect hashes of the per-transaction data fields: the transaction parameters eh(tx_params), fee eh(fee), (optional) detection data eh(detection_data), and (optional) memo eh(memo) which are derived as described above. Then, we include the number of actions and the fixed-size effect hash of each action a_0 through a_j. Combining all fields:

effect_hash = BLAKE2b-512(len(type_url) || type_url || eh(tx_params) || eh(fee) || eh(memo) || eh(detection_data) || j || eh(a_0) || ... || eh(a_j))

where the type_url are the bytes /penumbra.core.transaction.v1alpha1.TransactionBody, and len(type_url) is the length of that string encoded as 8 bytes in little-endian byte order.

Binding Signature

The Binding signature is computed once on the transaction level. It is a signature over a hash of the authorizing data of that transaction, called the auth hash. The auth hash of each transaction is computed using the BLAKE2b-512 hash function over the proto-encoding of the entire TransactionBody.

The Binding signature is computed using the decaf377-rdsa Sign algorithm where the message to be signed is the auth hash as described above, and the decaf377-rdsa domain is Binding. The binding signing key is computed using the random blinding factors for each balance commitment.