Penumbra is a fully private proofofstake network and decentralized exchange for the Cosmos ecosystem.
Penumbra integrates privacy with proofofstake through a novel private delegation mechanism that provides staking derivatives, taxefficient staking, and onchain governance with private voting. Penumbra connects to the Cosmos ecosystem via IBC, acting as an ecosystemwide shielded pool and allowing private transactions in any IBCcompatible asset. Users can also swap these assets using ZSwap, a private decentralized exchange supporting sealedbid batch auctions and Uniswapv3style concentrated liquidity. Sealedbid batch auctions prevent frontrunning, provide better execution, and reveal only the net flow across a pair of assets in each block, and liquidity positions are created anonymously, allowing traders to approximate their desired trading function without revealing their individual beliefs about prices.
This website renders the workinprogress protocol specification for Penumbra.
Press s
or use the magnifying glass icon for fulltext search.
If you’re interested in technical discussion about the project, why not
 join the discord,
 check out the repo and issue tracker,
 view the roadmap goals,
 or follow the project on Twitter for updates.
Private Transactions
Penumbra records all value in a single multiasset shielded pool based on the Zcash Sapling design, but allows private transactions in any kind of IBC asset. Inbound IBC transfers shield value as it moves into the zone, while outbound IBC transfers unshield value.
Unlike Zcash, Penumbra has no notion of transparent transactions or a
transparent value pool; instead, inbound IBC transfers are analogous to t2z
Zcash transactions, outbound IBC transfers are analogous to z2t
Zcash
transactions, and the entire Cosmos ecosystem functions analogously to
Zcash’s transparent pool.
Unlike the Cosmos Hub or many other chains built on the Cosmos SDK, Penumbra has no notion of accounts. Only validators have any kind of longterm identity, and this identity is only used (in the context of transactions) for spending the validator’s commission.
Private Staking
In a proofofstake system like the Cosmos Hub, stakeholders delegate staking tokens by bonding them to validators. Validators participate in Tendermint consensus with voting power determined by their delegation size, and delegators receive staking rewards in exchange for taking on the risk of being penalized for validator misbehavior (slashing).
Integrating privacy and proof of stake poses significant challenges. If delegations are public, holders of the staking token must choose between privacy on the one hand and staking rewards and participation in consensus on the other hand. Because the majority of the stake will be bonded to validators, privacy becomes an uncommon, optin case. But if delegations are private, issuing staking rewards becomes very difficult, because the chain no longer knows the amount and duration of each address’ delegations.
Penumbra sidesteps this problem using a new mechanism that eliminates staking rewards entirely, treating unbonded and bonded stake as separate assets, with an epochvarying exchange rate that prices in what would be a staking reward in other systems. This mechanism ensures that all delegations to a particular validator are fungible, and can be represented by a single delegation token representing a share of that validator’s delegation pool, in effect a firstclass staking derivative. Although delegation fungibility is key to enabling privacy, as a side effect, delegators do not realize any income while their stake is bonded, only a capital gain (or loss) on unbonding.
The total amount of stake bonded to each validator is part of the public chain state and determines consensus weight, but the delegation tokens themselves are each just another token to be recorded in a multiasset shielded pool. This provides accountability for validators and privacy and flexibility for delegators, who can trade and transact with their delegation tokens just like they can with any other token.
It also provides an alternate perspective on the debate between fixedsupply and inflationbased rewards. Choosing the unbonded token as the numéraire, delegators are rewarded by inflation for taking on the risk of validator misbehavior, and the token supply grows over time. Choosing (a basket of) delegation tokens as the numéraire, nondelegators are punished by depreciation for not taking on any risk of misbehavior, and the token supply is fixed.
Private Governance
Like the Cosmos Hub, Penumbra supports onchain governance with delegated voting. Unlike the Cosmos Hub, Penumbra’s governance mechanism supports secret ballots. Penumbra users can anonymously propose votes by escrowing a deposit of bonded stake. Stakeholders vote by proving ownership of their bonded stake prior to the beginning of the voting period and encrypting their votes to a threshold key controlled by the validator set. Validators sum encrypted votes and decrypt only the perepoch totals.
Private DEX
Penumbra provides private batch swaps using ZSwap. ZSwap allows users to privately swap between any pair of assets. Individual swaps publically burn their input notes and privately mint their output notes. All swaps in each block are executed in a single batch. Users can also provide liquidity by anonymously creating concentrated liquidity positions. These positions reveal the amount of liquidity and the bounds in which it is concentrated, but are not otherwise linked to any identity, so that (with some care) users can privately approximate arbitrary trading functions without revealing their specific views about prices.
Concepts and Mechanisms
This section provides an overview of the concepts involved in Penumbra’s mechanism design.
Validators
Validators in Penumbra undergo various transitions depending on chain activity.
┌────────────────────────────────────────────────────────────────────────────┐
│ │
│ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ │
│ Genesis Validator │ │
│ │ ┏━━━━━━━━━━━━━━━━━━━━━━━┓ │
│ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ ┃ Tombstoned ┃ │
│ │ ┌──────▶┃ (Misbehavior) ┃ │
│ │ │ ┗━━━━━━━━━━━━━━━━━━━━━━━┛ │
│ │ │ │
┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ │ ▼ │ ▼
Validator │ ┏━━━━━━━━━┓ ╔══════╗ │ ┏━━━━━━━━━━━┓
│ Definition ──────▶┃Inactive ┃─────────────▶║Active║───────┼────────────────────────────────┬─▶┃ Disabled ┃
(in transaction) │ ┗━━━━━━━━━┛ ╚══════╝ │ │ ┗━━━━━━━━━━━┛
└ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ▲ │ ┏━━━━━━━━━━━━━━━━━━━━━┓ │ │
│ └──────▶┃ Jailed (Inactivity) ┃──┘ │
│ ┗━━━━━━━━━━━━━━━━━━━━━┛ │
│ │ │
└─────────────────────────────────────────────────────┴──────────────────────┘
Single lines represent unbonded stake, and double lines represent bonded stake.
Validators become known to the chain either at genesis, or by means of a transaction with a ValidatorDefinition
action in them. Validators transition through five states:
 Inactive: a validator whose delegation pool is too small to participate in consensus set
 Active: a validator whose delegation pool is large enough to participate in consensus and must meet uptime requirements
 Jailed: a validator that has been slashed and removed from consensus for downtime, that may return later
 Tombstoned: a validator that has been permanently slashed and removed from consensus for byzantine misbehavior and may not return
 Disabled: a validator that has been manually disabled by the operator, that may return to
Inactive
later
Validators specified in the genesis config begin in the active state, with whatever stake was allocated to their delegation pool at genesis. Otherwise, new validators begin in the inactive state, with no stake in their delegation pool. At this point, the validator is known to the chain, and stake can be contributed to its delegation pool. Stake contributed to an inactive validator’s delegation pool does not earn rewards (the validator’s rates are held constant), but it is also not bonded, so undelegations are effective immediately, with no unbonding period and no output quarantine.
The chain chooses a validator limit N as a consensus parameter. When a validator’s delegation pool (a) has a nonzero balance and (b) its (votingpoweradjusted) size is in the top N validators, it moves into the active state during the next epoch transition. Active validators participate in consensus, and are communicated to Tendermint. Stake contributed to an active validator’s delegation pool earns rewards (the validator’s rates are updated at each epoch to track the rewards accruing to the pool). That stake is bonded, so undelegations have an unbonding period and an output quarantine. An active validator can exit the consensus set in four ways.
First, the validator could be jailed and slashed for inactivity. This can happen in any block, triggering an unscheduled epoch transition. Jailed validators are immediately removed from the consensus set. The validator’s rates are updated to price in the slashing penalty, and are then held constant. Validators jailed for inactivity are not permanently prohibited from participation in consensus, and their operators can reactivate them by reuploading the validator definition. Stake cannot be delegated to a slashed validator. Stake already contributed to a slashed validator’s delegation pool will enter an unbonding period to hold the validator accountable for any byzantine behavior during the unbonding period. Redelegations may occur after the validator enters the “Inactive” state.
Second, the validator could be tombstoned and slashed for byzantine misbehavior. This can happen in any block, triggering an unscheduled epoch transition. Tombstoned validators are immediately removed from the consensus set. Any pending undelegations from a slashed validator are cancelled: the quarantined output notes are deleted, and the quarantined nullifiers are removed from the nullifier set. The validator’s rates are updated to price in the slashing penalty, and are then held constant. Tombstoned validators are permanently prohibited from participation in consensus (though their operators can create new identity keys, if they’d like to). Stake cannot be delegated to a tombstoned validator. Stake already contributed to a tombstoned validator’s delegation pool is not bonded (the validator has already been slashed and tombstoned), so undelegations are effective immediately, with no unbonding period and no quarantine.
Third, the validator could be manually disabled by the operator. The validator is then in the disabled state. It does not participate in consensus, and the stake in its delegation pool does not earn rewards (the validator’s rates are held constant). The stake in its delegation pool will enter an unbonding period at the time the validator becomes disabled. The only valid state a disabled validator may enter into is “inactive”, if the operator reactivates it by updating the validator definition.
Fourth, the validator could be displaced from the validator set by another validator with more stake in its delegation pool. The validator is then in the inactive state. It does not participate in consensus, and the stake in its delegation pool does not earn rewards (the validator’s rates are held constant). The stake in its delegation pool will enter an unbonding period at the time the validator becomes inactive. Inactive validators have three possible state transitions:
 they can become active again, if new delegations boost its weight back into the top N;
 they can be tombstoned, if evidence of misbehavior arises during the unbonding period;
 they can be disabled, if the operator chooses.
If (2) occurs, the same state transitions as in regular tombstoning occur: all pending undelegations are cancelled, etc. If (3) occurs, the unbonding period continues and the validator enters the disabled state. If (1) occurs, the validator stops unbonding, and all delegations become bonded again.
Batching Flows
Penumbra’s ledger records value as it moves between different economic roles – for instance, movement between unbonded stake and delegation tokens, movement between different assets as they are traded, etc. This creates a tension between the need to reveal the total amount of value in each role as part of the public chain state, and the desire to shield value amounts in individual transactions.
To address this tension, Penumbra provides a mechanism to aggregate value flows across a batch of transactions, revealing only the total amount and not the value contributed by each individual transaction. This mechanism is built using an integervalued homomorphic encryption scheme that supports threshold decryption, so that the network’s validators can jointly control a decryption key.
Transactions that contribute value to a batch contain an encryption of the amount. To flush the batch, the validators sum the ciphertexts from all relevant transactions to compute an encryption of the batch total, then jointly decrypt it and commit it to the chain.
This mechanism doesn’t require any coordination between the users whose transactions are batched, but it does require that the validators create and publish a threshold decryption key. To allow batching across block boundaries, Penumbra organizes blocks into epochs, and applies changes to the validator set only at epoch boundaries. Decryption keys live for the duration of the epoch, allowing value flows to be batched over any time interval from 1 block up to the length of an epoch. We propose epoch boundaries on the order of 13 days.
At the beginning of each epoch, the validator set performs distributed key generation to produce a decryption key jointly controlled by the validators (on an approximately stakeweighted basis) and includes the encryption key in the first block of the epoch.
Because this key is only available after the first block of each epoch, some transactions cannot occur in the first block itself. Assuming a block interval similar to the Cosmos Hub, this implies an ~8second processing delay once per day, a reasonable tradeoff against the complexity of phased setup procedures.
Addresses and Keys
Value transferred on Penumbra is sent to shielded payment addresses; these addresses are derived from spending keys through a sequence of intermediate keys that represent different levels of attenuated capability:
flowchart BT subgraph Address end; subgraph DTK[Detection Key] end; subgraph IVK[Incoming Viewing Key] end; subgraph OVK[Outgoing Viewing Key] end; subgraph FVK[Full Viewing Key] end; subgraph SK[Spending Key] end; subgraph SeedPhrase[Seed Phrase] end; index(address index); div{ }; SeedPhrase > SK; SK > FVK; FVK > OVK; FVK > IVK; index > div; IVK > div; div > Address & DTK; DTK > Address;
From bottom to top:
 the seed phrase is the root key material. Multiple wallets  each with separate spend authority  can be derived from this root seed phrase.
 the spending key is the capability representing spending authority for a given wallet;
 the full viewing key represents the capability to view all transactions related to the wallet;
 the outgoing viewing key represents the capability to view only outgoing transactions, and is used to recover information about previously sent transactions;
 the incoming viewing key represents the capability to view only incoming transactions, and is used to scan the block chain for incoming transactions.
Penumbra allows the same account to present multiple, publicly unlinkable addresses, keyed by an 16byte address index. Each choice of address index gives a distinct shielded payment address. Because these addresses share a common incoming viewing key, the cost of scanning the blockchain does not increase with the number of addresses in use.
Finally, Penumbra also allows outsourcing probabilistic transaction detection to third parties using fuzzy message detection. Each address has a detection key; a third party can use this key to detect transactions that might be relevant to that key. Like a Bloom filter, this detection has false positives but no false negatives, so detection will find all relevant transactions, as well as some amount of unrelated cover traffic. Unlike incoming viewing keys, detection keys are not shared between diversified addresses, allowing finegrained control of delegation.
This diagram shows only the uservisible parts of the key hierarchy. Internally, each of these keys has different components, described in detail in the Addresses and Keys chapter.
Privacy Implications
Users should be aware that giving out multiple detection keys to a detection entity can carry a subset of the privacy implications, described in Addresses and Detection Keys.
Assets and Amounts
Penumbra’s shielded pool can record arbitrary assets. To be precise, we define:
 an amount to be an untyped quantity of some asset;
 an asset type to be an ADR001style denomination trace uniquely identifying a crosschain asset and its provenance, such as:
denom
(native chain A asset)transfer/channelToA/denom
(chain B representation of chain A asset)transfer/channelToB/transfer/channelToA/denom
(chain C representation of chain B representation of chain A asset)
 an asset ID to be a fixedsize hash of an asset type;
 a value to be a typed quantity, i.e., an amount and an asset id.
