Technical Overview of Avalanche Proofs and Proof DelegationsTechnical Overview of Avalanche Proofs and Proof DelegationsTechnical Overview of Avalanche Proofs and Proof Delegations
Note: this is a technical article. If you are only interested in building a Proof and using it but are not interested in technical details, read the eCash Avalanche Tutorial instead.
An Avalanche Proof is a collection of data shared over the network to identify the nodes that are granted permission to vote on avalanche items. Examples of avalanche voting decisions include finalizing or invalidating blocks or transactions. Avalanche voting is secured by a Proof of Stake mechanism achieved by the Avalanche Proofs.
Content of an Avalanche Proof
- Sequence number: an arbitrary number that can be used to determine which proof is valid when multiple proofs share the same UTXO. This is not the only parameter and will be detailed below.
- Expiration time: timestamp designed to expire a proof. The first block with a MTP after the timestamp will cause the proof to be invalidated.
- Master key: this public key is the owner identifier for the proof.
- Payout script: the scriptpubkey used as a destination for the staking rewards. This can be any kind of standard script: P2PK, P2PKH, P2SH, up to n-of-3 multi-sig, and unspendable (i.e. OP_RETURN). For most users, this will be a low-level encoding of an eCash address.
- Signature: the signature of the proof (not to be confused with the signature of a stake).
- Stakes: an ordered list (sorted by stakeid) of the proof signed stakes (up to 1000). Each signed stake is made of:
- Stake: the stake itself, which contains:
- UTXO: the transaction output, itself made of the TxId and the output index for this transaction. This is used to identify the UTXO. All the UTXOs need to have at least 2016 confirmations to be considered valid.
- Amount: the XEC amount of this UTXO.
- Height: the height of the block that mined this transaction.
- IsCoinbase: whether this is a coinbase transaction.
- Pubkey: only P2PKH UTXOs can be staked, this is the public key that matches the scriptpubkey of this UTXO.
- Signature: the signature of this signed stake. More info on the signature process below.
- Stake: the stake itself, which contains:
The proof also has a score, which is computed based on the staked amount. The proof score is not part of the proof but is determined from the proof. The proof score is used to weigh the proofs during peer selection. The proof score is computed as:
score = floor(total staked amount / 1000000)
- Proofid: a proof is uniquely identified by its proofid. This is more or less a double sha256 hash of all the proof content (includes an intermediate step, see below), with the exception of the stakes signatures. This means that the proofid can be computed before the stakes are signed.
- Limitedproofid: the limited proofid is an intermediate proof identifier that does not guarantee the proof unicity. Its sole purpose is to facilitate the creation of a delegation. It is a double sha256 hash of all the proof content, with the exception of the stake signatures and the proof master public key.
- StakeId: the unique identifier of a stake (non signed). It's only used to sort the stakes. It’s a double sha256 hash of the stake content (not the signed stake, so there is no signature here).
Computing the identifiers
sha256d(x) is sha256(sha256(x))
The | operator is a concatenation operator
All the elements are serialized (e.g. lists are prepended by their length).
Beware not to confuse signed stakes with stakes: signed stake = stake + signature
limitedproofid = sha256d(sequence number | expiration time | payout script | stakes)
proofid = sha256d(limitedproofid | master key)
stakeid = sha256d(txid | index | amount | height << 1 + is_coinbase | pubkey)
There are 2 kinds of signatures: the stake signature and the proof signature. They are different because they do not commit to the same data.
The stake signature commits to the stake content, plus the proof expiration time and proof master public key. A stake signature remains valid if any other parameter of the proof changes. The sequence number and the payout script can change without invalidating the signed stakes contained in the proof. This enables proof pooling, aka merging stakes from various signers into a single proof.
The proof signature is the signature of the limited proofid. Because the signed stakes commit to the proof master key, there is no need to embed it into the proof signature as well. Should the proof master change, the signed stakes will become invalid due to the wrong signature.
stake commitment = sha256d(expiration time | proof master key)
stake signature content: sha256d(stake commitment | txid | index | amount | height << 1 + is_coinbase | pubkey)
The stake signature content is signed with a Schnorr signature using the stake private key associated with the stake public key (see stake content above). This means you need to have access to the UTXO private key to be able to sign the stake (just like you need it to spend the UTXO), and ensures you own that UTXO.
The proof signature is the proof’s limitedproofid, Schnorr signed using the private key associated with the proof master public key.
NOTE 1: Sorting of the stakes is required to make the limitedproofid deterministic. If any order could be used, the limitedproofid calculation could change with the same set of stakes in the proof and create a malleability opportunity.
NOTE 2: the use of the limitedproofid as the content of the proof signature means that the expiration time is signed twice, once by the stake and once by the proof.
A delegation is a stack of signatures that is used to delegate the right to use a proof to a public key different from the proof’s master.
Glossary: the delegator key is delegating the right to use the delegated proof to the delegated key.
Only the proof master key can sign the first level of delegation, proving that the delegated public key has the right to use it. It also gives that key the right to become the delegator for another public key, and so on.
Content of a Delegation
- Limitedproofid: the limitedproofid of the delegated proof.
- Proof master key: the master public key of the delegated proof.
- Levels: the stacked delegation levels, each containing:
- Pubkey: the delegated public key.
- Signature: the signature for this delegation level.
Of course, there is a delegationid !
This is an incremental double sha256 hash of the previous delegation level and the current delegated public key. The initial value of the delegationid is the proofid, computed from the limitedproofid and the proof master key which are both part of the delegation.
delegationid = proofid
delegationid = sha256d(delegationid | delegated public key if this delegation level)
An empty delegation is a valid delegation, but it has no purpose. In this special case the delegationid = proofid.
Since the delegation levels are stacked the signature process is incremental as well.
signature content = sha256d(delegationid | delegated public key for the new level)
The content is signed with the delegator private key for this level, and the signature can be verified against the previous level delegated public key. In order to check the delegated public key can use the proof, the receiver of a delegation will start from the last level, and verify the signature against the previous level public key, down to the first level that is verified against the proof master key.
Example of a single level delegation (most common case):
The first level delegator private key is the proof master private key. The first delegation level contains the first delegated public key and a signature. The signature can be verified against the proof master key.