Penumbra’s shielded pool can record arbitrary assets. These assets either originate on Penumbra itself, or, more commonly, originate on other IBC-connected chains. To record arbitrary assets and enforce value balance between them, we draw on ideas originally proposed for Zcash and adapt them to the Cosmos context.
To be precise, we define:
- an amount to be an untyped
u64quantity of some asset;
- an asset ID to be an element;
- a value to be a typed quantity, i.e., an amount and an asset ID.
Some asset IDs correspond to a denomination, an ADR001-style denomination trace uniquely identifying a cross-chain 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)
However, Penumbra deviates slightly from ADR001 in the definition of the asset
ID. While ADR001 defines the IBC asset ID as the SHA-256 hash of the
denomination trace, Penumbra hashes to a field element, so that asset IDs can be
more easily used inside of a zk-SNARK circuit. Specifically, define
from_le_bytes(bytes) as the function that interprets its input bytes as an
integer in little-endian order, and
hash(label, input) as BLAKE2b-512 with
label on input
input. Then asset IDs are computed as
asset_id = from_le_bytes(hash(b"Penumbra_AssetID", asset_type)) mod q
Other asset IDs do not correspond to denominations, but are computed as hashes of other state data. By making the asset ID itself be a hash of extended state data, a note recording value of that type also binds to that extended data, even though it has the same size as any other note. For instance:
- The ZSwap mechanism has the
Swapaction form a Swap NFT, whose asset ID is a Poseidon hash of the user’s secret input values address, so that when the
SwapClaimaction spends the NFT, it can (zk)prove consistency of the newly minted swap outputs with the user’s inputs and the chain’s clearing prices, and that the newly minted outputs are sent to the user’s own address.
- The LPNFT mechanism creates asset IDs that bind both a liquidity position ID and the position state, so that the value balance mechanism described below can track state changes to concentrated liquidity positions.
Each asset ID has an associated value generator . The value generator is computed as , where is a hash-to-group function constructed by first applying
rate-1 Poseidon hashing with domain separator
from_le_bytes(b"penumbra.value.generator") and then the
We use the value generator associated to an asset ID to construct homomorphic commitments to (typed) value. To do this, we first define the blinding generator as
V_tilde = decaf377_encode_to_curve(from_le_bytes(blake2b(b"decaf377-rdsa-binding")))
The commitment to value , i.e., amount of asset , with blinding factor , is the Pedersen commitment
These commitments are homomorphic, even for different asset types, say values and : Alternatively, this can be thought of as a commitment to a (sparse) vector recording the amount of every possible asset type, almost all of whose coefficients are zero.
Finally, we’d like to be able to prove that a certain value commitment is a commitment to . One way to do this would be to prove knowledge of an opening to the commitment, i.e., producing such that But this is exactly what it means to create a Schnorr signature for the verification key , because a Schnorr signature is a proof of knowledge of the signing key in the context of the message.
Therefore, we can prove that a value commitment is a commitment to by
treating it as a
decaf377-rdsa verification key and using the corresponding
signing key (the blinding factor) to sign a message. This also gives a way to
bind value commitments to a particular context (e.g., a transaction), by using
the context as the message to be signed, in order to, e.g., ensure that value
commitments cannot be replayed across transactions.