Penumbra deviates slightly from ADR001 in the definition of the asset ID. While ADR001 defines the IBC asset ID as the SHA256 hash of the denomination trace, Penumbra hashes to a field element, so that asset IDs can be more easily used inside of a zkSNARK circuit.
Notes, Nullifiers, and Trees
Transparent blockchains operate as follows: all participants maintain a copy of the (consensusdetermined) application state. Transactions modify the application state directly, and participants check that the state changes are allowed by the application rules before coming to consensus on them.
On a shielded blockchain like Penumbra, however, the state is fragmented across all users of the application, as each user has a view only of their “local” portion of the application state recording their funds. Transactions update a user’s state fragments privately, and use a zeroknowledge proof to prove to all other participants that the update was allowed by the application rules.
Penumbra’s transaction model is derived from the Zcash shielded transaction design, with modifications to support multiple asset types, and several extensions to support additional functionality. The Zcash model is in turn derived from Bitcoin’s unspent transaction output (UTXO) model, in which value is recorded in transaction outputs that record the conditions under which they can be spent.
In Penumbra, value is recorded in notes, which function similarly to UTXOs. Each note specifies (either directly or indirectly) a type of value, an amount of value of that type, a spending key that authorizes spending the note’s value, and a unique nullifier derived from the note’s contents.
However, unlike UTXOs, notes are not recorded as part of the public chain state. Instead, the chain contains a state commitment tree, an incremental Merkle tree containing (public) commitments to (private) notes. Creating a note involves creating a new note commitment, and proving that it commits to a valid note. Spending a note involves proving that the spent note was previously included in the state commitment tree, using the spending key to demonstrate spend authorization, and revealing the nullifier, which prevents the same note from being spent twice. Nullifiers that correspond to each spent note are tracked in the nullifier set, which is recorded as part of the public chain state.
Transactions
Transactions describe an atomic collection of changes to the ledger state. Each transaction consists of a sequence of descriptions for various actions^{1}. Each description adds or subtracts (typed) value from the transaction’s value balance, which must net to zero.
Penumbra adapts Sapling’s Spend, which spends a note and adds to the transaction’s value balance, and Output, which creates a new note and subtracts from the transaction’s value balance. Penumbra also adds many new descriptions to support additional functionality:
Transfers

Spend descriptions spend an existing note, adding its value to the transaction’s value balance;

Output descriptions create a new note, subtracting its value from the transaction’s value balance;

Transfer descriptions transfer value out of Penumbra by IBC, consuming value from the transaction’s value balance, and producing an ICS20
FungibleTokenPacketData
for the counterparty chain;
Staking

Delegate descriptions deposit unbonded stake into a validator’s delegation pool, consuming unbonded stake from the transaction’s value balance and producing new notes recording delegation tokens representing the appropriate share of the validator’s delegation pool;

Undelegate descriptions withdraw from a validator’s delegation pool, consuming delegation tokens from the transaction’s value balance and producing new notes recording the appropriate amount of unbonded stake;
Governance

CreateProposal descriptions are used to propose measures for onchain governance and supply a deposit, consuming bonded stake from the transaction’s value balance and producing a new note that holds the deposit in escrow;

WithdrawProposal descriptions redeem an escrowed proposal deposit, returning it to the transaction’s value balance and immediately withdrawing the proposal.

Vote descriptions perform private voting for onchain governance and declare a vote. This description leaves the value balance unchanged.
Trading

Swap descriptions perform the first phase of a swap, consuming tokens of one type from a transaction’s value balance, burning them, and producing a swap commitment for use in the second stage;

SwapClaim descriptions perform the second phase of a swap, allowing a user who burned tokens of one type to mint tokens of the other type at the chainspecified clearing price, and adding the new tokens to a transaction’s value balance;
Marketmaking

OpenPosition descriptions open concentrated liquidity positions, consuming value of the traded types from the transaction’s value balance and adding the specified position to the AMM state;

ClosePosition descriptions close concentrated liquidity positions, removing the specified position to the AMM state and adding the value of the position, plus any accumulated fees or liquidity rewards, to the transaction’s value balance.
Each transaction also contains a fee specification, which is always transparently encoded. The value balance of all of a transactions actions, together with the fees, must net to zero.
Note that like Zcash Orchard, we use the term “action” to refer to one of a number of possible state updates; unlike Orchard, we do not attempt to conceal which types of state updates are performed, so our Action is an enum.
Governance
Penumbra supports onchain governance with delegated voting. Validators’ votes are public and act as default votes for their entire delegation pool, while delegators’ votes are private, and override the default vote provided by their validator.
Votes are similar to Cosmos Hub: Yes
, No
, and Abstain
have the same meanings.
However, Penumbra does not have NoWithVeto
; instead, a sufficiently high threshold
of “no” votes (determined by a chain parameter) will slash a proposal, which causes
its deposit to be burned. This functions as a spam deterrent.
Proposals
Penumbra users can propose votes by escrowing a minimum amount of PEN
. They
do this by creating a transaction with a ProposalSubmit
description, which
consumes some amount of PEN
from the transaction’s balance, and creates a
proposal_N_voting
NFT, which can be redeemed for the proposal deposit in the case
that the voting concludes without slashing the proposal (both failed and passed
proposals can reclaim their deposit).
Proposals can either be normal or emergency proposals. In either case, the voting period begins immediately, in the next block after the proposal has been committed to the chain. Normal proposals have a fixedlength voting period, while emergency proposals are accepted as soon as a 1/3 majority of the stake is reached.
Because validators provide default votes for their delegation pool, an emergency proposal can in principle be accepted immediately, without any input from delegators. This allows timecritical resolution of emergencies (e.g., deploying an 0day hotfix); a 1/3 + 1 plurality of the stake required is already sufficient to halt the chain.
Proposals can also be withdrawn by their proposer prior to the end of the voting
period. This is done by creating a transaction with a ProposalWithdraw
description, and allows the community to iterate on proposals as the (social)
governance process occurs. For instance, a chain upgrade proposal can be
withdrawn and reproposed with a different source hash if a bug is discovered
while upgrade voting is underway. Withdrawn proposals cannot be accepted, even
if the vote would have passed, but they can be slashed.^{1}
Voting
Stakeholder votes are of the form $(x_{y},x_{n},x_{a})$, representing the weights for yes, no, and abstain, respectively. Most stakeholders would presumably set all but one weight to $0$. Stakeholders vote by proving ownership of some amount of bonded stake (their voting power) prior to the beginning of the voting period.
To do this, they create a transaction with a Vote
description. This
description identifies the validator $v$ and the proposal, proves spend
authority over a note recording $y$ dPEN(v)
, and reveals the note’s nullifier.
Finally, it proves vote consistency $y=x_{y}+x_{n}+x_{a}$, produces a new
note with $y$ dPEN(v)
, and includes $Enc_{D}(x_{i})$, an encryption
of the vote weights to the validators’ decryption key.
The proof statements in a Vote
description establishing spend authority over
the note are almost identical to those in a Spend
description. However, there
are two key differences. First, rather than checking the note’s
nullifier against the global nullifier set and marking it as spent, the
nullifier is checked against a snapshot of the nullifier set at the time that
voting began (establishing that it was unspent then), as well as against a
perproposal nullifier set (establishing that it has not already been used for
voting). In other words, instead of marking that the note has been spent in
general, we only mark it as having been spent in the context of voting on a
specific proposal. Second, the ZK proof additionally proves that the note was
created before the proposal started, and the stateful checks ensure that it was not
spent after the proposal started.
This change allows multiple proposals to be voted on concurrently, at the cost
of linkability. While the same note can be used to vote on multiple proposals,
those votes, as well as the subsequent spend of the note, will have the same
nullifier and thus be linkable to each other. However, the Vote
descriptions
are shielded, so an observer only learns that two opaque votes were related to
each other.
We suggest that wallets roll over the note the first time it is used for voting
by creating a transaction with Vote
, Spend
, and Output
descriptions. This
mitigates linkability between Vote
and Spend
descriptions, and means that
votes on any proposals created after the first vote are unlinkable from prior
votes.
Counting Votes
At the end of each epoch, validators collect the encrypted votes from each delegation pool, aggregate the encrypted votes into encrypted tallies and decrypt the tallies. These intermediate tallies are revealed, because it is not possible to batch value flows over time intervals longer than one epoch. In practice, this provides a similar dynamic as existing (transparent) onchain governance schemes, where tallies are public while voting is ongoing.
At the end of the voting period, the perepoch tallies are summed. For each validator $v$, the votes for each option are summed to determine the portion of the delegation pool that voted; the validator’s vote acts as the default vote for the rest of the delegation pool. Finally, these pervalidator subtotals are multiplied by the voting power adjustment function $θ_{v}(e)$ to obtain the final vote totals.
If the vote was not slashed, the proposal_N_voting
NFT created during proposal
submission can be redeemed for the original proposal deposit (if not slashed)
and a commemorative proposal_N_passed
, proposal_N_failed
, or proposal_N_slashed
NFT.
If withdrawing a proposal halted onchain voting immediately, the escrow mechanism would not be effective at deterring spam, since the proposer could yank their proposal at the last minute prior to losing their deposit. However, at the UX level, withdrawn proposals can be presented as though voting were closed, since validators’ default votes are probably sufficient for spam deterrence.
Primitives
Penumbra uses the following cryptographic primitives, described in the following sections:

The Proof System section describes the choice of proving curve (BLS12377) and proof system (Groth16, and potentially PLONK in the future);

The
decaf377
section describesdecaf377
, a parameterization of the Decaf construction defined over the BLS12377 scalar field, providing a primeorder group that can be used inside or outside of a circuit; 
The Poseidon for BLS12377 section describes parameter selection for an instantiation of Poseidon, a SNARKfriendly sponge construction, over the BLS12377 scalar field;

The Fuzzy Message Detection section describes a construction that allows users to outsource a probabilistic detection capability, allowing a third party to scan and filter the chain on their behalf, without revealing precisely which transactions are theirs.

The Homomorphic Threshold Decryption section describes the construction used to batch flows of value across transactions.

The Randomizable Signatures section describes
decaf377rdsa
, a variant of the Zcash RedDSA construction instantiated overdecaf377
, used for binding and spend authorization signatures. 
The Key Agreement section describes
decaf377ka
, an instantiation of DiffieHellman key agreement overdecaf377
.
Proving Considerations
Penumbra needs SNARK proofs. Because the choice of proving system and proving curve can’t really be cleanly separated from the rest of the system choices (e.g., the native field of the proving system informs what embedded curve is available, and how circuit programming is done), large parts of the rest of the system design block on making a choice of proving system.
Goals

Nearterm implementation availability. We’d like to ship a useful product first, and iterate and expand it later.

High performance for fixed functionality. Penumbra intends to support fixed functionality initially; programmability is a good future goal but isn’t a nearterm objective. The fixed functionality should have as high performance as possible.

Longerterm flexibility. The choice should ideally not preclude many future choices for later functionality. More precisely, it should not impose high switching costs on future choices.

Recursion capability. Penumbra doesn’t currently make use of recursion, but there are a lot of interesting applications it could be used for.
Setup ceremonies are beneficial to avoid for operational reasons, but not for security reasons. A decentralized setup procedure is sufficient for security.
Options
Proof systems:
 Groth16:
 Pros: high performance, very small proofs, mature system
 Cons: requires a setup for each proof statement
 PLONK:
 Pros: universal setup, still fairly compact proofs, seems to be a point of convergence with useful extensions (plookup, SHPLONK, etc)
 Cons: bigger proofs, worse constants than Groth16
 Halo 2
 Pros: no setup, arbitrary depth recursion
 Cons: bigger proof sizes, primary implementation for the Pallas/Vesta curves which don’t support pairings
Curve choices:

BLS12381:
 Pros: very mature, used by Sapling already
 Cons: no easy recursion

BLS12377:
 Pros: constructed as part of Zexe to support depth 1 recursion using a bigger parent curve, deployed in Celo, to be deployed in Zexe
 Cons: ?

Pallas/Vesta:
 Pros: none other than support for Halo 2’s arbitrary recursion
 Cons: no pairings mean they cannot be used for any pairingbased SNARK
Considerations
Although the choice of proof system (Groth16, Plonk, Halo, Pickles, …) is not completely separable from the choice of proving curve (e.g., pairingbased SNARKs require pairingfriendly curves), to the extent that it is, the choice of the proof system is relatively less important than the choice of proving curve, because it is easier to encapsulate.
The choice of proving curve determines the scalar field of the arithmetic circuit, which determines which curves are efficient to implement in the circuit, which determines which cryptographic constructions can be performed in the circuit, which determines what kind of key material the system uses, which propagates all the way upwards to uservisible details like the address format. While swapping out a proof system using the same proving curve can be encapsulated within an update to a client library, swapping out the proving curve is extremely disruptive and essentially requires all users to generate new addresses and migrate funds.
This means that, in terms of proof system flexibility, the Pallas/Vesta curves are relatively disadvantaged compared to pairingfriendly curves like BLS12381 or BLS12377, because they cannot be used with any pairingbased SNARK, or any other pairingbased construction. Realistically, choosing them is committing to using Halo 2.
Choosing BLS12377 instead of BLS12381 opens the possibility to do depth1 recursion later, without meaningfully restricting the nearterm proving choices. For this reason, BLS12377 seems like the best choice of proving curve.
Penumbra’s approach is to first create a useful set of fixed functionality, and
generalize to custom, programmable functionality only later. Compared to
Sapling, there is more functionality (not just Spend
and Output
but
Delegate
, Undelegate
, Vote
, …), meaning that there are more proof
statements. Using Groth16 means that each of these statements needs to have its
own proving and verification key, generated through a decentralized setup.
So the advantage of a universal setup (as in PLONK) over perstatement setup (as in Groth16) would be:
 The setup can be used for additional fixed functionality later;
 Client software does not need to maintain distinct proving/verification keys for each statement.
(2) is a definite downside, but the impact is a little unclear. As a point of reference, the Sapling spend and output parameters are 48MB and 3.5MB respectively. The size of the spend circuit could be improved using a snarkfriendly hash function.
With regard to (1), if functionality were being developed in many independent pieces, doing many setups would impose a large operational cost. But doing a decentralized setup for a dozen proof statements simultaneously does not seem substantially worse than doing a decentralized setup for a single proof statement. So the operational concern is related to the frequency of groups of new statements, not the number of statements in a group. Adding a later group of functionality is easy if the first group used a universal setup. But if it didn’t, the choice of perstatement setup initially doesn’t prevent the use of a universal setup later, as long as the new proof system can be implemented using the same curve.
Because Penumbra plans to have an initial set of fixed functionality, and performance is a concern, Groth16 seems like a good choice, and leaves the door open for a future universal SNARK. Using BLS12377 opens the door to future recursion, albeit only of depth 1.
The decaf377
group
Penumbra, like many other zeroknowledge protocols, requires a cryptographic group that can be used inside of an arithmetic circuit. This is accomplished by defining an “embedded” elliptic curve whose base field is the scalar field of the proving curve used by the proof system.
The Zexe paper, which defined BLS12377, also defined (called $E_{Ed/BLS}$ in Figure 16 of the paper) a cofactor4 Edwards curve defined over the BLS12377 scalar field for exactly this purpose. However, nonprimeorder groups are a leaky abstraction, forcing all downstream constructions to pay attention to correct handling of the cofactor. Although it is usually possible to do so safely, it requires additional care, and the optimal technique for handling the cofactor is different inside and outside of a circuit.
Instead, applying the Decaf construction to this curve gives decaf377
, a
clean abstraction that provides a primeorder group complete with hashtogroup
functionality and whose encoding and decoding functions integrate validation.
Although it imposes a modest additional cost in the circuit context, as
discussed in Costs and Alternatives, the
construction works the same way inside and outside of a circuit and imposes no
costs for lightweight, softwareonly applications, making it a good choice for
generalpurpose applications.
Curve Parameters
The cofactor4 Edwards curve defined over the BLS12377 scalar field has the following parameters:
 Base field: Integers mod prime $q=8444461749428370424248824938781546531375899335154063827935233455917409239041$
 Elliptic curve equation: $ax_{2}+y_{2}=1+dx_{2}y_{2}$ with $a=−1$ and $d=3021$
 Curve order: $4r$ where $r=2111115437357092606062206234695386632838870926408408195193685246394721360383$
