Pre-UIP: Address-linked Attestations

Pre-UIP: Address-linked Attestations

This is a client-side only change, i.e. it requires no protocol changes / network upgrades.

Problem

We want to enable an entity with a Penumbra address to demonstrate approval of an arbitrary message. Currently we don’t have a way to sign messages with Penumbra addresses.

Proposed Solution

Since Penumbra addresses are ultimately associated with a spending key, we could:

  1. Provide a way to sign an arbitrary message using decaf377-rdsa and a user’s existing spend authorization key
  2. Provide a ZKP that demonstrates that the address is derived from the spending key, and that the randomized verification key is a correct randomization of the spend verification key
  3. Define a way to bundle up the attestations which consist of a decaf377-rdsa signature, a randomized verification key, and a ZKP

1. Signing

Arbitrary byte messages $m$ will be signed using decaf377-rdsa in the SpendAuth signature domain [0]. The payload $p$ that will be signed is:

$p = \texttt{BLAKE2b-512}(\texttt{b"Penumbra_AddrAtt"} || m)$

We will demonstrate in the ZKP below that the randomized verification key is correctly derived.

2. ZKP

The Penumbra address consists of a diversifier $d$, a transmission key $pk_d$, and a clue key $ck_d$. We need to demonstrate in the proof statements that the transmission key $pk_d$ is derived via:

$pk_d = [ivk] B_d$

and:

$ivk = \texttt{poseidon_hash_2}(\texttt{from_le_bytes}(\texttt{b"penumbra.derive.ivk"}), nk, \texttt{decaf377_s}(ak)) \mod r$

where $nk$ is the nullifier-deriving key and $ak$ is the spend verification key.

We also need to demonstrate that the randomized verification key $rk$ is a correct randomization of the spend verification key $ak$ given a witnessed randomizer $\alpha$:

$rk = ak + [\alpha]B_{\texttt{SpendAuth}}$

Both of these checks are performed by the existing SpendProof [1], so we can reuse that ZKP and fill in the following data for the other witness and public input fields:

  1. The witnessed note should have zero value, an Rseed consisting of zero bytes [0u8; 32], the fixed asset ID of the Penumbra staking token, and the address that is being attested to.
  2. By using a zero value note, the note will be treated as a dummy note in the circuit. This means signers/verifiers don’t need to maintain state or provide a valid Merkle authentication path or anchor.
  3. The balance commitment can be filled in using a fixed zero blinding factor.

3. Attestation Format

The attestations $a$ will consist of:

  • a 64 bytes SpendAuth signature $\sigma$ derived as described above,
  • a 32 byte randomized verification key $rk$,
  • a 192 byte spend ZKP $\pi$.

These three components are concatenated into a 288 byte array and provided to verifiers:

$a = \sigma || rk || \pi$

Verifiers need the verification key for the spend ZKP (provided in the penumbra-proof-params crate) and the address being attested to in order to verify this attestation.

[0] Randomizable Signatures - The Penumbra Protocol
[1] Spend - The Penumbra Protocol

1 Like

This design seems good.

  1. The purpose of the Penumbra_AddrAtt string is to domain-separate signatures from any possible transaction authorization, right? Can we note that explicitly and give an explanation of why an address-linked attestation can never sign a transaction?

  2. Should we specify particular public inputs for the spend proof verification?

  3. Should we extend the CustodyService with an RPC method to perform address attestations? Should that method work on text or binary data, or should there be two different methods? (Or should there be just one and the custodian figures out whether it’s text or binary data when asking the user to authorize?)

It might be worth looking at the Ethereum ecosystem before proceeding with the design, since there’s heavy use of signing messages to authorize applications / verify ownership of a wallet.

Re Point 3. I think we should specifically make the custody RPC take in a binary field. My reasoning is that this allows a higher level protocol to have a specific encoding of the allowed attestations in some binary serialization format (i.e. protobuf) and then easily shove that inside this field.

For example, a particular wallet can decide to support a finite set of attestation use cases, and nonetheless conform to the custody RPC, by having a protobuf spec for the use cases it knows about, and then attempting to decode the binary data embedded in the generic call, and failing for unknown use cases, while also displaying use-case specific messages for confirmation by the user (e.g. “you’re trying to attest to X, which would allow Y”, "you’re trying to attest to Y, Z which would …)

That seems like a good idea. Perhaps an ecosystem norm could be:

  • The data should be a Protobuf Any (we could even require that the data be passed that way rather than as bytes?)
  • Custody backends should only allow signing of messages they understand.

WDWT?

In terms of client-side logic, zero-value notes aren’t currently filtered from the spendable note record (SNR) set at the RPC, UI, or scanning layers; instead, they are only filtered on retrieval in wasm when necessary. There’s also early design artifacts related to not supporting non-dummy spends of zero-value notes.

Our current note prioritization scheme in the planner filters zero-value notes from the note set on retrieval. To support attestations, we would need to modify this scheme to recognize attestation notes as a special case. Am I missing anything here?

Yes – these attestations are stateless, and don’t involve any actual notes. In fact this is very important, the signature would be domain-separated so that the signature can never be a signature of a transaction. Reusing the Spend proof is just a convenient way to prove that a specific rk used for signing the attestation is correctly related to the provided address.

Should we specify particular public inputs for the spend proof verification?

One hiccup: for the verifier to check the signature, they need:

  • the rk - provided in the attestation as described in the original post,
  • the Merkle root - can be anything if we use a zero-valued dummy note,
  • the balance commitment - a commitment to zero with zero blinding factor,
  • the nullifier nf - derived from the (zero) position, note commitment, and nullifier-deriving key nk associated with the address associated with the note. The verifier can’t generate this nullifier value as they lack the nk

So we’ll need to add the nullifier nf into the attestation such that the verifier can provide it as a public input to verify the spend proof. This increases the size of the attestation by 32 bytes - from 288 bytes to 320 bytes.

1 Like

wrote up the design thoughts so far in a draft UIP: UIP: Address-linked attestations by redshiftzero · Pull Request #15 · penumbra-zone/UIPs · GitHub