We use a conventional generator basepoint selected to have a convenient hex encoding:
0x0800000000000000000000000000000000000000000000000000000000000000
In affine coordinates this generator point has coordinates:
 $x=4959445789346820725352484487855828915252512307947624787834978378872129235627$
 $y=6060471950081851567114691557659790004756535011754163002297540472747064943288$
Implementation
An implementation of decaf377
can be found here.
Costs and Alternatives
Arithmetic circuits have a different cost model than software. In the software cost model, software executes machine instructions, but in the circuit cost model, relations are certified by constraints. Unfortunately, while Decaf is a clearly superior choice in the software context, in the circuit context it imposes some additional costs, which must be weighed against its benefits.
At a high level, Decaf implements a primeorder group using a nonprimeorder curve by constructing a group quotient. Internally, group elements are represented by curve points, with a custom equality check so that equivalent representatives are considered equal, an encoding function that encodes equivalent representatives as identical bitstrings, and a decoding function that only accepts canonical encodings of valid representatives.
To do this, the construction defines a canonical encoding on a Jacobi quartic curve mod its 2torsion (a subgroup of size 4) by making two independent sign choices. Then, it uses an isogeny to transport this encoding from the Jacobi quartic to a target curve that will be used to actually implement the group operations. This target curve can be an Edwards curve or a Montgomery curve. The isogenies are only used for deriving the construction. In implementations, all of these steps are collapsed into a single set of formulas that perform encoding and decoding on the target curve.
In other words, one way to think about the Decaf construction is as some machinery that transforms two sign choices into selection of a canonical representative. Ristretto adds extra machinery to handle cofactor 8 by making an additional sign choice.
In the software cost model, where software executes machine instructions, this construction is essentially free, because the cost of both the Decaf and conventional Edwards encodings are dominated by the cost of computing an inverse or an inverse square root, and the cost of the sign checks is insignificant.
However, in the circuit cost model, where relations are certified by various constraints, this is no longer the case. On the one hand, certifying a square root or an inverse just requires checking that $y_{2}=x$ or that $xy=1$, which is much cheaper than actually computing $y=x $ or $y=1/x$. On the other hand, performing a sign check involves bitconstraining a field element, requiring hundreds of constraints.
Sign checks
The definition of which finite field elements are considered nonnegative is essentially arbitrary. The Decaf paper suggests three possibilities:

using the least significant bit, defining $x$ to be nonnegative if the least absolute residue for $x$ is even;

using the most significant bit, defining $x$ to be nonnegative if the least absolute residue for $x$ is in the range $0≤x≤(q−1)/2$;

for fields where $q≡3(mod4)$, using the Legendre symbol, which distinguishes between square and nonsquare elements.
Using the Legendre symbol is very appealing in the circuit context, since it has an algebraic definition and, at least in the case of square elements, very efficient certification. For instance, if square elements are chosen to be nonnegative, then certifying that $x$ is nonnegative requires only one constraint, $x=y_{2}$. However, the reason for the restriction to $3(mod4)$ fields is that $1$ and $−1$ should have different signs, which can only be the case if $−1$ is nonsquare. Unfortunately, many SNARKfriendly curves, including BLS12377, are specifically chosen so that $q≡1(mod2_{α})$ for as large a power $α$ as possible (e.g., $α=47$ in the case of BLS12377).
This leaves us with either the LSB or MSB choices. The least significant bit is potentially simpler for implementations, since it is actually the low bit of the encoding of $x$, while the most significant bit isn’t, because it measures from $(q−1)/2$, not a bit position $2_{k}$, so it seems to require a comparison or range check to evaluate. However, these choices are basically equivalent, in the following sense:
Lemma.^{}1
The most significant bit of $x$ is $0$ if and only if the least significant bit of $2x$ is $0$.
Proof.
The MSB of $x$ is $0$ if and only if $2x≤q−1$, but this means that $2x$, which is even, is the least absolute residue, so the LSB of $2x$ is also $0$. On the other hand, the MSB of $x$ is $1$ if and only if $x>(q−1)/2$, i.e., if $x≥(q−1)/2+1$, i.e., if $2x≥q−1+2=q+1$. This means that the least absolute residue of $2x$ is $2x−q$; since $2x$ is even and $q$ is odd, this is odd and has LSB $1$. $□$
This means that transforming an LSB check to an MSB check or vice versa requires multiplication by $2$ or $1/2$, which costs at most one constraint.
Checking the MSB requires checking whether a value is in the range $[0,(q−1)/2]$. Using Daira Hopwood’s optimized range constraints, the range check costs $73C$^{2}. However, the input to the range check is a bitconstrained unpacking of a field element, not a field element itself. This unpacking costs $253C$.
Checking the LSB is no less expensive, because although the check only examines one bit, the circuit must certify that the bitencoding is canonical. This requires checking that the value is in the range $[0,q−1]$, which also costs $73C$, and as before, the unpacking costs $253C$.
In other words, checking the sign of a field element costs $253C+73C=326C$, or $76C$ in the case where the field element is already bitencoded for other reasons. These checks are the dominant cost for encoding and decoding, which both require two sign checks. Decoding from bits costs c. $73C+326C≅400C$, decoding from a field element costs c. $326C+326C≅750C$, and encoding costs c. $750C$ regardless of whether the output is encoded as bits or as a field element.
For decaf377
, we choose the LSB test for sign checks.
Alternative approaches to handling cofactors
Decaf constructs a primeorder group whose encoding and decoding methods perform validation. A more conventional alternative approach is to use the underlying elliptic curve directly, restrict to its primeorder subgroup, and do subgroup validation separately from encoding and decoding. If this validation is done correctly, it provides a primeorder group. However, because validation is an additional step, rather than an integrated part of the encoding and decoding methods, this approach is necessarily more brittle, because each implementation must be sure to do both steps.
In the software cost model, there is no reason to use subgroup validation, because it is both more expensive and more brittle than Decaf or Ristretto. However, in the circuit cost model, there are cheaper alternatives, previously analyzed by Daira Hopwood in the context of Ristretto for JubJub (1, 2).
Multiplication by the group order.
The first validation method is to do a scalar multiplication and check that $[q]P=1$. Because the prime order is fixed, this scalar multiplication can be performed more efficiently using a hardcoded sequence of additions and doublings.
Cofactor preimage.
The second validation method provides a preimage $Q=[1/4]P$ in affine coordinates $(x,y)$. Because the image of $[4]:E→E$ is the primeorder subgroup, checking that $Q$ satisfies the curve equation and that $P=[4]Q$ checks that $P$ is in the primeorder subgroup.
In the software context, computing $[1/4]P$ and computing $[q]P$ cost about the same, although both are an order of magnitude more expensive than decoding. But in the circuit context, the prover can compute $Q=[1/4]P$ outside of the circuit and use only a few constraints to check the curve equation and two doublings. These costs round to zero compared to sign checks, so the validation is almost free.
The standard “compressed Edwards y” format encodes a point $(x,y)$ by the $y$coordinate and a sign bit indicating whether $x$ is nonnegative. In software, the cost of encoding and decoding are about the same, and dominated by taking an inverse square root. In circuits, the costs of encoding and decoding are also about the same, but they are instead dominated by a sign check that matches the sign of the recovered $x$coordinate with the supplied sign bit. This costs c. $325C$ as above.
Comparison and discussion
This table considers only approximate costs.
Operation  decaf377  Compressed Ed + Preimage 

Decode (from bits)  400C  400C 
Decode (from $F_{q}$)  750C  325C 
Encode (to bits)  750C  750C 
Encode (to $F_{q}$)  750C  325C 
When decoding from or encoding to field elements, the marginal cost of Decaf compared to the compressed Edwards + cofactor preimage is an extra bitunpacking and range check. While this effectively doubles the number of constraints, the marginal cost of c. $325C$ is still small relative to other operations like a scalar multiplication, which at 6 constraints per bit is approximately $1500C$.
When decoding from or encoding to bits, the marginal cost of Decaf disappears. When the input is already bitconstrained, Decaf’s first sign check can reuse the bit constraints, saving c. $250C$, but the compressed Edwards encoding must rangecheck the bits (which Decaf already does), costing c. $75C$ extra. Similarly, in encoding, Decaf’s second sign check produces bitconstrained variables for free, while the compressed Edwards encoding spends c. $250C+75C$ bitconstraining and rangechecking them.
However, in the software context, the primeorder validation check costs approximately 10x more than the cost of either encoding. Many applications require use of the embedded group both inside and outside of the circuit, and uses outside of the circuit may have additional resource constraints (for instance, a hardware token creating a signature authorizing delegated proving, etc.).
Performing validation as an additional, optional step also poses additional risks. While a specification may require it to be performed, implementations that skip the check will appear to work fine, and the history of invalidpoint attacks (where implementations should, but don’t, check that point coordinates satisfy the curve equation) suggests that structuring validation as an integral part of encoding and decoding is a safer design. This may not be a concern for a specific application with a single, highquality implementation that doesn’t make mistakes, but it’s less desirable for a generalpurpose construction.
In summary, Decaf provides a construction that works the same way inside and outside of a circuit and integrates validation with the encoding, imposing only a modest cost for use in circuits and no costs for lightweight, softwareonly applications, making it a good choice for generalpurpose constructions.
I have no idea whether this is common knowledge; I learned of this fact from its use in Mike Hamburg’s Ed448Goldilocks implementation.
The value 73 is computed as:
from itertools import groupby
def cost(k):
return min(k1, 2)
def constraints(bound):
costs = [cost(len(list(g))+1) for (c, g) in groupby(bound.bits()[:1]) if c == 1]
return sum(costs)
constraints(ZZ((q1)/2))
as here.
Inverse Square Roots
As in the internetdraft, the decaf377
functions are defined in terms of the
following function, which computes the square root of a ratio of field elements,
with the special behavior that if the input is nonsquare, it returns the square
root of a related field element, to allow reuse of the computation in the
hashtogroup setting.
Define $ζ$ as a nonsquare in the field and sqrt_ratio_zeta(N,D)
as:
 (True, $DN $) if $N$ and $D$ are nonzero, and $DN $ is square;
 (True, $0$) if $N$ is zero;
 (False, $0$) if $D$ is zero and $N$ is nonzero;
 (False, $ζDN $) if $N$ and $D$ are nonzero, and $DN $ is nonsquare.
Since $ζ$ is nonsquare, if $DN $ is nonsquare, $ζDN $ is
square. Note that unlike the similar function in the
ristretto255
/decaf448
internetdraft, this function does not make any
claims about the sign of its output.
To compute sqrt_ratio_zeta
we use a tablebased method adapted from Sarkar 2020 and zcashpasta, which is described in the remainder of this section.
Constants
We set $n>1$ (the 2adicity of the field) and $m$ odd such that $p−1=2_{n}m$. For the BLS12377 scalar field, $n=47$ and $m=60001509534603559531609739528203892656505753216962260608619555$.
We define $g=ζ_{m}$ where $ζ$ is a nonsquare as described above. We fix $ζ$ as 2841681278031794617739547238867782961338435681360110683443920362658525667816.
We then define a $k≥1$ and $ℓ_{0},…,ℓ_{k−1}>0$ such that $ℓ_{0}+…+ℓ_{k−1}=n−1$. We also define a parameter $w$ where $1≤w≤max(ℓ_{0},ℓ_{1},…,ℓ_{k−1})$. For decaf377 we choose:
 $k=6$
 $ℓ_{0,1}=7$
 $ℓ_{2,3,4,5}=8$
 $w=8$
Precomputation
Lookup tables are needed which can be precomputed as they depend only on the 2adicity $n$ and the choice of $ℓ_{i}$ above.
$g$ lookup table: $g_{ν2_{iw}}$
We compute $g_{ν2_{iw}}$ for $ν∈1,…,2_{w}−1$ and $i∈0,…,k−1$, indexed on $x$ and $ν$:
$ gg_{2_{8}}⋮g_{2_{40}} g_{2}g_{2_{9}}⋮g_{2_{41}} ……⋱… g_{2_{8}−1}(g_{2_{8}})_{2_{8}−1}⋮(g_{2_{40}})_{2_{8}−1} $
This table lets us look up powers of $g$.
$s$ lookup table: $g_{−ν(2_{n−w})}$
We compute $g_{−ν(2_{n−w})}$ for $ν∈0,…,2_{w}−1$, indexed on $g_{−ν(2_{n−w})}$:
$(g_{0} g_{−2_{39}} (g_{−2_{39}})_{2} … (g_{−2_{39}})_{2_{8}−1} )$
We use this table in the procedure that follows to find $q_{i}$ (they are the $ν$ values) in order to compute $s_{i}$.
Procedure
In the following procedure, let $u=N/D$. We use the following relations from Sarkar 2020:
 Equation 1: $α_{i}=x_{i}g_{t_{i}}$ and $t_{i}=(t_{i−1}+s_{i−1})/2_{ℓ_{i}}$ for $i∈0,…,k−1$ and $t_{k}=t_{k−1}+s_{k−1}$
 Lemma 3: $α_{i}g_{s_{i}}=1$ for $i∈0,…,k−1$
 Equation 2: $s_{i}=q_{i}2_{n−l_{i}}$
In these expressions, $x_{i}$ and $α_{i}$ are field elements. $q_{i}$ are unsigned $ℓ_{i}$bit integers. At each $i$, the algorithm first computes $x_{i}$, then $t_{i}$ and $α_{i}$ (from the previous step’s $t_{i−1}$ and $s_{i−1}$), then finally $s_{i}$ and $q_{i}$, in each case such that they satisfy the above expressions. Note that in the algorithm $t_{0}=0$.
Step 1: Compute $v=u_{2m−1}$
We compute $v=u_{2m−1}$. This corresponds to line 2 of the findSqRoot
Algorithm 1 in Sarkar 2020.
Substituting $u=N/D$:
$v=(DN )_{2m−1}=N_{2m−1}D_{−2m−1}$
Applying Fermat’s Little Theorem (i.e. $D_{p−1}=1modp$):
$v=N_{2m−1}D_{p−1−2m−1}$
Substituting $p−1=2_{n}m$ and rearranging:
$v=N_{2m−1}D_{2_{n}m−2m−1}$
$v=N_{2m−1}D_{21(2_{n+1}m−m−1)}$
$v=N_{2m−1}D_{21(2_{n+1}m−m−1−2_{n+1}+2_{n+1})}$
$v=N_{2m−1}D_{21(2_{n+1}−1)(m−1)+2_{n}}$
$v=N_{2m−1}D_{21(2_{n+1}−1)(m−1)}D_{2_{n}}$
We compute $v$ using a quantity $w$ we define as:
$w=(ND_{2_{n+1}−1})_{(m−1)/2}D_{2_{n}−1}$
We also define:
$s=D_{2_{n}−1}$
$t=D_{2_{n+1}−1}=Ds_{2}$
And substitute $s$ and $t$ into $w$ which gives us:
$w=s(Nt)_{2m−1}$
We now can use $w$ in the computation for $v$ and $uv$:
$v=wD$
$uv=wN$
Step 2: Compute $x$
Compute $x=(uv)v$ using $v$ and $uv$ as calculated in the prior step. This corresponds to line 4 of the findSqRoot
Algorithm 1 in Sarkar 2020.
Step 3: Compute $x_{i}$
We next compute $x_{i}=x_{2_{n−1−(ℓ+…+ℓ)}}$ for $i=0,..,k−1$. This corresponds to line 5 of the findSqRoot
Algorithm 1 in Sarkar 2020. This gives us the following components:
$x_{0}=x_{2_{n−1−ℓ}}=x_{2_{39}}=x_{1}$
$x_{1}=x_{2_{n−1−(ℓ+ℓ)}}=x_{2_{32}}=x_{2}$
$x_{2}=x_{2_{24}}=x_{3}$
$x_{3}=x_{2_{16}}=x_{4}$
$x_{4}=x_{2_{8}}=x_{5}$
$x_{5}=x_{2_{0}}=x$
Step 4: Compute $s_{i}$ and $t_{i}$
Next, we loop over $k$. This corresponds to lines 69 of the findSqRoot
Algorithm 1 in Sarkar 2020.
For $i=0$
Using Lemma 3:
$α_{0}g_{s_{0}}=1$
Substituting the definition of $α_{0}$ from equation 1:
$x_{0}g_{t_{0}}g_{s_{0}}=1$
Rearranging and substituting in $t_{0}=0$ (initial condition):
$x_{0}=g_{−s_{0}}$
Substituting in equation 2:
$x_{0}=g_{−q_{0}2_{n−ℓ}}=g_{−q_{0}2_{40}}$
This is almost in a form where we can look up $q_{0}$ in our s lookup table to get $q_{0}$ and thus $s_{0}$. If we define $q_{0}=2q_{0}$ we get:
$x_{0}=g_{−q_{0}2_{39}}$
Which we can use with our s lookup table to get $q_{0}$. Expressing $s_{0}$ in terms of $q_{0}$, we get $s_{0}=q_{0}2_{39}$.
For $i=1$
First we compute $t_{1}$ using equation 1:
$t_{1}=(t_{0}+s_{0})/2_{ℓ_{1}}=(t_{0}+s_{0})/2_{7}=q_{0}2_{32}$
Next, similar to the first iteration, we use lemma 3 and substitute in $t_{1}$ and $s_{1}$ to yield:
$α_{1}g_{s_{1}}=1$
$x_{1}g_{q_{0}2_{32}}=g_{−q_{1}2_{39}}$
In this expression we can compute the quantities on the left hand side, and the right hand side is in the form we expect for the s lookup table, yielding us $q_{1}$. Note that here too we define $q_{1}=2q_{1}$ such that the s lookup table can be used. Expressing $s_{1}$ in terms of $q_{1}$, we get $s_{1}=q_{1}2_{39}$.
For $i=2,…,5$
The remaining iterations proceed similarly, yielding the following expressions:
$t_{2}=q_{0}2_{24}+q_{1}2_{31}$
$s_{2}=q_{2}2_{39}$
Note for $q_{2}$ and the remaining iterations we do not require a trick (i.e. where $q_{i}=2q_{i}$) to get $s_{2}$ in a form where it can be used with the s lookup table. In the following expressions for $t_{i}$, $q_{1}$ is always even, and so the high bit of each value is unchanged when adding $q_{1}$.
$t_{3}=q_{0}2_{16}+q_{1}2_{23}+q_{2}2_{31}$
$s_{3}=q_{3}2_{39}$
$t_{4}=q_{0}2_{8}+q_{1}2_{15}+q_{2}2_{23}+q_{3}2_{31}$
$s_{4}=q_{4}2_{39}$
$t_{5}=q_{0}+q_{1}2_{7}+q_{2}2_{15}+q_{3}2_{23}+q_{4}2_{31}$
$s_{5}=q_{5}2_{39}$
At the end of this step, we have found $s_{i}$ and $t_{i}$ for $i∈0,…,k−1$.
Step 5: Return result $y$
Next, we can use equation 1 to compute $t=t_{5}+s_{5}$ using $t_{5}$ and $s_{5}$ from the previous step:
$t=q_{0}+q_{1}2_{7}+q_{2}2_{15}+q_{3}2_{23}+q_{4}2_{31}+q_{5}2_{39}$
This matches the expression from Lemma 4 in Sarkar 2020.
Next, to compute $g_{t/2}$, we lookup entries in the g lookup table. To do so, we can decompose $t/2$ into:
$t/2=q_{0}2_{0}+q_{1}2_{8}+q_{2}2_{16}+q_{3}2_{24}+q_{4}2_{32}+q_{5}2_{40}$
then $g_{t/2}$ is computed as:
$g_{t/2}=g_{q_{0}2_{0}}g_{q_{1}2_{8}}g_{q_{2}2_{16}}g_{q_{3}2_{24}}g_{q_{4}2_{32}}g_{q_{5}2_{40}}$
Multiplying in $uv$ from step 1, we compute:
$y=uvg_{t/2}$
This corresponds to line 10 of the findSqRoot
Algorithm 1 in Sarkar 2020.
In the nonsquare case, $q_{0}$ will be odd, and $t$ will be odd. We will have computed $y=(N/D)g $ and multiply $y$ by a correction $ζ/g $ to get our desired output.
We can use the result of this computation $y$ to determine whether or not the square exists, recalling from Step 1 that $u=N/D$:
 If $u$ is square, then $y=N/D $, and $y_{2}D−N=0$
 If $u$ is nonsquare, then $y=ζN/D $ and $y_{2}D−ζN=0$.
Decoding
Decoding to a point works as follows where $a$ and $d$ are the curve parameters as described here.

Decode
s_bytes
to a field element $s$. We interpret these bytes as unsigned littleendian bytes. We check if the length has 32 bytes, where the top 3 bits of the last byte are 0. The 32 bytes are verified to be canonical, and rejected if not (if the input is already a field element in the circuit case, skip this step). 
Check that $s$ is nonnegative, or reject (sign check 1).

$u_{1}←1+as_{2}$.

$u_{2}←u_{1}−4ds_{2}$.

(was_square, v) = sqrt_ratio_zeta(1, u_2 * u_1^2)
, rejecting ifwas_square
is false. 
$v←−v$ if $2su_{1}v$ is negative (sign check 2)^{1}.

$(x,y)←(2sv_{2}u_{1}u_{2},(1−as_{2})vu_{1})$.
The resulting coordinates are the affine Edwards coordinates of an internal representative of the group element.
Note this differs from the Decaf paper in Appendix A.2, but
implementations of decaf377
should follow the convention described here.
Encoding
Given a representative in extended coordinates $(X,Y,Z,T)$, encoding works as follows where $a$ and $d$ are the curve parameters as described here.

$u_{1}←(X+T)(X−T)$.

(_ignored, v) = sqrt_ratio_zeta(1, u_1 * (a  d) * X^2)
. 
$u_{2}←∣vu_{1}∣$ (sign check 1).

$u_{3}←u_{2}Z−T$.

$s←∣(a−d)vu_{3}X∣$.

Set
s_bytes
to be the canonical unsigned littleendian encoding of $s$, which is an integer mod $q$.s_bytes
has extra0x00
bytes appended to reach a length of 32 bytes.
Group Hash
Elligator can be applied to map a field element to a curve point. The map can be applied once to derive a curve point suitable for use with computational DiffieHellman (CDH) challenges, and twice to derive a curve point indistinguishable from random.
In the following section, $a$ and $d$ are the curve parameters as described here. $ζ$ is a constant and sqrt_ratio_zeta(v_1,v_2)
is a function, both defined in the Inverse Square Roots section.
The Elligator map is applied as follows to a field element $r_{0}$:

$r←ζr_{0}$.

$u_{1}←(dr−d+a)(dr−ar−d)$.

$n_{1}←(r+1)(a−2d)$.

$m,x=$
sqrt_ratio_zeta
$(1,u_{1}n_{1})$ where $m$ is a boolean indicating whether or not a square root exists for the provided input. 
If a square root for $u_{1}n_{1}$ does not exist, then $q=−1$ and $x=r_{0}x$. Else, $q=1$ and $x$ is unchanged.

$s←xn_{1}$.

$t←−qxs(r−1)(a−2d)_{2}−1$.

If ($s<0$ and $m$ is true) or ($s>0$ and $m$ is false) then $s=−s$.
The Jacobi quartic representation of the resulting point is given by $(s,t)$. The resulting point can be converted from its Jacobi quartic representation to extended projective coordinates via:
$E←2s$
$F←1+as_{2}$
$G←1−as_{2}$
$H←t$
$X←EH$
$Y←FG$
$Z←FH$
$T←EG$
For singlewidth hashtogroup (encode_to_curve
), we apply the above map once. For doublewidth (hash_to_curve
) we apply the map to two field elements and add the resulting curve points.
Test Vectors
Small generator multiples
This table has hexencodings of small multiples of the generator $B$:
Element  Hex encoding 

$0$  0000000000000000000000000000000000000000000000000000000000000000 
$B$  0800000000000000000000000000000000000000000000000000000000000000 
$[2]B$  b2ecf9b9082d6306538be73b0d6ee741141f3222152da78685d6596efc8c1506 
$[3]B$  2ebd42dd3a2307083c834e79fb9e787e352dd33e0d719f86ae4adb02fe382409 
$[4]B$  6acd327d70f9588fac373d165f4d9d5300510274dffdfdf2bf0955acd78da50d 
$[5]B$  460f913e516441c286d95dd30b0a2d2bf14264f325528b06455d7cb93ba13a0b 
$[6]B$  ec8798bcbb3bf29329549d769f89cf7993e15e2c68ec7aa2a956edf5ec62ae07 
$[7]B$  48b01e513dd37d94c3b48940dc133b92ccba7f546e99d3fc2e602d284f609f00 
$[8]B$  a4e85dddd19c80ecf5ef10b9d27b6626ac1a4f90bd10d263c717ecce4da6570a 
$[9]B$  1a8fea8cbfbc91236d8c7924e3e7e617f9dd544b710ee83827737fe8dc63ae00 
$[10]B$  0a0f86eaac0c1af30eb138467c49381edb2808904c81a4b81d2b02a2d7816006 
$[11]B$  588125a8f4e2bab8d16affc4ca60c5f64b50d38d2bb053148021631f72e99b06 
$[12]B$  f43f4cefbe7326eaab1584722b1b4860de554b23a14490a03f3fd63a089add0b 
$[13]B$  76c739a33ffd15cf6554a8e705dc573f26490b64de0c5bd4e4ac75ed5af8e60b 
$[14]B$  200136952d18d3f6c70347032ba3fef4f60c240d706be2950b4f42f1a7087705 
$[15]B$  bcb0f922df1c7aa9579394020187a2e19e2d8073452c6ab9b0c4b052aa50f505 
Hashtogroup
This table has input field elements along with the $(x,y)$ affine coordinates of the output point after applying the elligator map once:
Input field element  $(x,y)$ output point 

2873166235834220037104482467644394559952202754715866736878534498814378075613  (1267955849280145133999011095767946180059440909377398529682813961428156596086, 5356565093348124788258444273601808083900527100008973995409157974880178412098) 
7664634080946480262422274939177258683377350652451958930279692300451854076695  (1502379126429822955521756759528876454108853047288874182661923263559139887582, 7074060208122316523843780248565740332109149189893811936352820920606931717751) 
707087697291448463178823336344479808196630248514167087002061771344499604401  (2943006201157313879823661217587757631000260143892726691725524748591717287835, 4988568968545687084099497807398918406354768651099165603393269329811556860241) 
4040687156656275865790182426684295234932961916167736272791705576788972921292  (2893226299356126359042735859950249532894422276065676168505232431940642875576, 5540423804567408742733533031617546054084724133604190833318816134173899774745) 
6012393175004325154204026250961812614679561282637871416475605431319079196219  (2950911977149336430054248283274523588551527495862004038190631992225597951816, 4487595759841081228081250163499667279979722963517149877172642608282938805393) 
7255180635786717958849398836099816771666363291918359850790043721721417277258  (3318574188155535806336376903248065799756521242795466350457330678746659358665, 7706453242502782485686954136003233626318476373744684895503194201695334921001) 
6609366864829739556945402594963920739176902000316365292959221199804402230199  (3753408652523927772367064460787503971543824818235418436841486337042861871179, 2820605049615187268236268737743168629279853653807906481532750947771625104256) 
6875465950337820928985371259904709015074922314668494500948688901607284806973  (7803875556376973796629423752730968724982795310878526731231718944925551226171, 7033839813997913565841973681083930410776455889380940679209912201081069572111) 
Randomizable Signatures
Penumbra’s signatures are provided by decaf377rdsa
, a variant of the Zcash
RedDSA construction instantiated using the decaf377
group.
These are Schnorr signatures, with two additional properties relevant to
Penumbra:

They support randomization of signing and verification keys. Spending a note requires use of the signing key that controls its spend authorization, but if the same spend verification key were included in multiple transactions, they would be linkable. Instead, both the signing and verification keys are kept secret^{1}, and each spend description includes a randomization of the verification key, together with a proof that the randomized verification key was derived from the correct spend verification key.

They support addition and subtraction of signing and verification keys. This property is used for binding signatures, which bind zeroknowledge proofs to the transaction they were intended for and enforce conservation of value.
decaf377rdsa
Let $G$ be the decaf377
group of prime order $r$. Keys and signatures
are parameterized by a domain $D$. Each domain has an associated generator
$B_{D}$. Currently, there are two defined domains: $D=SpendAuth$ and
$D=Binding$. The hash function $H_{⋆}:{0,1}_{∗}→F_{r}$ is instantiated by using blake2b
with the personalization string
decaf377rdsa
, treating the 64byte output as the littleendian encoding of
an integer, and reducing that integer modulo $r$.
A signing key is a scalar $a∈F_{r}$. The corresponding verification key is the group element $A=[a]B_{D}$.
Sign
On input message m
with signing key $a$, verification key $A$, and domain $D$:
 Generate 80 random bytes.
 Compute the nonce as $r←H_{⋆}(random_bytes∣∣A_bytes∣∣m)$.
 Commit to the nonce as $R←[r]B_{D}$.
 Compute the challenge as $c←H_{⋆}(R_bytes∣∣A_bytes∣∣m)$.
 Compute the response as $s←r+ac$.
 Output the signature
R_bytes  s_bytes
.
Verify
On input message m
, verification key A_bytes
, and signature sig_bytes
:
 Parse $A$ from
A_bytes
, or fail ifA_bytes
is not a valid (hence canonical)decaf377
encoding.  Parse
sig_bytes
asR_bytes  s_bytes
and the components as $R$ and $s$, or fail if they are not valid (hence canonical) encodings.  Recompute the challenge as $c←H_{⋆}(R_bytes∣∣A_bytes∣∣m)$.
 Check the verification equation $R=[s]B−[c]A$, rejecting the signature if it is not satisfied.
SpendAuth
signatures
The first signature domain used in Penumbra is for spend authorization signatures. The basepoint $B_{SpendAuth}$ is the conventional decaf377
basepoint 0x0800000...
.
Spend authorization signatures support randomization:
Randomize.SigningKey
Given a randomizer $r$ F_{r}$, the randomized signing key is $a+r$.
Randomize.VerificationKey
Given a randomizer $r$ F_{r}$, the randomized verification key is $A+[r]B_{SpendAuth}$.
Randomizing a signing key and then deriving the verification key associated to the randomized signing key gives the same result as randomizing the original verification key (with the same randomizer).
Implementation
An implementation of decaf377rdsa
can be found here.
Binding
signatures
The second signature domain used in Penumbra is for binding signatures. The
basepoint $B_{Binding}$ is the result of converting
blake2b(b"decaf377rdsabinding")
to an $F_{r}$ element and applying
decaf377
’s CDH encodetocurve method.
Since the verification key corresponding to the signing key $a∈F_{r}$ is $A=[a]B_{D}$, adding and subtracting signing and verification keys commutes with derivation of the verification key, as desired.
This situation is a good example of why it’s better to avoid the terms “public key” and “private key”, and prefer more precise terminology that names keys according to the cryptographic capability they represent, rather than an attribute of how they’re commonly used. In this example, the verification key should not be public, since it could link different transactions.
Simple example: Binding signature
Let’s say we have two actions in a transaction: one spend (indicated with subscript $s$) and one output (indicated with subscript $o$).
The balance commitments for those actions are:
$cv_{o}=[v_{o}]G_{v}+[rcv_{o}]G_{rcv}$
$cv_{s}=[v_{s}]G_{v}+[rcv_{s}]G_{rcv}$
where $G_{v}$ and $G_{rcv}$ are generators, $rcv_{i}$ are the blinding factors, and $v_{i}$ are the values.
When the signer is computing the binding signature, they have the blinding factors for all commitments.
They derive the signing key $bsk$ by adding up the blinding factors based on that action’s contribution to the balance:
$bsk=rcv_{s}−rcv_{o}$
The signer compute the binding signature using this key $bsk$.
When the verifier is checking the signature, they add up the balance commitments to derive the verification key $bvk$ based on their contribution to the balance:
$bvk=cv_{s}−cv_{o}=[v_{s}−v_{o}]G_{v}+[rcv_{s}−rcv_{o}]G_{rcv}$
If the transaction is valid, then the first term on the LHS ($[v_{s}−v_{o}]G_{v}$) is zero since for Penumbra all transactions should have zero value balance.
This leaves the verifier with the verification key:
$bvk=[rcv_{s}−rcv_{o}]G_{rcv}$
If the value balance is not zero, the verifier will not be able to compute the verification key with the data in the transaction.
Key Agreement
Poseidon for BLS12377
The Poseidon hash function is a cryptographic hash function that operates natively over prime fields. This allows the hash function to be used efficiently in the context of a SNARK. In the sections that follow we describe our instantiation of Poseidon over BLS12377.
Overview of the Poseidon Permutation
This section describes the Poseidon permutation. It consists of rounds, where each round has the following steps:
AddRoundConstants
: where constants (denoted byarc
in the code) are added to the internal state,SubWords
: where the Sbox $S(x)=x_{α}$ is applied to the internal state,MixLayer
: where a matrix is multiplied with the internal state.
The total number of rounds we denote by $R$. There are two types of round in the Poseidon construction, partial and full. We denote the number of partial and full rounds by $R_{P}$ and $R_{F}$ respectively.
In a full round in the SubWords
layer the Sbox is applied to each element of the
internal state, as shown in the diagram below:
┌───────────────────────────────────────────────────────────┐
│ │
│ AddRoundConstants │
│ │
└────────────┬──────────┬──────────┬──────────┬─────────────┘
│ │ │ │
┌─▼─┐ ┌─▼─┐ ┌─▼─┐ ┌─▼─┐
│ S │ │ S │ │ S │ │ S │
└─┬─┘ └─┬─┘ └─┬─┘ └─┬─┘
│ │ │ │
┌────────────▼──────────▼──────────▼──────────▼─────────────┐
│ │
│ MixLayer │
│ │
└────────────┬──────────┬──────────┬──────────┬─────────────┘
│ │ │ │
▼ ▼ ▼ ▼
In a partial round, in the SubWords
layer we apply the Sbox only to one element
of the internal state, as shown in the diagram below:
│ │ │ │
│ │ │ │
┌────────────▼──────────▼──────────▼──────────▼─────────────┐
│ │
│ AddRoundConstants │
│ │
└────────────┬──────────────────────────────────────────────┘
│
┌─▼─┐
│ S │
└─┬─┘
│
┌────────────▼──────────────────────────────────────────────┐
│ │
│ MixLayer │
│ │
└────────────┬──────────┬──────────┬──────────┬─────────────┘
│ │ │ │
▼ ▼ ▼ ▼
We apply half the full rounds ($R_{f}=R_{F}/2$) first, then we apply the $R_{P}$ partial rounds, then the rest of the $R_{f}$ full rounds. This is called the HADES design strategy in the literature.
Poseidon Parameter Generation
The problem of Poseidon parameter generation is to pick secure choices for the parameters used in the permutation given the field, desired security level $M$ in bits, as well as the width $t$ of the hash function one wants to instantiate (i.e. 1:1 hash, 2:1 hash, etc.).
Poseidon parameters consist of:
 Choice of Sbox: choosing the exponent $α$ for the Sbox layer where $S(x)=x_{α}$,
 Round numbers: the numbers of partial and full rounds,
 Round constants: the constants to be added in the
AddRoundConstants
step,  MDS Matrix: generating a Maximum Distance Separable (MDS) matrix to use in the linear layer, where we multiply this matrix by the internal state.
Appendix B of the Poseidon paper provides sample implementations of both the Poseidon
permutation as well as parameter generation. There is a Python script called
calc_round_numbers.py
which provides the round numbers given the security level
$M$, the width of the hash function $t$, as well as the choice of $α$ used
in the Sbox step. There is also a Sage script, which generates the round numbers,
constants, and MDS matrix, given the security level $M$, the width of the hash
function $t$, as well as the choice of $α$ used in the Sbox step.
Since the publication of the Poseidon paper, others have edited these scripts, resulting in a number of implementations being in use derived from these initial scripts. We elected to implement Poseidon parameter generation in Rust from the paper, checking each step, and additionally automating the Sbox parameter selection step such that one can provide only the modulus of a prime field and the best $α$ will be selected.
Below we describe where we deviate from the parameter selection procedure described in the text of the Poseidon paper.
Choice of SBox
The Poseidon paper focuses on the cases where $α=[−1,3,5]$, as well as BLS12381 and BN254. For a choice of positive $α$, it must satisfy $gcd(α,p−1)=1$, where $p$ is the prime modulus.
For our use of Poseidon on BLS12377, we wanted to generate a procedure for selecting the optimal $α$ for a general curve, which we describe below.
We prefer small, positive $α$ for software (noncircuit) performance, only using $α=−1$ when we are unable to find an appropriate positive $α$. For positive $α$, the number of constraints in an arithmetic circuit per SBox is equal to its depth in a tree of shortest addition chains. For a given number of constraints (i.e. at a given depth in the tree), we pick the largest $α$ at that level that meets the GCD requirement. Since larger $α$ provides more security, choosing the largest at a given depth reduces the round requirement.
The procedure in detail:
We proceed down the tree from depth 2 to depth 5 (where depth 0 is the root of 1):
 At a given depth, proceed from largest number to smaller numbers.
 For a given element, check if $gcd(α,p−1)=1$ is satisfied. If yes, we choose it, else continue.
If we get through these checks to depth of 5 without finding a positive exponent for $α$, then we pick $α=−1$, which is wellstudied in the original Poseidon paper.
For decaf377
, following this procedure we end up with $α=17$.
Round Numbers
We implement the round numbers as described in the original paper. These are the number of rounds necessary to resist known attacks in the literature, plus a security margin of +2 full rounds, and +7.5% partial rounds.
We test our round number calculations with tests from Appendices G and H from the paper which contain concrete instantiations of Poseidon for $α=[−1,3,5]$ and their round numbers.
Round Constants
We do not use the Grain LFSR for generating pseudorandom numbers as described in Appendix F of the original paper. Instead, we use a Merlin transcript to enable parameter generation to be fully deterministic and easily reproducible.
We first append the message "poseidonparamgen"
to the transcript
using label domsep
.
We then bind this transcript to the input parameter choices:
 the width of the hash function $t$ using label
t
,  the security level $M$ using label
M
, and  the modulus of the prime field $p$ using label
p
.
We then also bind the transcript to the specific instance, as done with the Grain LFSR in Appendix F, so we bind to:
 the number of full rounds $R_{F}$ using label
r_F
,  the number of partial rounds $R_{P}$ using label
r_P
, and  the choice of Sbox exponent $α$ using label
alpha
.
We generate random field elements from hashes of this complete transcript of all the input parameters and the derived parameters $α$, $R_{F}$, and $R_{P}$.
Each round constant is generated by obtaining challenge bytes from the Merlin transcript, derived
using label roundconstant
. We obtain $(n+135)/8$ bytes where $n$ is the
field size in bits. These bytes are then interpreted as an unsigned littleendian
integer reduced modulo the field.
MDS Matrix
We generate MDS matrices using the Cauchy method. However instead of randomly sampling the field as described in the text, we deterministically generate vectors $x$ and $y$ as:
$x=[0..t)$ $y=[t..2t)$
Each element $M_{ij}$ of the matrix is then constructed as:
$M_{ij}=x_{i}+y_{j}1 $
where $i,j∈[0..t)$.
This deterministic matrix generation method has been verified to be safe over the
base field of decaf377
, using algorithms 13 described in Grassi, Rechberger
and Schofnegger 2020 over the t
range 1100.
The parameters that are used for poseidon377
are located in the params.rs
module of the source code. The parameters were generated on an Apple M1.
Test Vectors
The following are test vectors for the poseidon377
Poseidon instantiation.
Each section is for a given rate of a fixedwidth hash function, where
capacity is 1. Inputs and output are $F_{q}$ elements. The domain separator
used in each case are the bytes "Penumbra_TestVec"
decoded to an $F_{q}$ element,
where we interpret these bytes in littleendian order.
Rate 1
Input element:
7553885614632219548127688026174585776320152166623257619763178041781456016062
Output element:
2337838243217876174544784248400816541933405738836087430664765452605435675740
Rate 2
Input elements:
7553885614632219548127688026174585776320152166623257619763178041781456016062
2337838243217876174544784248400816541933405738836087430664765452605435675740
Output element:
4318449279293553393006719276941638490334729643330833590842693275258805886300
Rate 3
Input elements:
7553885614632219548127688026174585776320152166623257619763178041781456016062
2337838243217876174544784248400816541933405738836087430664765452605435675740
4318449279293553393006719276941638490334729643330833590842693275258805886300
Output element:
2884734248868891876687246055367204388444877057000108043377667455104051576315
Rate 4
Input elements:
7553885614632219548127688026174585776320152166623257619763178041781456016062
2337838243217876174544784248400816541933405738836087430664765452605435675740
4318449279293553393006719276941638490334729643330833590842693275258805886300
2884734248868891876687246055367204388444877057000108043377667455104051576315
Output element:
5235431038142849831913898188189800916077016298531443239266169457588889298166
Rate 5
Input elements:
7553885614632219548127688026174585776320152166623257619763178041781456016062
2337838243217876174544784248400816541933405738836087430664765452605435675740
4318449279293553393006719276941638490334729643330833590842693275258805886300
2884734248868891876687246055367204388444877057000108043377667455104051576315
5235431038142849831913898188189800916077016298531443239266169457588889298166
Output element:
66948599770858083122195578203282720327054804952637730715402418442993895152
Rate 6
Input elements:
7553885614632219548127688026174585776320152166623257619763178041781456016062
2337838243217876174544784248400816541933405738836087430664765452605435675740
4318449279293553393006719276941638490334729643330833590842693275258805886300
2884734248868891876687246055367204388444877057000108043377667455104051576315
5235431038142849831913898188189800916077016298531443239266169457588889298166
66948599770858083122195578203282720327054804952637730715402418442993895152
Output element:
6797655301930638258044003960605211404784492298673033525596396177265014216269
Fuzzy Message Detection
By design, privacypreserving blockchains like Penumbra don’t reveal metadata about the sender or receiver of a transaction. However, this means that users must scan the entire chain to determine which transactions relate to their addresses. This imposes large bandwidth and latency costs on users who do not maintain online replicas of the chain state, as they must “catch up” each time they come online by scanning all transactions that have occurred since their last activity.
Alternatively, users could delegate scanning to a third party, who would monitor updates to the chain state on their behalf and forward only a subset of those updates to the user. This is possible using viewing keys, as in Zcash, but viewing keys represent the capability to view all activity related to a particular address, so they can only be delegated to trusted third parties.
Instead, it would be useful to be able to delegate only a probabilistic detection capability. Analogous to a Bloom filter, this would allow a detector to identify all transactions related to a particular address (no false negatives), while also identifying unrelated transactions with some false positive probability. Unlike viewing capability, detection capability would not include the ability to view the details of a transaction, only a probabilistic association with a particular address. This is the problem of fuzzy message detection (FMD), analyzed by Beck, Len, Miers, and Green in their paper Fuzzy Message Detection, which proposes a cryptographic definition of fuzzy message detection and three potential constructions.
This section explores how Penumbra could make use of fuzzy message detection:

In Sender and Receiver FMD, we propose a generalization of the original definition where the false positive probability is set by the sender instead of the receiver, and discusses why this is useful.

In Constructing SFMD, we realize the new definition using a variant of one of the original FMD constructions, and extend it in two ways:
 to support arbitrarily precise detection with compact, constantsize keys;
 to support diversified detection, allowing multiple, publicly unlinkable addresses to be scanned by a single detection key.
Unfortunately, these extensions are not mutually compatible, so we only use the first one, and record the second for posterity.
 In SFMD Threat Model, we describe the threat model for SFMD on Penumbra.
 In SFMD in Penumbra, we describe how SFMD is integrated into Penumbra.
 In Parameter Considerations, we discuss how the false positive rates should be chosen.
Acknowledgements
Thanks to George Tankersley and Sarah Jamie Lewis for discussions on this topic (and each independently suggesting the modifications to realize SFMD), to Gabrielle Beck for discussions about the paper and ideas about statistical attacks, and to Guillermo Angeris for pointers on analyzing information disclosure.
Sender and Receiver FMD
The goal of detection capability is to be able to filter the global stream of state updates into a local stream of state updates that includes all updates related to a particular address, without identifying precisely which updates those are. The rate of updates on this filtered stream should be bounded below, to ensure that there is a large enough anonymity set, and bounded above, so that users processing the stream have a constant and manageable amount of work to process it and catch up with the current chain state. This means that the detection precision must be adaptive to the global message rates: if the false positive rate is too low, the filtered stream will have too few messages, and if it is too high, it will have too many messages.
However, this isn’t possible using the paper’s original definition of fuzzy message detection, because the false positive probability is chosen by the receiver, who has no way to know in advance what probability will produce a correctly sized stream of messages. One way to address this problem is to rename the original FMD definition as Receiver FMD (RFMD), and tweak it to obtain Sender FMD (SFMD), in which the sender chooses the detection probability.
Receiver FMD
The paper’s original definition of fuzzy message detection is as a tuple of
algorithms (KeyGen, CreateClue, Extract, Examine)
.^{1} The receiver uses
KeyGen
to generate a root key and a clue key. A sender uses the
receiver’s clue key as input to CreateClue
to produce a clue. The Extract
algorithm takes the root key and a false positive rate $p$ (chosen from some set
of supported rates), and produces a detection key. The Examine
algorithm
uses a detection key to examine a clue and produce a detection result.
This scheme should satisfy certain properties, formalizations of which can be found in the paper:
Correctness.
Valid matches must always be detected by Examine
; i.e., there are no false negatives.
Fuzziness.
Invalid matches should produce false positives with probability approximately $p$, as long as the clues and detection keys were honestly generated.
Detection Ambiguity.
An adversarial detector must be unable to distinguish between a true positive and a false positive, as long as the clues and detection keys were honestly generated.
In this original definition, the receiver has control over how much detection precision they delegate to a third party, because they choose the false positive probability when they extract a detection key from their root key. This fits with the idea of attenuating credentials, and intuitively, it seems correct that the receiver should control how much information they reveal to their detector. But the information revealed to their detector is determined by both the false positive probability and the amount of other messages that can function as cover traffic. Without knowing the extent of other activity on the system, the receiver has no way to make a principled choice of the detection precision to delegate.
Sender FMD
To address this problem, we generalize the original definition (now Receiver FMD) to Sender FMD, in which the false positive probability is chosen by the sender.
SFMD consists of a tuple of algorithms (KeyGen, CreateClue, Examine)
. Like
RFMD, CreateClue
creates a clue and Examine
takes a detection key and a
clue and produces a detection result. As discussed in the next
section, SFMD can be realized using tweaks to either of the
RFMD constructions in the original paper.
Unlike RFMD, the false positive rate is set by the sender, so CreateClue
takes both the false positive rate $p$ and the receiver’s clue key. Because the
false positive rate is set by the sender, there is no separation of capability
between the root key and a detection key, so KeyGen
outputs a clue key and a
detection key, and Extract
disappears.
In RFMD, flag ciphertexts are universal with respect to the false positive rate, which is applied to the detection key; in SFMD, the false positive rate is applied to the flag ciphertext and the detection key is universal.
Unlike RFMD, SFMD allows detection precision to be adaptive, by having senders use a (consensusdetermined) false positive parameter. This parameter should vary as the global message rates vary, so that filtered message streams have a bounded rate, and it should be the same for all users, so that messages cannot be distinguished by their false positive rate.
We change terminology from the FMD paper; the paper calls detection and
clue keys the secret and public keys respectively, but we avoid this in favor of
capabilitybased terminology that names keys according to the precise capability
they allow. The “clue” terminology is adopted from the Oblivious Message
Retrieval paper of Zeyu Liu and Eran Tromer; we CreateClue
and Examine
clues
rather than Flag
and Test
flag ciphertexts.
Constructing SFMD
The original FMD paper provides three constructions of RFMD. The first two realize functionality for restricted falsepositive probabilities of the form $p=2_{−n}$; the third supports arbitrary fractional probabilities using a much more complex and expensive construction.
The first two schemes, RFMD1 and RFMD2, are constructed similarly to a Bloom
filter: the CreateClue
procedure encrypts a number of 1
bits, and the Examine
procedure uses information in a detection key to check whether some subset of
them are 1
, returning true
if so and false
otherwise. The false positive
probability is controlled by extracting only a subset of the information in the
root key into the detection key, so that it can only check a subset of the bits
encoded in the clue. We focus on RFMD2, which provides more compact
ciphertexts and chosenciphertext security.
In this section, we:
 recall the construction of RFMD2, changing notation from the paper;
 adapt RFMD2 to SFMD2, which provides sender FMD instead of receiver FMD;
 extend SFMD2 to use deterministic derivation, allowing 32byte flag and detection keys to support arbitraryprecision false positive probabilities;
 extend SFMD2 to support diversified detection, allowing multiple, unlinkable flag keys to be detected by a single detection key (though, as this extension conflicts with the preceding one, we do not use it);
 summarize the resulting construction and describe how it can be integrated with a Sapling or Orchardstyle key hierarchy.
The RFMD2 construction
First, we recall the paper’s construction of RFMD2, changing from multiplicative to additive notation and adjusting variable names to be consistent with future extensions.
The construction supports restricted false positive values $p=2_{−n}$ for $n≤γ$, a global parameter determining the minimum false positive rate. $G$ is a group of prime order $q$ and $H_{1}:G_{3}→{0,1}$ and $H_{2}:G×{0,1}_{γ}→Z_{q}$ are hash functions.
RFMD2/KeyGen
Choose a generator $B$ G$. For $i=1,…,γ$, choose $x_{i}$ Z_{q}$ and compute $X_{i}=[x_{i}]B$. Return the root key $rk←(x_{1},…,x_{γ})$ and the clue key $ck←(B,X_{1},…,X_{γ})$.
RFMD2/Extract
On input $p=2_{−n}$ and root key $rk$, parse $(x_{1},…,x_{γ})←rk$ and return $(x_{1},…,x_{n})$ as the detection key.
RFMD2/Flag
On input $ck$, first parse $(B,X_{1},…,X_{n})←ck$, then proceed as follows:
 Choose $r$ Z_{q}$ and compute $P←[r]B$.
 Choose $z$ Z_{q}$ and compute $Q←[z]B$.
 For each $i=1,…,γ$, compute
 a key bit $k_{i}←H_{1}(P∣∣[r]X_{i}∣∣Q)$;
 a ciphertext bit $c_{i}←k_{i}⊕1$.
 Compute $m←H_{2}(P∣∣c_{1}∣∣⋯∣∣c_{γ})$.
 Compute $y←(z−m)r_{−1}$.
Return the clue $c←(P,y,c_{1},…,c_{γ})$.
RFMD2/Examine
On input $dk$, $c$, first parse $(x_{1},…,x_{n})←dk$, $(P,y,c_{1},…,c_{γ})←c$, then proceed as follows:
 Compute $m←H_{2}(P∣∣c_{1}∣∣⋯∣∣c_{γ})$.
 Recompute $Q$ as $Q←[y]P+[m]B$.
 For each $i=1,…,γ$, compute
 a key bit $k_{i}←H_{1}(P∣∣[x_{i}]P∣∣Q)$;
 a plaintext bit $b_{i}←c_{i}⊕k_{i}$.
If all plaintext bits $b_{i}=1$, return $1$ (match); otherwise, return $0$.
To see that the Test
procedure detects true positives, first note that
since $P=[r]B$ and $y=(z−m)r_{−1}$,
$Q =[y]P+[m]B=[(z−m)r_{−1}][r]B+[m]B=[z]B, $
so the detector will recompute the $Q$ used by the sender; then, since $[r]X_{i}=[r][x_{i}]B=[x_{i}]P$, the detector’s $k_{i}←H_{1}(P∣∣[x_{i}]P∣∣Q)$ is the
same as the sender’s $k_{i}←H_{1}(P∣∣[r]X_{i}∣∣Q)$.
On the other hand, if the clue was created with a different clue key, the resulting $k_{i}$ will be random bits, giving the desired $2_{−n}$ false positive probability.
One way to understand the components of the construction is as follows: the message consists of $γ$ bits of a Bloom filter, each encrypted using hashed ElGamal with a common nonce $r$ and nonce commitment $P=[r]B$. The points $P,Q$ are included in the ElGamal hash, ensuring that any change to either will result in a random bit on decryption. The pair $(Q,y)$ act as a public key and signature for a onetime signature on the ciphertext as follows: the sender constructs the point $Q$ as the output of a chameleon hash with basis $(P,B)$ on input $(0,z)$, then uses the hash trapdoor (i.e., their knowledge of the discrete log relation $P=[r]B$) to compute a collision $(y,m)$ involving $m$, a hash of the rest of the ciphertext. The collision $y$ acts as a onetime signature with public key $Q$.
The fact that the generator $B$ was selected at random in each key generation is not used by the construction and doesn’t seem to play a role in the security analysis; in fact, the security proof in Appendix F has the security game operate with a common generator. In what follows, we instead assume that $B$ is a global parameter.
From RFMD2 to SFMD2
Changing this construction to the SFMD model is fairly straightforward: rather
than having the sender encrypt $γ$ bits of the Bloom filter and only allow
the detector to decrypt $n≤γ$ of them, we have the sender only encrypt
$n≤γ$ bits of the Bloom filter and allow the detector to decrypt all
potential bits. As noted in the previous
section, this means that in the SFMD context,
there is no separation of capability between the root key and the detection key.
For this reason, we skip the Extract
algorithm and (conceptually) merge the
root key into the detection key.
The SFMD2 construction then works as follows:
SFMD2/KeyGen
For $i=1,…,γ$, choose $x_{i}$ Z_{q}$ and compute $X_{i}=[x_{i}]B$. Return the detection key $dk←(x_{1},…,x_{γ})$ and the clue key $ck←(X_{1},…,X_{γ})$.
SFMD2/CreateClue
On input clue key $ck$, first parse $(X_{1},…,X_{n})←ck$, then proceed as follows:
 Choose $r$ Z_{q}$ and compute $P←[r]B$.
 Choose $z$ Z_{q}$ and compute $Q←[z]B$.
 For each $i=1,…,n$, compute
 a key bit $k_{i}←H_{1}(P∣∣[r]X_{i}∣∣Q)$;
 a ciphertext bit $c_{i}←k_{i}⊕1$.
 Compute $m←H_{2}(P∣∣n∣∣c_{1}∣∣⋯∣∣c_{n})$.
 Compute $y←(z−m)r_{−1}$.
Return the clue $c←(P,y,n,c_{1},…,c_{n})$. (We include $n$ explicitly rather than have it specified implicitly by $c_{n}$ to reduce the risk of implementation confusion).
SFMD2/Examine
On input detection key $dk$ and clue $c$, first parse $(x_{1},…,x_{γ})←dk$ and $(P,y,n,c_{1},…,c_{n})←c$, then proceed as follows:
 Compute $m←H_{2}(P∣∣n∣∣c_{1}∣∣⋯∣∣c_{n})$.
 Recompute $Q$ as $Q←[y]P+[m]B$.
 For each $i=1,…,n$, compute
 a key bit $k_{i}←H_{1}(P∣∣[x_{i}]P∣∣Q)$;
 a plaintext bit $b_{i}←c_{i}⊕k_{i}$.
If all plaintext bits $b_{i}=1$, return $1$ (match); otherwise, return $0$.
The value of $n$ is included in the ciphertext hash to ensure that the encoding of the ciphertext bits is nonmalleable. Otherwise, the construction is identical.
Compact clue and detection keys
One obstacle to FMD integration is the size of the clue keys. Clue keys are required to create clues, so senders who wish to create clues need to obtain the receiver’s clue key. Ideally, the clue keys would be bundled with other address data, so that clues can be included with all messages, rather than being an optin mechanism with a much more limited anonymity set.
However, the size of clue keys makes this difficult. A clue key supporting false positive probabilities down to $2_{−γ}$ requires $γ$ group elements; for instance, supporting probabilities down to $2_{−14}=1/16384$ with a 256bit group requires 448byte flag keys. It would be much more convenient to have smaller keys.
One way to do this is to use a deterministic key derivation mechanism similar to BIP32 to derive a sequence of child keypairs from a single parent keypair, and use that sequence as the components of a flag / detection keypair. To do this, we define a hash function $H_{4}:G×Z→Z_{q}$. Then given a parent keypair $(x,X=[x]B)$, child keys can be derived as $x_{i}X_{i} ←x+H_{4}(X∣∣i)←X+[H_{4}(X∣∣i)]B $ with $X_{i}=[x_{i}]B$. Unlike BIP32, there is only one sequence of keypairs, so there is no need for a chain code to namespace different levels of derivation, although of course the hash function $H_{4}$ should be domainseparated.
Applying this to SFMD2, the scalar $x$ becomes the detection key, and the point $X$ becomes the clue key. The former detection key is renamed the expanded detection key $(x_{1},…,x_{γ})$, and the former clue key is renamed the expanded clue key $(X_{1},…,X_{γ})$; these are derived from the detection and clue keys respectively.
In addition, it is no longer necessary to select the minimum false positive probability as a global parameter $γ$ prior to key generation, as the compact keys can be extended to arbitrary length (and thus arbitrary false positive probabilities).
Deterministic derivation is only possible in the SFMD setting, not the RFMD setting, because SFMD does not require any separation of capability between the component keys used for each bit of the Bloom filter. Public deterministic derivation does not allow separation of capability: given $X$ and any child secret $x_{i}$, it’s possible to recover the parent secret $x$ and therefore the entire subtree of secret keys. Thus, it cannot be used for RFMD, where the detection key is created by disclosing only some of the bits of the root key.
Diversified detection: from SFMD2 to SFMD2d
The Sapling design provides viewing keys that support diversified addresses: a collection of publicly unlinkable addresses whose activity can be scanned by a common viewing key. For integration with Sapling, as well as other applications, it would be useful to support diversified detection: a collection of publicly unlinkable diversified clue keys that share a common detection key. This collection is indexed by data called a diversifier; each choice of diversifier gives a different diversified clue key corresponding to the same detection key.
In the Sapling design, diversified addresses are implemented by selecting the basepoint of the key agreement protocol as the output of a groupvalued hash function whose input is the diversifier. Because the key agreement mechanism only makes use of the secret key (the incoming viewing key) and the counterparty’s ephemeral public key, decryption is independent of the basepoint, and hence the choice of diversifier, allowing a common viewing key to decrypt messages sent to any member of its family of diversified transmission (public) keys.
A similar approach can be applied to SFMD2, but some modifications to the tag
mechanism are required to avoid use of the basepoint in the Test
procedure.
Unfortunately, this extension is not compatible with compact clue and
detection keys, so although it is presented here for posterity, it is not used
in Penumbra.
Let $D$ be the set of diversifiers, and let $H_{3}:D→G$ be a groupvalued hash function. At a high level, our goal is to replace use of a common basepoint $B$ with a diversified basepoint $B_{d}=H_{3}(d)$ for some $d∈D$.
Our goal is to have diversified clue keys, so we first replace $B$ by $B_{d}$ in
KeyGen
, so that $X_{i}=[x_{i}]B_{d}$ and the components of the clue key are
parameterized by the diversifier $d$. The sender will compute the key bits as
$k_{i}←H_{1}(P∣∣[r]X_{i}∣∣Q),$
and the receiver computes them as
$k_{i}←H_{1}(P∣∣[x_{i}]P∣∣Q).$
If $P=[r]B_{d}$ instead of $P=[r]B$, then $[x_{i}]P=[r]X_{i}$ and the middle
component will match.
But then the sender needs to construct $Q$ as $Q←[z]B_{d}$ in order to have a known discrete log relation between $P$ and $Q$. This is a problem, because $Q$ is recomputed by the receiver, but if the receiver must recompute it as $Q←[y]P+[m]B_{d}$, detection is bound to the choice of diversifier, and we need detection to be independent of the choice of diversifier.
The root of this problem is that the chameleon hash uses basis $(P,B)$, so the receiver’s computation involves the basepoint $B$. To avoid it, we can use a basis $(P_{1},P_{2})$, where $P_{i}←[r_{i}]B$. $P_{1}$ is used in place of $P$, but $Q$ is computed as $[z]P_{2}$, i.e., as the chameleon hash $⟨(0,z),(P_{1},P_{2})⟩$. The sender’s collision then becomes $y←(z−m)r_{1}r_{2} $, allowing the detector to recompute $Q$ as $Q←=== [y]P_{1}+[m]P_{2}[(z−m)r_{1}r_{2} ][r_{1}]B+[m]P_{2}[z−m]P_{2}+[m]P_{2}[z]P_{2}. $ The point $P_{2}$ must be included in the clue, increasing its size, but detection no longer involves use of a basepoint (diversified or otherwise).
Concretely, this mechanism works as follows, splitting out generation of clue keys (diversifierdependent) from generation of detection keys:
SFMD2d/KeyGen
For $i=1,…,γ$, choose $x_{i}$ Z_{q}$, and return the detection key $dk←(x_{1},…,x_{γ})$.
SFMD2d/Diversify
On input detection key $dk$ and diversifier $d$, first parse $(x_{1},…,x_{γ})←dk$. Then compute the diversified base $B_{d}←H_{3}(d)$, and use it to compute $X_{i}=[x_{i}]B_{d}$. Return the diversified clue key $ck_{d}←(X_{1},…,X_{γ})$.
SFMD2d/CreateClue
On input diversified clue key $ck_{d}$ and diversifier $d$, first parse $(X_{1},…,X_{n})←ck_{d}$, then proceed as follows:
 Compute the diversified base $B_{d}←H_{3}(d)$.
 Choose $r_{1}$ Z_{q}$ and compute $P_{1}←[r]B_{d}$.
 Choose $r_{2}$ Z_{q}$ and compute $P_{2}←[r]B_{d}$.
 Choose $z$ Z_{q}$ and compute $Q←[z]P_{2}$.
 For each $i=1,…,n$, compute
 a key bit $k_{i}←H_{1}(P_{1}∣∣P_{2}∣∣[r]X_{i}∣∣Q)$;
 a ciphertext bit $c_{i}←k_{i}⊕1$.
 Compute $m←H_{2}(P_{1}∣∣P_{2}∣∣n∣∣c_{1}∣∣⋯∣∣c_{n})$.
 Compute $y←(z−m)r_{1}r_{2} $.
Return the clue $c←(P_{1},P_{2},y,n,c_{1},…,c_{n})$.
SFMD2d/Examine
On input detection key $dk$ and clue $c$, first parse $(x_{1},…,x_{γ})←dk$ and $(P_{1},P_{2},y,n,c_{1},…,c_{n})←c$, then proceed as follows:
 Compute $m←H_{2}(P_{1}∣∣P_{2}∣∣n∣∣c_{1}∣∣⋯∣∣c_{n})$.
 Recompute $Q$ as $Q←[y]P_{1}+[m]P_{2}$.
 For each $i=1,…,n$, compute
 a key bit $k_{i}←H_{1}(P_{1}∣∣P_{2}∣∣[x_{i}]P∣∣Q)$;
 a plaintext bit $b_{i}←c_{i}⊕k_{i}$.
If all plaintext bits $b_{i}=1$, return $1$ (match); otherwise, return $0$.
Unfortunately, this extension does not seem to be possible to combine with the compact clue and detection keys extension described above. The detection key components $x_{i}$ must be derived from the parent secret and (the hash of) public data, but diversified detection requires that different public data (clue keys) have the same detection key components.
Of the two extensions, compact clue keys are much more important, because they allow clue keys to be included in an address, and this allows message detection to be a default behavior of the system, rather than an optin behavior of a select few users.
SFMD Threat Model
Open vs Closed World Setting
The previous literature on FMD (e.g. Beck et al. 2021, Seres et al. 2021) focuses on a model we will call the closed world setting where:
 A single untrusted server performs FMD on behalf of users. This server has all detection keys.
 All users in the messaging system use FMD.
This is appropriate for a messaging application using a single centralized server where FMD is a requirement at the protocollevel.
However, Penumbra operates in what we will call the open world setting in which:
 Multiple untrusted servers perform FMD on behalf of users, i.e. there is no single centralized detection server with all detection keys.
 Not all users use FMD: in Penumbra FMD is optin. A fraction of Penumbra users will download all messages, and never provide detection keys to a third party.
A further difference in Penumbra is that the total number of distinct users is unknown. Each user can create multiple addresses, and they choose whether or not to derive a detection key for a given address.
Assumptions
We assume the FMD construction is secure and the properties of correctness, fuzziness (false positives are detected with rate $p$), and detection ambiguity (the server cannot distinguish between false and true positives) hold as described in the previous section.
All parties malicious or otherwise can access the public chain state, e.g. the current and historical global network activity rates and the current and historical network false positive rates.
Each detection server also observes which messages are detected, but only for the detection keys they have been given. No detection server has detection keys for all users, since only a subset of users optin to FMD.
A malicious detection server can passively compare the detected sets between users. They can also perform active attacks, sending additional messages to artificially increase global network volume, or to the user to increase the number of true positives. We consider these attacks in the next section. In the open world setting, multiple malicious detection servers may be attempting to boost traffic globally, or may target the same user. Malicious detection servers may collude, sharing the sets of detection keys they have in order to jointly maximize the information they learn about network activity.
We assume no detection servers have access to sender metadata, as would be the case if participants routed their traffic through a network privacy layer such as Tor.
A passive eavesdropper can observe the network traffic between recipient and detection server, and attempt to infer the number of messages they have downloaded. We assume the connection between the detection server and recipient is secured using HTTPS.
SFMD in Penumbra
In the secions that follow we describe how SFMD clue keys, detection keys, and clues are integrated into the Penumbra system.
Clue Keys
Each Penumbra diversified address includes as part of the encoded address an SFMD clue key. This key can be used to derive a clue for a given output.
See the Addresses section for more details.
Detection Keys
Each Penumbra address has an associated SFMD detection key. This key is what the user can optionally disclose to a thirdparty service for detection. Recall the detection key is used to examine each clue and return if there is a detection or not.
Clues
Each Penumbra transaction can have multiple outputs, to the same or different recipients. If a transaction contains $n$ outputs for the same recipient, then the FMD false positive rate $p$ will be $p_{n}<<p$ if the detector uses the detection key that does not correspond to that recipient. To ensure that transactions are detected with false positive rate $p$, we attach clues to transactions such that there is a single clue per recipient clue key per transaction.
In order not to leak the number of distinct recipients to a passive observer through the number of clues in a transaction, we add dummy clues to the transaction until there are an equal number of clues and outputs. A consensus rule verifies that all transactions have an equal number of clues and outputs.
A consensus rule verifies that clues in transactions have been generated using the appropriate precision, within a grace period of 10 blocks^{1}. Transactions with clues generated using the incorrect precision are rejected by the network.
This imposes an bound on the latency of signing workflows.
Parameter Considerations

How should the false positive rate be determined? In some epoch, let $p$ be the false positive rate, $N$ be the total number of messages, $M$ be the number of true positives for some detection key, and $D$ be the number of detections for that detection key. Then $E[D]=M+p(N−M)=pN+M(1−p),$ and ideally $p$ should be chosen so that:
 $E[D]$ is bounded above;
 When $M$ is within the range of “normal use”, $E[D]$ is close enough to $pN$ that it’s difficult for a detector to distinguish (what does this mean exactly?);

The notion of detection ambiguity only requires that true and false positives be ambiguous in isolation. In practice, however, a detector has additional context: the total number of messages, the number of detected messages, and the false positive probability. What’s the right notion in this context?

What happens when an adversary manipulates $N$ (diluting the global message stream) or $M$ (by sending extra messages to a target address)? There is some analogy here to flashlight attacks, although with the critical difference that flashlight attacks on decoy systems degrade privacy of the transactions themselves, whereas here the scope is limited to transaction detection.
Flow Encryption
NOTE: Flow Encryption will not ship in Penumbra V1, because ABCI 2.0 was delayed. Instead, flow contributions will be in the clear until a future network upgrade.
These notes are a workinprogress and do not reflect the current protocol.
In Penumbra, we need some scheme to achieve privacy in a multiparty context, where knowledge from multiple participants is required. Traditional zeroknowledge proofs are not sufficient for this, since there is a global state that no individual participant can prove knowledge of if the global state is private. This is described in the Batching Flows section at length. To implement flow encryption, we need a few constructions:
 Ideal Functionality
 The Eddy Construction
 Distributed Key Generation: a distributed key generation scheme that allows a quorum of validators to derive a shared
decaf377
public key as well as a set of private key shares.  Homomorphic Threshold Encryption: a partially homomorphic encryption scheme which allows users to encrypt values and validators to aggregate (using the homomorphism) and perform threshold decryption on aggregate values using the private key shares from DKG.
Ideal Functionality
Flow encryption is ultimately “just” a form of additively homomorphic threshold encryption. However, usefully integrating that primitive into a ledger and byzantine consensus system requires a number of additional properties, and it’s useful to have a single name that refers to that complete bundle of properties.
In this section, we sketch what those properties are, and the algorithms that a
flow encryption construction should provide, separately from our instantiation
of flow encryption, eddy
, described in the next section.
Participants
There are three participants in flow encryption:
 the ledger, which is assumed to be a BFT broadcast mechanism that accepts messages based on some applicationspecific rules;
 users, who encrypt their individual contributions to some flow of value and submit them to the ledger;
 decryptors, who aggregate encryptions posted to the ledger and decrypt the batched flow.
In this description, we refer to decryptors, rather than validators, in order to more precisely name their role in flow encryption. In Penumbra, the decryptors are also the validators who perform BFT consensus to agree on the ledger state. However, this is not a requirement of the construction; one could also imagine a set of decryptors who jointly decrypted data posted to a common, external ledger such as Juno or Ethereum.
We include the ledger as a participant to highlight that a BFT broadcast mechanism is assumed to be available, and to explicitly define when communication between other participants happens via the ledger, and is thus already BFT, and when communication happens outofband, and must be made BFT.
Importantly, users are not required to have any online interactions or perform any protocols with any other participant. They must be able to encrypt and submit their contribution noninteractively. This is essential for the intended role of flow encryption in allowing private interaction with public shared state: the decryption protocol acts as a form of specialized, lightweight MPC, and the encryption algorithm acts as a way for users to delegate participation in that MPC to a set of decryptors, without imposing wider coordination requirements on the users of the system.
Algorithms and Protocols
FlowEnc/DistKeyGen
This is a multiround protocol performed by the decryptors. On input a set of $n$ decryptors and a decryption threshold $1≤t≤n$, this protocol outputs a common threshold encryption key $D$, a private key share $d_{p}$ for each participant $p$, and a public set of commitments to decryption shares $ϕ_{p}$. The exact details of the DKG, its number of rounds, etc., are left to the specific instantiation.
FlowEnc/Encrypt
This is an algorithm run by users. On input an encryption key $D$ and the opening $(v,v)$ to a Pedersen commitment $C$, this algorithm outputs a ciphertext $E=Enc(v)$ and a proof $π_{Enc}$ which establishes that $E=Enc(v)$ is wellformed and is consistent, in the sense that it encrypts the same value committed to by $C=Commit(v,v)$.
We assume that all ciphertexts are submitted to the ledger, which verifies $π_{Enc}$ along with any other applicationspecific validity rules. These rules need to check, among other things, that $v$ is the correct value to encrypt, and the Pedersen commitment provides a flexible way to do so. The consistency property established by $π_{Enc}$ allows applicationspecific proof statements about (a commitment to) $v$ to extend to the ciphertext $Enc(v)$.
FlowEnc/Aggregate
This algorithm describes how to add ciphertexts $∑_{i}Enc(v_{i})$ to output the encryption $Enc(∑_{i}v_{i})$ of their sum.
We also assume that, because ciphertexts are posted to the ledger, all decryptors have a consistent view of available ciphertexts, and of the applicationspecific rules concerning which contributions should be batched together, over what time period, etc., so that they also have a consistent view of the aggregated ciphertext to decrypt.
In the case of sameblock decryption, this assumption requires some care to integrate with the process for coming to consensus on the block containing transactions to batch and decrypt, but this is outofscope for flow encryption itself. See Flow Encryption and Consensus for details on this aspect in Penumbra specifically.
FlowEnc/PreDecrypt
On input ciphertext $E=Enc(w)$ and key share $d_{p}$, this algorithm outputs a decryption share $S_{p}$ and a decryption share integrity proof $π_{p}$.
FlowEnc/Decrypt
On input a ciphertext $E=Enc(w)$ and any set of $k$ decryption shares and proofs $(S_{1},π_{1}),…,(S_{k},π_{k})$ with $t≤k≤n$, output $w$ if at least $t$ of $k$ decryption shares were valid.
Properties
We require the following properties of these algorithms:
Additive Homomorphism
Ciphertexts must be additively homomorphic: $i∑ Enc(v_{i})=Enc(i∑ v_{i})$
Value Integrity
The flow encryption must ensure conservation of value, from the individual users’ contributions all the way to the decrypted batch total. Establishing value integrity proceeds in a number of steps:
 Applicationspecific logic proves that each user’s contribution value $v_{i}$ conserves value according to the application rules, by proving statements about the commitment $C_{i}$ to $v_{i}$.
 The encryption proof $π_{Enc}$ extends integrity from $C_{i}$ to $E_{i}$.
 Integrity extends to the aggregation $E=∑_{i}E_{i}$ automatically, since the aggregation can be publicly recomputed by anyone with access to the ledger.
 The decryption share integrity proofs extend integrity from $E$ to $w$. This requires that, if $w$ is the result of decryption using valid decryption shares, than $E=Enc(w)$.
 Publication of (any) decryption transcript allows any participant to check the endtoend property that $w=∑_{i}v_{i}$ for (application)valid $v_{i}$.
Decryption Robustness
The decryption process must succeed after receiving any $t$ valid decryption shares, so that any decryptor who can receive messages from $t$ honest decryptors must be able to compute the correct plaintext.
Unlike the DKG, where we do not impose a constraint on the number of rounds, we require that decryption succeed with only one round of communication. This allows us to integrate the decryption process with the consensus process, so that in the case where decryptors are validators, they can jointly learn the batched flow at the same time as they finalize a block and commit its state changes. This property is important to avoid requiring a pipelined execution model. For more details, see Flow Encryption and Consensus.
Note that we do not require that any specific subset of decryption shares is
used to get the (unique) decryption result in FlowEnc/Decrypt
. This permits a
streaming implementation where all $n$ decryptors participate, but decryption
completes for each participant as soon as they receive $t$ valid shares,
ensuring that decryption is not bottlenecked on the slowest
participant.
DKG Verifiability
The DKG must be verifiable: participants (decryptors) must be able to verify that counterparty participants (other decryptors) are contributing to the DKG honestly, without the use of a trusted dealer. This can be achieved using something similar to Feldman’s Verifiable Secret Sharing protocol, where each participant shares a commitment to their share which is visible to all other participants. In addition, our DKG must be able to tolerate roguekey attacks: that is, it must tolerate the instance where a validator maliciously chooses their share based on the value of the other validator’s shares in order to cancel out other validator’s keyshares and gain unilateral control over the resulting DKG key. One way this can be prevented is by each validator producing a proof of knowledge of their secret share.
DKG Robustness
The DKG must have robustness. The DKG should be able to tolerate a byzantine threshold of decryptors intentionally refusing to participate in the DKG round, or intentionally contributing malformed shares during DKG execution, without requiring a full restart of the DKG protocol. This is due to DoS concerns: with a naive, nonrobust implementation, a single malicious decryptor could potentially indefinitely delay the beginning of an epoch by refusing to participate in DKG or by contributing invalid shares.
The eddy construction
Distributed Key Generation
A prerequisite for flow encryption in Penumbra is some distributed key
generation algorithm. Our threshold encryption
scheme uses ElGamal and operates over the decaf377
group, thus we must choose
a DKG which operates over decaf377
outputs a decaf377
public key.
Desired Properties
Minimal Round Complexity
The DKG must be run by all validators at the start of every epoch. Ideally, the
DKG should be able to run in over a single block, such that there is no delay
between the start of the epoch and when users can delegate in the staking
system or trade on ZSwap. As such, for optimal UX and simplicity of
implementation, the optimal DKG for our scheme should have minimal round
complexity. Each round of communication can occur in the
ABCI++
vote extension phase. Since Tendermint votes already impose an O(n^2)
communication complexity for each block, minimal communication complexity is
not important for our DKG.
Verifiability and Security Under RogueKey Attacks
In addition to minimal round complexity, our DKG must be verifiable: participants (validators) must be able to verify that counterparty participants (other validators) are contributing to the DKG honestly, without the use of a trusted dealer. This can be achieved using something similar to Feldman’s Verifiable Secret Sharing protocol, where each participant shares a commitment to their share which is visible to all other participants. In addition, our DKG must be able to tolerate roguekey attacks: that is, it must tolerate the instance where a validator maliciously chooses their share based on the value of the other validator’s shares in order to cancel out other validator’s keyshares and gain unilateral control over the resulting DKG key. One way this can be prevented is by each validator producing a proof of knowledge of their secret share.
Robustness
An additional property that our DKG should have is that of robustness. The DKG should be able to tolerate a byzantine threshold of validators intentionally refusing to participate in the DKG round, or intentionally contributing malformed shares during DKG execution, without requiring a full restart of the DKG protocol. This is due to DoS concerns: with a naive, nonrobust implementation, a single malicious validator could potentially indefinitely delay the beginning of an epoch by refusing to participate in DKG or by contributing invalid shares.
A Survey of Existing DKGs
Gurkan, Jovanovic, Maller, Meiklejohn, Stern, and Tomescu present a survey of DKG instantiations with their communication complexity, public verifiablity, round complexity, prover, and verifier costs:
Note that PV, public verifiability, is slightly different than our requirement of verifiability: public verifiability means that any nonparticipating observer must be able to verify the correct execution of the DKG.
Of the schemes listed, FouqueStern has the lowest round complexity, however
the scheme uses Pallier which is not compatible with our encryption scheme.
The scheme described in that paper uses pairings, and thus is also not
compatible with our scheme. In addition, O(n*log*n)
communication complexity
is not important for our scheme, since our vote extensions already require
n^2
communication.
In addition to these schemes, ETHDKG is in use by Anoma and Osmosis in Ferveo. However, this scheme is also not applicable to our threshold encryption scheme as it is based on pairings rather than a discrete log DKG.
FROST
Komlo and Goldberg introduced FROST: Flexible RoundOptimized Schnorr
Threshold Signatures. FROST contains a DKG which is a slightly modified
version of Pedersen’s DKG, modified to protect against roguekey attacks. FROST
DKG operates on any prime order group where Decisional DiffieHellman is
difficult, and thus is compatible with decaf377
.
Verifiability
FROST DKG fulfills the requirement of verifiability and security under roguekey attacks. FROST DKG achieves this by slightly modifying Pedersen’s DKG to include a proof of knowledge of each participant’s secret in the first round of communication.
Robustness
FROST DKG trades off simplicity and efficiency in favor of robustness. A
single participant can cause the DKG to abort by contributing an invalid share
or by refusing to produce a key share. Gennaro, Jarecki, Krawczyk, and Tal
Rabin presented an alternative instantiation of Pedersen’s DKG, which
can tolerate up to nt
invalid or missing shares. This is accomplished by
adding an additional complaint round, where participants can publish evidence
of a participant’s invalid contribution and disqualify them from the DKG. A
similar strategy could be added to FROST DKG to adapt it to be robust.
Round Complexity
By including an additional round for relaying complaints from each participant to each counterparty, the round complexity of our DKG rises to 3 rounds.
ABCI++ Implementation
A prerequisite for implementing any DKG scheme in Penumbra is ABCI++, the extension to Tendermint which adds additional hooks to the Tendermint state machine interface, most importantly vote extensions.
Implementing FROST DKG with ABCI++ can be done naively by placing each round of communication in the Vote Extensions phase of ABCI++. This gives an upper bound on the delay between the start of a new epoch and the completion of the DKG as 3 blocks: 1 block per vote extension communication round.
This could potentially be pipelined by including each participant’s commitments for the next epoch (the commitments $a_{ij}∗G$ from FROST) in the block data at one of the last blocks of the previous epoch. Round 2 of FROST could occur during the vote extensions phase of the epoch transition block.
NOTE: FROST requires private communication channels for round 2, thus we
must assign validators encryption keys, perform key agreement, and encrypt
round 2 communications before placing them in the vote extension. This can be
accomplished using ECDH with decaf377
, and an AEAD cipher (though
authentication is not strictly required, since vote extensions are signed).
TODO:
[] concrete robust FROST implementation
Homomorphic Threshold Encryption
The core of the flow encryption system requires a partially homomorphic encryption scheme, which allows for users to publish transactions that contain encrypted values. These encrypted values are then aggregated, using the homomorphism, by validators. The aggregate value (the “batched flow”) is then decrypted using the threshold decryption scheme described here.
Desired Properties
For our threshold encryption scheme, we require three important properties:
 Homomorphism: we must be able to operate over ciphertexts, by combining balance commitments from many participants into a batched value.
 Verifiability: we must be able to verify that a given value $v_{i}$ was encrypted correctly to a given ciphertext $c_{i}$
 Robustness: up to $n−t$ validators must be permitted to either fail to provide a decryption share or provide in invalid decryption share.
Concrete Instantiation: Homomorphic ElGamal
Setup
Compute a lookup table $LUT$ for every $v_{i}∈[0,2_{23})$ by setting
$LUT[v_{i}]=v_{i}G$ where $G$ is the basepoint of decaf377
.
Store $LUT$ for later use in value decryption.
Value Encryption
┌───────────────────┐
│DKG Public Key (D) │
│ │
└───────────────────┘
│
┌─────────▼─────────┐ ┌──────────────┐
│ │ │ v_e │
│ │ │ (encrypted │
│ │ │ value) │
┌──────────┐ │ElGamal Encryption │ │ ┌──────────┐ │
┌─▶│v_0 (u16) │────▶ D: DKG Pubkey ├────┼▶│ c_0 │ │
│ └──────────┘ │ M = v_i*G │ │ └──────────┘ │
│ ┌──────────┐ │ e < F_q │ │ ┌──────────┐ │
┌────────────┐ ├─▶│v_1 (u16) │────▶ c_i = (e*G, M+eD) ├────┼▶│ c_1 │ │
│ │ │ └──────────┘ │ │ │ └──────────┘ │
│v [0, 2^64) │─┤ ┌──────────┐ │ │ │ ┌──────────┐ │
│ │ ├─▶│v_2 (u16) │────▶ Correctness Proof ├────┼▶│ c_2 │ │
└────────────┘ │ └──────────┘ │ σ │ │ └──────────┘ │
│ ┌──────────┐ │ │ │ ┌──────────┐ │
└─▶│v_3 (u16) │────▶ ├────┼▶│ c_3 │ │
└──────────┘ │ │ │ └──────────┘ │
│ │ │ │
│ │ │ │
└───────────────────┘ └──────────────┘
│
│ ┌────────────────────────┐
└──────────▶│proofs σ_ci = (r, s, t) │
└────────────────────────┘
A value $v$ is given by an unsigned 64bit integer $v∈[0,2_{64})$. Split $v$ into four 16bit limbs
$v_{q}=v_{0}+v_{1}2_{16}+v_{2}2_{32}+v_{3}2_{48}$ with $v_{i}∈[0,2_{16}]$.
Then, perform ElGamal encryption to form the ciphertext $v_{e}$ by taking (for each $v_{i}$)
$M_{i}=v_{i}∗G$ $e←randF_{q}$ $c_{i}=(e∗G,M_{i}+e∗D)$
Where $G$ is the basepoint generator for decaf377
, $F_{q}$ is the
large primeorder scalar field for decaf377
, and $D$ is the public key output
from DKG.
Next, compute a proof of correctness of the ElGamal encryption by executing the following sigma protocol:
$k_{1}←randF_{q}$ $k_{2}←randF_{q}$ $α=k_{1}∗G$ $γ=k_{1}∗D+k_{2}∗G$ $t=H(c_{i0},c_{i1},D,α,γ)$ $r=k_{1}−e∗t$ $s=k_{2}−v_{i}∗t$
The proof is then $σ_{c_{i}}=(r,s,t)$. The encryption of value $v$ is given as $v_{e}=[c_{1},c_{2},c_{3},c_{4}]$.
Upon receiving an encrypted value $v_{e}$ with proofs $σ_{c_{i}}$, a validator or validating full node should verify each proof $σ_{c_{i}}$ by checking
$α←G∗r+c_{i0}∗t$ $γ←D∗r+G∗s+c_{i1}∗t$ $H(c_{i0},c_{i1},D,α,γ)=?t$
Considering the value invalid if the proof fails to verify.
This protocol proves, in NIZK, the relation $R={((e,v_{i}),c_{i0},c_{i1})∈F_{q}×G:c_{i0}=eG∧c_{i1}=v_{i}G+eD}$
Showing that the ciphertext $c_{i}$ is an actual encryption of $v_{i}$ for the DKG pubkey $D$, and using the hash transcript to bind the proof of knowledge of $(e,v_{i})$ to each $c_{i}$.
Each ciphertext $c_{i}$ is two group elements, accompanied by a proof
$σ_{c_{i}}$ which is three scalars. decaf377
group elements and scalars
are encoded as 32byte values, thus every encrypted value $v_{e}$ combined with
its proof $σ_{ci}$ is $5∗32∗4$ = 640 bytes.
Value Aggregation
n (batch size)
┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐
│┌──────────────┐ ┌──────────────┐ ┌──────────────┐│
│ │ │ │ │ │
│ v_{e0} │ │ v_{e1} │ │ v_{ek} │
│ │ │ │ │ │
│ ┌──────────┐ │ │ ┌──────────┐ │ │ ┌──────────┐ │
│ │ c_0 │ │ │ │ c_0 │ │ │ │ c_0 │ │
│ └──────────┘ │ │ └──────────┘ │ │ └──────────┘ │
│ ┌──────────┐ │ │ ┌──────────┐ │ │ ┌──────────┐ │
│ │ c_1 │ │ │ │ c_1 │ │ │ │ c_1 │ │
│ └──────────┘ │ │ └──────────┘ │ │ └──────────┘ │
│ ┌──────────┐ │ │ ┌──────────┐ │ ... │ ┌──────────┐ │
│ │ c_2 │ │ │ │ c_2 │ │ │ │ c_2 │ │
│ └──────────┘ │ │ └──────────┘ │ │ └──────────┘ │
│ ┌──────────┐ │ │ ┌──────────┐ │ │ ┌──────────┐ │
│ │ c_3 │ │ │ │ c_3 │ │ │ │ c_3 │ │
│ └──────────┘ │ │ └──────────┘ │ │ └──────────┘ │
│ │ │ │ │ │
│ │ │ │ │ │
└──────────────┘ └──────────────┘ └──────────────┘
│ │ │ │
│ │ │ │
└──────────────────┴─────┬───────┴─────────────┘
│ ┌──────────────┐
▼ │ │
┌───┐ │ v_{agg} │
│ + │───────────▶ │
└───┘ │ ┌──────────┐ │
│ │ c_0 │ │
│ └──────────┘ │
│ ┌──────────┐ │
│ │ c_1 │ │
│ └──────────┘ │
│ ┌──────────┐ │
│ │ c_2 │ │
│ └──────────┘ │
│ ┌──────────┐ │
│ │ c_3 │ │
│ └──────────┘ │
│ │
│ │
└──────────────┘
To batch flows, we must use the homomorphic property of ElGamal ciphertexts. Aggregation should be done componentwise, that is, on each limb of the ciphertext ($c_{i}$). To aggregate a given $v_{e},v_{e}$, simply add each limb:
$v_{agg}=$