diff --git a/documentation/spec/.gitignore b/documentation/spec/.gitignore index 7585238efe..4e42a1bcd4 100644 --- a/documentation/spec/.gitignore +++ b/documentation/spec/.gitignore @@ -1 +1 @@ -book +book/ \ No newline at end of file diff --git a/documentation/spec/Makefile b/documentation/spec/Makefile index 804a2f00d8..c67fb98176 100644 --- a/documentation/spec/Makefile +++ b/documentation/spec/Makefile @@ -10,7 +10,8 @@ dev-deps: $(cargo) install mdbook $(cargo) install mdbook-mermaid $(cargo) install mdbook-linkcheck + $(cargo) install --git https://github.com/heliaxdev/mdbook-katex.git --rev 2b37a542808a0b3cc8e799851514e145990f1e3a $(cargo) install mdbook-open-on-gh $(cargo) install mdbook-admonish -.PHONY: build serve +.PHONY: build serve \ No newline at end of file diff --git a/documentation/spec/book.toml b/documentation/spec/book.toml index 04d0dab657..9e2663d5d3 100644 --- a/documentation/spec/book.toml +++ b/documentation/spec/book.toml @@ -1,6 +1,20 @@ [book] -authors = ["Raymond E. Pasco"] +authors = ["Heliax AG"] language = "en" multilingual = false +site-url = "https://specs.namada.net" src = "src" title = "Namada Specifications" + +[output.html] +edit-url-template = "https://github.com/anoma/namada/edit/master/documentation/spec/{path}" +git-repository-url = "https://github.com/anoma/namada" + +[output.html.search] +expand = true + +[output.katex] + +[output.linkcheck] + +[preprocessor.katex] \ No newline at end of file diff --git a/documentation/spec/src/SUMMARY.md b/documentation/spec/src/SUMMARY.md index 7390c82896..df68ce25b3 100644 --- a/documentation/spec/src/SUMMARY.md +++ b/documentation/spec/src/SUMMARY.md @@ -1,3 +1,30 @@ # Summary -- [Chapter 1](./chapter_1.md) +- [Index](./index.md) +- [Base ledger](./base-ledger.md) + - [Consensus](./base-ledger/consensus.md) + - [Execution](./base-ledger/execution.md) + - [Governance](./base-ledger/governance.md) +- [Multi-asset shielded pool](./masp.md) + - [Ledger integration](./masp/ledger-integration.md) + - [Asset type](./masp/asset-type.md) + - [Burn & mint](./masp/burn-and-mint.md) + - [Convert circuit](./masp/convert-circuit.md) + - [Trusted setup](./masp/trusted-setup.md) +- [Interoperability](./interoperability.md) + - [Ethereum bridge](./interoperability/ethereum-bridge.md) + - [IBC](./interoperability/ibc.md) +- [Economics](./economics.md) + - [Fee system](./economics/fee-system.md) + - [Inflation system](./economics/inflation-system.md) + - [Proof-of-stake](./economics/proof-of-stake.md) + - [Bonding mechanism](./economics/proof-of-stake/bonding-mechanism.md) + - [Cubic slashing](./economics/proof-of-stake/cubic-slashing.md) + - [Reward distribution](./economics/proof-of-stake/reward-distribution.md) + - [Shielded pool incentives](./economics/shielded-pool-incentives.md) + - [Public goods funding](./economics/public-goods-funding.md) +- [User interfaces](./user-interfaces.md) + - [Web wallet](./user-interfaces/web-wallet-interface.md) + - [Web explorer](./user-interfaces/web-explorer-interface.md) + - [External integrations](./user-interfaces/external-integrations.md) +- [Further readhing](./further-reading.md) \ No newline at end of file diff --git a/documentation/spec/src/base-ledger.md b/documentation/spec/src/base-ledger.md new file mode 100644 index 0000000000..b345208e58 --- /dev/null +++ b/documentation/spec/src/base-ledger.md @@ -0,0 +1,3 @@ +## Base ledger + +The base ledger of Namada includes a [consensus system](./base-ledger/consensus.md), validity predicate-based [execution system](./base-ledger/execution.md), and signalling-based [governance mechanism](./base-ledger/governance.md). Namada's ledger also includes proof-of-stake, slashing, fees, and inflation funding for staking rewards, shielded pool incentives, and public goods -- these are specified in the [economics section](./economics.md). \ No newline at end of file diff --git a/documentation/spec/src/base-ledger/consensus.md b/documentation/spec/src/base-ledger/consensus.md new file mode 100644 index 0000000000..668169dffa --- /dev/null +++ b/documentation/spec/src/base-ledger/consensus.md @@ -0,0 +1,3 @@ +# Consensus + +Namada uses [Tendermint Go](https://github.com/tendermint/tendermint) through the [tendermint-rs](https://github.com/heliaxdev/tendermint-rs) bindings in order to provide peer-to-peer transaction gossip, BFT consensus, and state machine replication for Namada's custom state machine. \ No newline at end of file diff --git a/documentation/spec/src/base-ledger/execution.md b/documentation/spec/src/base-ledger/execution.md new file mode 100644 index 0000000000..16e7c25257 --- /dev/null +++ b/documentation/spec/src/base-ledger/execution.md @@ -0,0 +1,29 @@ +# Execution + +The Namada ledger execution system is based on an initial version of the [Anoma protocol](https://specs.anoma.net). The system implements a generic computational substrate with WASM-based transactions and validity predicate verification architecture, on top of which specific features of Namada such as IBC, proof-of-stake, and the MASP are built. + +## Namada ledger + +The Namada ledger is built on top of [Tendermint](https://docs.tendermint.com/master/spec/)'s [ABCI](https://docs.tendermint.com/master/spec/abci/) interface with a slight deviation from the ABCI convention: in Namada, the transactions are currently *not* being executed in ABCI's `DeliverTx` method, but rather in the `EndBlock` method. The reason for this is to prepare for future DKG and threshold decryption integration, which has not yet been fully finished and hence is out-of-scope for the initial release version of Namada. + +The ledger features an account-based system (in which UTXO-based systems such as the MASP can be internally implemented as specific accounts), where each account has a unique address and a dynamic key-value storage sub-space. Every account in Namada is associated with exactly one validity predicate. Fungible tokens, for example, are accounts, whose rules are governed by their validity predicates. Many of the base ledger subsystems specified here are themselves just special Namada accounts too (e.g. PoS, IBC and MASP). + +Interaction with the Namada ledger are made possible via transactions (note [transaction whitelist](#transaction-and-validity-predicate-whitelist)). Please refer to the [protocol section](https://docs.anoma.network/master/specs/ledger.html#the-protocol) that specifies the transaction execution model. In Namada, transactions are allowed to perform arbitrary modifications to the storage of any account, but the transaction will be accepted and state changes applied only if all the validity predicates that were triggered by the transaction accept it. That is, the accounts whose storage sub-spaces were touched by the transaction and/or an account that was explicitly elected by the transaction as the verifier will all have their validity predicates verifying the transaction. A transaction can add any number of additional verifiers, but cannot remove the ones determined by the protocol. For example, a transparent fungible token transfer would typically trigger 3 validity predicates - those of the token, source and target addresses. + +## Supported validity predicates + +Conceptually, a VP is a function from the transaction's data and the storage state prior and posterior to a transaction execution returning a boolean value. A transaction may modify any data in the accounts' dynamic storage sub-space. Upon transaction execution, the VPs associated with the accounts whose storage has been modified are invoked to verify the transaction. If any of them reject the transaction, all of its storage modifications are discarded. While the execution model is fully programmable, for Namada only a selected subset of provided validity predicates and transactions will be permitted through pre-defined whitelists configured at network launch. + +There are some native VPs for internal transparent addresses that are built into the ledger. All the other VPs are implemented as WASM programs. One can build a custom VP using the [VP template](https://github.com/anoma/anoma/tree/master/wasm/vp_template) or use one of the pre-defined VPs. + +Supported validity predicates for Namada: +- Native + - Proof-of-stake (see [spec](../economics/proof-of-stake.md)) + - IBC & IbcToken (see [spec](../interoperability/ibc.md)) + - Governance (see [spec](./governance.md)) + - Protocol parameters +- WASM + - Fungible token + - MASP (see [spec](../masp.md)) + - Implicit account VP (allows cryptographic signature authorization) + - k-of-n multisignature VP \ No newline at end of file diff --git a/documentation/spec/src/base-ledger/governance.md b/documentation/spec/src/base-ledger/governance.md new file mode 100644 index 0000000000..c6ca1691c7 --- /dev/null +++ b/documentation/spec/src/base-ledger/governance.md @@ -0,0 +1,261 @@ +# Namada Governance + +Namada introduces a governance mechanism to propose and apply protocol changes with or without the need for an hard fork. Anyone holding some `NAM` will be able to propose some changes to which delegators and validators will cast their `yay` or `nay` votes. Governance on Namada supports both `signaling` and `voting` mechanisms. The difference between the the two is that the former is needed when the changes require an hard fork. In cases where the chain is not able to produce blocks anymore, Namada relies on [off chain](#off-chain-protocol) signaling to agree on a common move. + +## On-chain protocol + +### Governance Address +Governance adds 2 internal addresses: +- GovernanceAddress +- TreasuryAddress + +The first address contains all the proposals under its address space. +The second address holds the funds of rejected proposals. + +### Governance storage +Each proposal will be stored in a sub-key under the internal proposal address. The storage keys involved are: + +``` +/$GovernanceAddress/proposal/$id/content : Vec +/$GovernanceAddress/proposal/$id/author : Address +/$GovernanceAddress/proposal/$id/start_epoch: Epoch +/$GovernanceAddress/proposal/$id/end_epoch: Epoch +/$GovernanceAddress/proposal/$id/grace_epoch: Epoch +/$GovernanceAddress/proposal/$id/proposal_code: Option> +/$GovernanceAddress/proposal/$id/funds: u64 +``` + +`Author` address field will be used to credit the locked funds if the proposal is approved. + +The `content` value should follow a standard format. We leverage something similar to what is described in the [BIP2](https://github.com/bitcoin/bips/blob/master/bip-0002.mediawiki#bip-format-and-structure) document: + +```json +{ + "title": "", + "authors": " ", + "discussions-to": "", + "created": "", + "license": "", + "abstract": "", + "motivation": "", + "details": " - optional field", + "requires": " - optional field", +} +``` + +`GovernanceAddress` parameters and global storage keys are: + +``` +/$GovernanceAddress/?: Vec +/$GovernanceAddress/counter: u64 +/$GovernanceAddress/min_proposal_fund: u64 +/$GovernanceAddress/max_proposal_code_size: u64 +/$GovernanceAddress/min_proposal_period: u64 +/$GovernanceAddress/max_proposal_content_size: u64 +/$GovernanceAddress/min_proposal_grace_epochs: u64 +/$GovernanceAddress/pending/$proposal_id: u64 + +``` + +`counter` is used to assign a unique, incremental ID to each proposal.\ +`min_proposal_fund` represents the minimum amount of locked tokens to submit a proposal.\ +`max_proposal_code_size` is the maximum allowed size (in bytes) of the proposal wasm code.\ +`min_proposal_period` sets the minimum voting time window (in `Epoch`).\ +`max_proposal_content_size` tells the maximum number of characters allowed in the proposal content.\ +`min_proposal_grace_epochs` is the minimum required time window (in `Epoch`) between `end_epoch` and the epoch in which the proposal has to be executed. +`/$GovernanceAddress/pending/$proposal_id` this storage key is written only before the execution of the the code defined in `/$GovernanceAddress/proposal/$id/proposal_code` and deleted afterwards. Since this storage key can be written only by the protocol itself (and by no other means), VPs can check for the presence of this storage key to be sure that a a proposal_code has been executed by the protocol and not by a transaction. + +The governance machinery also relies on a subkey stored under the `NAM` token address: + +``` +/$NAMAddress/balance/$GovernanceAddress: u64 +``` + +This is to leverage the `NAM` VP to check that the funds were correctly locked. +The governance subkey, `/$GovernanceAddress/proposal/$id/funds` will be used after the tally step to know the exact amount of tokens to refund or move to Treasury. + +### GovernanceAddress VP +Just like Pos, also governance has his own storage space. The `GovernanceAddress` validity predicate task is to check the integrity and correctness of new proposals. A proposal, to be correct, must satisfy the followings: +- Mandatory storage writes are: + - counter + - author + - funds + - voting_start epoch + - voting_end epoch + - grace_epoch +- Lock some funds >= `MIN_PROPOSAL_FUND` +- Contains a unique ID +- Contains a start, end and grace Epoch +- The difference between StartEpoch and EndEpoch should be >= `MIN_PROPOSAL_PERIOD * constant`. +- Should contain a text describing the proposal with length < `MAX_PROPOSAL_CONTENT_SIZE` characters. +- Vote can be done only by a delegator or validator +- Validator can vote only in the initial 2/3 of the whole proposal duration (`EndEpoch` - `StartEpoch`) +- Due to the previous requirement, the following must be true,`(EndEpoch - StartEpoch) % 3 == 0` +- If defined, `proposalCode` should be the wasm bytecode rappresentation of the changes. This code is triggered in case the proposal has a position outcome. +- `GraceEpoch` should be greater than `EndEpoch` of at least `MIN_PROPOSAL_GRACE_EPOCHS` + +`MIN_PROPOSAL_FUND`, `MAX_PROPOSAL_CODE_SIZE`, `MIN_PROPOSAL_GRACE_EPOCHS`, `MAX_PROPOSAL_CONTENT_SIZE` and `MIN_PROPOSAL_PERIOD` are parameters of the protocol. +Once a proposal has been created, nobody can modify any of its fields. +If `proposalCode` is `Empty` or `None` , the proposal upgrade will need to be done via hard fork. + +It is possible to check the actual implementation [here](https://github.com/anoma/namada/blob/master/shared/src/ledger/governance/mod.rs#L69). + +Example of `proposalCode` could be: +- storage writes to change some protocol parameter +- storage writes to restore a slash +- storage writes to change a non-native vp + +This means that corresponding VPs need to handle these cases. + +### Proposal Transactions + +The proposal transaction will have the following structure, where `author` address will be the refund address. + +```rust +struct OnChainProposal { + id: u64 + content: Vec + author: Address + votingStartEpoch: Epoch + votingEndEpoch: Epoch + graceEpoch: Epoch + proposalCode: Option> +} +``` + +### Vote transaction + +Vote transactions have the following structure: + +```rust +struct OnChainVote { + id: u64 + voter: Address + yay: bool +} +``` + +Vote transaction creates or modify the following storage key: + +``` +/$GovernanceAddress/proposal/$id/vote/$delegation_address/$voter_address: Enum(yay|nay) +``` + +The storage key will only be created if the transaction is signed either by a validator or a delagator. +Validators will be able to vote only for 2/3 of the total voting period, while delegators can vote until the end of the voting period. + +If a delegator votes opposite to its validator, this will *override* the corresponding vote of this validator (e.g. if a delegator has a voting power of 200 and votes opposite to the delegator holding these tokens, than 200 will be subtracted from the votig power of the involved validator). + +As a small form of space optimization, if a delegator votes accordingly to its validator, the vote will not actually be submitted to the chain. This logic is applied only if the following conditions are satisfied: + +- The transaction is not being forced +- The vote is submitted in the last third of the voting period (the one exclusive to delegators). This second condition is necessary to prevent a validator from changing its vote after a delegator vote has been submitted, effectively stealing the delegator's vote. + +### Tally +At the beginning of each new epoch (and only then), in the `FinalizeBlock` event, tallying will occur for all the proposals ending at this epoch (specified via the `endEpoch` field). +The proposal has a positive outcome if 2/3 of the staked `NAM` total is voting `yay`. Tallying is computed with the following rules: + +- Sum all the voting power of validators that voted `yay` +- For any validator that voted `yay`, subtract the voting power of any delegation that voted `nay` +- Add voting power for any delegation that voted `yay` (whose corresponding validator didn't vote `yay`) +- If the aformentioned sum divided by the total voting power is >= 0.66, the proposal outcome is positive otherwise negative. + +All the computation above must be made at the epoch specified in the `end_epoch` field of the proposal. + +It is possible to check the actual implementation [here](https://github.com/anoma/namada/blob/master/shared/src/ledger/governance/utils.rs#L68). + +### Refund and Proposal Execution mechanism +Together with tallying, in the first block at the beginning of each epoch, in the `FinalizeBlock` event, the protocol will manage the execution of accepted proposals and refunding. For each ended proposal with a positive outcome, it will refund the locked funds from `GovernanceAddress` to the proposal author address (specified in the proposal `author` field). For each proposal that has been rejected, instead, the locked funds will be moved to the `TreasuryAddress`. Moreover, if the proposal had a positive outcome and `proposalCode` is defined, these changes will be executed right away. + +If the proposal outcome is positive and current epoch is equal to the proposal `grace_epoch`, in the `FinalizeBlock` event: +- transfer the locked funds to the proposal author +- execute any changes to storage specified by `proposalCode` + +In case the proposal was rejected or if any error, in the `FinalizeBlock` event: +- transfer the locked funds to `TreasuryAddress` + +**NOTE**: we need a way to signal the fulfillment of an accepted proposal inside the block in which it is applied to the state. We could do that by using `Events` https://github.com/tendermint/tendermint/blob/ab0835463f1f89dcadf83f9492e98d85583b0e71/docs/spec/abci/abci.md#events (see https://github.com/anoma/namada/issues/930). + +## TreasuryAddress +Funds locked in `TreasuryAddress` address should be spendable only by proposals. + +### TreasuryAddress storage +``` +/$TreasuryAddress/max_transferable_fund: u64 +/$TreasuryAddress/?: Vec +``` + +The funds will be stored under: +``` +/$NAMAddress/balance/$TreasuryAddress: u64 +``` + +### TreasuryAddress VP +The treasury validity predicate will approve a trasfer only if: +- the transfer has been made by the protocol (by checking the existence of `/$GovernanceAddress/pending/$proposal_id` storage key) +- the transfered amount is <= `MAX_SPENDABLE_SUM` + +`MAX_SPENDABLE_SUM` is a parameter of the treasury native vp. + +It is possible to check the actual implementation [here](https://github.com/anoma/namada/blob/master/shared/src/ledger/treasury/mod.rs#L55). + + +## ParameterAddress +Protocol parameter are described under the `$ParameterAddress` internal address. + +### ParameterAddress storage +``` +/$ParamaterAddress/: String +/$ParamaterAddress/?: Vec +``` + +At the moment there are 5 parameters: +- `max_expected_time_per_block` +- `vp_whitelist` +- `tx_whitelist` +- `epoch_duration` + +### ParameterAddress VP +The parameter validity predicate will approve changes to the protocol parameter only if: +- the changes have been made by the protocol (by checking the existence of `/$GovernanceAddress/pending/$proposal_id` storage key) + +It is possible to check the actual implementation [here](https://github.com/anoma/namada/blob/master/shared/src/ledger/parameters/mod.rs#L53). + + +## Off-chain protocol + +### Create proposal +A CLI command to create a signed JSON rappresentation of the proposal. The JSON will have the following structure: +``` +{ + content: Base64>, + author: Address, + votingStart: TimeStamp, + votingEnd: TimeStamp, + signature: Base64> +} +``` + +The signature is produced over the hash of the concatenation of: `content`, `author`, `votingStart` and `votingEnd`. + +### Create vote + +A CLI command to create a signed JSON rappresentation of a vote. The JSON will have the following structure: +``` +{ + proposalHash: Base64>, + voter: Address, + signature: Base64, + vote: Enum(yay|nay) +} +``` + +The proposalHash is produced over the concatenation of: `content`, `author`, `votingStart`, `votingEnd`, `voter` and `vote`. + +### Tally +Same mechanism as [on chain](#tally) tally but instead of reading the data from storage it will require a list of serialized json votes. + +## Interfaces + +- Ledger CLI +- Wallet \ No newline at end of file diff --git a/documentation/spec/src/chapter_1.md b/documentation/spec/src/chapter_1.md deleted file mode 100644 index b743fda354..0000000000 --- a/documentation/spec/src/chapter_1.md +++ /dev/null @@ -1 +0,0 @@ -# Chapter 1 diff --git a/documentation/spec/src/economics.md b/documentation/spec/src/economics.md new file mode 100644 index 0000000000..0af4869840 --- /dev/null +++ b/documentation/spec/src/economics.md @@ -0,0 +1,3 @@ +## Economics + +Namada's economic model is based around a single native token, NAM, which is controlled by the protocol. Users pay transaction fees in NAM, with the gas price adjusted on a sliding P control (aka EIP 1559, described further in [fee system](./economics/fee-system.md), so demand for NAM can be expected to track demand for block space. Half of fees are paid to block producers and half are burnt. On the supply side, the protocol mints NAM at a fixed maximum per-annum rate based on a fraction of the current supply (see [inflation system](./economics/inflation-system.md)), which is directed to three areas of protocol subsidy: [proof-of-stake](./economics/proof-of-stake.md), [shielded pool incentives](./economics/shielded-pool-incentives.md), and [public-goods funding](./economics/public-goods-funding.md). Inflation rates for these three areas are adjusted independently (the first two on PD controllers and the third based on funding decisions) and excess tokens are slowly burned. \ No newline at end of file diff --git a/documentation/spec/src/economics/fee-system.md b/documentation/spec/src/economics/fee-system.md new file mode 100644 index 0000000000..2c7b4758fb --- /dev/null +++ b/documentation/spec/src/economics/fee-system.md @@ -0,0 +1,20 @@ +## Fee system + +In order to be accepted by the Namada ledger, transactions must pay fees in NAM. Transaction fees serve two purposes: first, the efficient allocation of block space given permissionless transaction submission and varying demand, and second, incentive-compatibility to encourage block producers to add transactions to the blocks which they create and publish. + +Namada follows a [tipless version](https://arxiv.org/pdf/2106.01340.pdf) of the EIP 1559 scheme. In contrast with the original EIP 1559, the transaction fee of this tipless version consists solely of a base fee, with no tip. The base fee increases whenever blocks are fuller than the desired capacity and decreases when the blocks haven't reached this capacity (i.e. a P-controller). Namada uses a target block fullness of 0.5 (adjustable by governance). + +To provide an incentive for the inclusion of transactions by proposers, Namada transfers 50% of the base fee to the next few block proposers, proportional to block fullness. For example, if the block is 100% full, the proposer will receive full fees, whereas if the block is only 25% full, they will only receive 25% of the fees. These fees are kept in a temporary account, with at most a tenth used to pay out the current proposer. + +The other 50% of the base fee is immediately burned, reducing the total supply of NAM by the amount burned. + +Base fees are changed to reflect changes in demand, with a smoothing rate to reduce the frequency at which transaction authors need to calculate required fees. Namada requires a minimum of twenty (20) blocks between base fee changes and a delay of ten (10) blocks before a base fee change is applied. Each change of the base fee follows the function below: + +$$ +Tx_{fee}'=Tx_{fee}*(1+ch_{max}(F-0.5)) +$$ +where $Tx_{fee}$ is the previous transaction fee, $Tx_{fee}'$ is the new transcation fee, $ch_{max}$ is the max change the transaction fee can have, and $F$ is the block fullness. We decided that our target block fullness is 50 %. + +![](https://i.imgur.com/p3qeWw3.jpg) + +In Namada, the base fee is applied as a gas price, where the total fee of a particular transaction will be equal to the product of the base fee and consumed gas. \ No newline at end of file diff --git a/documentation/spec/src/economics/images/inflation.png b/documentation/spec/src/economics/images/inflation.png new file mode 100644 index 0000000000..54f5b79f32 Binary files /dev/null and b/documentation/spec/src/economics/images/inflation.png differ diff --git a/documentation/spec/src/economics/images/inflation.svg b/documentation/spec/src/economics/images/inflation.svg new file mode 100644 index 0000000000..6dfee62807 --- /dev/null +++ b/documentation/spec/src/economics/images/inflation.svg @@ -0,0 +1,2021 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/documentation/spec/src/economics/images/locking.png b/documentation/spec/src/economics/images/locking.png new file mode 100644 index 0000000000..cfb05d16fd Binary files /dev/null and b/documentation/spec/src/economics/images/locking.png differ diff --git a/documentation/spec/src/economics/images/locking.svg b/documentation/spec/src/economics/images/locking.svg new file mode 100644 index 0000000000..68f7f318cf --- /dev/null +++ b/documentation/spec/src/economics/images/locking.svg @@ -0,0 +1,1526 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/documentation/spec/src/economics/inflation-system.md b/documentation/spec/src/economics/inflation-system.md new file mode 100644 index 0000000000..ccc27e9b33 --- /dev/null +++ b/documentation/spec/src/economics/inflation-system.md @@ -0,0 +1,126 @@ +# Inflation system + +## Token flow + +The protocol controls Namada token NAM (the native staking token) sourced from two locations: + +- Fees paid for transactions per the description in [fee system](./fee-system.md), 50 % goes to block production and 50 % goes to treasury. +- Inflation (described below), as in tokens directly printed by the protocol (which we can do arbitrarily), where these tokens then flow to many different sinks: + +1. Proof-of-stake rewards, which are paid into the reward distribution mechanism in order to distribute them to validators and delegators. +2. Shielded pool rewards, which are locked in a way such that they can be eventually paid to users who kept tokens in the shielded pool. +3. A governance pool - aka treasury. + - These tokens are slowly burned at a fixed fraction per epoch. +4. A set of configurable custom sinks, which can be addresses on Namada, addresses on Ethereum (over the Ethereum bridge), or addresses on other chains connected over IBC. + - These can be paid fixed amounts per epoch. + - Initial recipients will be configured at genesis, and recipients can be added, removed, or altered by Namada governance. + +## Token Inflation +In general, inflation refers to the process of a currency losing its purchasing power over time. While this is a classical economic phenomenon, the way cryptocurrencies are produced permits great control over money supply, and doing so cleverly can have positive effects such as increasing incentives. The Namada inflation model depends on several factors, such as ratio between locked and liquid tokens (described below), the multi-asset shielded pool, and funds for treasury. + +When validators are selected they need to be backed by funds. These funds are locked for the duration of an epoch and 21 days after the epoch has ended. Locked tokens help secure the system while liquidity supports its activity and liveness. We need to choose the ratio between locked and liquid tokens carefully. Liquid tokens make sure the price of the token is not increasing out of scarcity and users have access to tokens to pay transaction fees, while locked tokens are the guarantee that attacking the system is expensive for an adversary. + +Here are some numbers from other projects + +| Blockchain platform | Approximate locking % | +|--------------------------------------------------|------| +| Cosmos | 66.7 | +| Polkadot | 50 | +| Ethereum | 47 | +| Solana | 77 | + + +Our desired percentage for Namada is 33%-66%: Locked for validating and the rest %33-%66 is liquid. When the price of the token is low we can aim for a higher % of locked tokens and reduce this as the price and demand for liquid tokens increases. For example, we can set a range, in the beginning have 50 % and later aim for 1/3. I don't think we should go lower than that. The staking reward should be ideally set. + + + + +The privacy that MASP is providing depends on the asset in the shielded pool. A transaction can only be private if it can hide among other transactions, hence more funds and activity in the shielded pool increase privacy for transactions. + +The Treasury is a pool of native tokens that can be appropriated for funding public-good products for Namada. The decision on spending these funds will be assigned to governance. + +### Related work +Ethereum 2.0, Solana, and Near protocols inflation rate are independent of how much tokens are staked. Near protocol and Ethereum 2.0 have fixed inflation rates, while Solana start with a high inflation rate that decreases over time, as less transaction fees are burned. + +In Polkadot and Cosmos the total inflation rate that is paid as rewards to validators depends on the staking ratio. This is to incentivize validators and delegators to invest in the staking pool. We will follow the same idea and have inflation vary depending on our target staking ratio. Here is how we achieve that. + +For funds going to treasury Near protocol where 5 % goes to treasury and Polkadot sends the difference between inflation for PoS and the total constant inflation to treasury. + +### Model + +Let us assume $T$ is the total token supply and $I$ is the total inflation of Namada. + +$$I=\frac{T_\textrm{end of year}-T_\textrm{beginning of year}}{T_\textrm{beginning of year}}$$ + +The total inflation consists of several components as follows. + +$$I=I_{PoS}+I_L+I_T-D_T$$ + +where $I_T$ is our inflation that goes to treasury, $I_{PoS}$ is inflation that is paid as PoS rewards, and $I_L$ is the inflation for locking that is paid to accounts in shielded pool. We can extend the $I_L$ be extended to be for many other types of $I_L1,...,I_Ln$. For simplicity we only assume to have one $I_L$. $D_T$ is the constant deflation of the treasury. This is applied to incentivize governance voters to spend treasury funds. + +These components are each varying depending on independent factors as follows. The $I_{PoS}$ depends on the staking ratio $R(t)$. The locking inflation $I_L$ depends on the locking ratio $L(t)$. Ideally we want the total token supply to consist of tokens locked for staking and shielded pool and the rest are liquid tokens $Y$. + +$$T=T*R_{target}+T*L_{target}+Y$$ + +where $R_{target}$ is the target staking ratio and $L_{target}$ is the target locking of assets in the shielded pool. + +We assume further assume $I_{target}$ is our target total inflation that we want to achieve on the long term, where we split it up into $I_{PoS,target}$ and $I_{L,target}$ for staking and locking respectivly. + +We define $I_{PoS}$ as a PD controller as follows. + +$$A(t)=K_1(R(t)-R_{target})+K_2(\frac{dR}{dt})$$ + +If $I_{PoS}^{min}< I_{PoS}< I_{PoS}^{max}$ then $\frac{dI_{PoS}}{dt}=A(t)$. + +If $I_{PoS}{min}< I_{PoS}$ then $\frac{dI_{PoS}}{dt}=max(A(t),0$. + +If $I_{PoS}< I_{PoS}^{max}$ then $\frac{dI_{PoS}}{dt}=min(A(t),0)$. + +For $I_{PoS}^{min}=0.05$, $I_{PoS}^{max}=0.15$, $I_{PoS,target}=0.10$, and $R_{target}=0.50$ we set $K_1=-0.01$ and $K_2=-0.2$. Lets review what these parameters give us with examples as follows. + +**Example 1:** If $I= I_{PoS,target}=0.10$ and $R_{target}=0.50$, but then $R$ drops quickly to $0.25$, then the effect of the $K_2$ term will be to increase $I_{PoS}$ by $-0.2 \times -0.25=0.05$ and inflation will hit its maximum value of $0.15$. Changes in $R$ smaller than $0.25$ will not cause inflation to hit its maximum or minimum quickly. + +**Example 2:** If $I_{PoS}=0.05$, but $R$ holds steady at $0.40$, then $K_1$ term will cause $I$ to increase by $-0.01 \times -0.10=0.001$ per day/epoch. $I_{PoS}$ will take 100 days to reach its maximum. This is slow compared to the unbonding period, allowing delegators time to react. + + +--- + +We define $I_{L}$ as a PD controller follows. + +$$A(t)=K_1(L(t)-L_{target})+K_2(\frac{dL}{dt})$$ + +If $I_{L}^{min}< I_{L}< I_{L}^{max}$ then $\frac{dI_{L}}{dt}=A(t)$. + +If $I_{L}^{min}< I_{L}$ then $\frac{dI_{L}}{dt}=max(A(t),0$. + +If $I_{L}< I_{L}^{max}$ then $\frac{dI_{L}}{dt}=min(A(t),0)$. + +For $I_{L}^{min}=0.03$, $I_{L}^{max}=0.07$, $I_{L,target}=0.05$, and $L_{target}=0.30$ we set $K_1=-0.05$ and $K_2=-0.1$. Lets review what these parameters give us with examples as follows. + +**Example 1:** If $I= I_{L,target}=0.05$ and $L_{target}=0.30$, but then $L$ drops quickly to $0.15$, then the effect of the $K_2$ term will be to increase $I_L$ by $-0.1 \times -0.15=0.015$ and inflation will hit $0.065$ which is short of its maximum value of $0.07$. Changes in $L$ smaller than $0.15$ will not cause inflation to hit its maximum or minimum quickly. + +**Example 2:** If $I_{L}=0.03$, but $L$ holds steady at $0.20$, then $K_1$ term will cause $I_L$ to increase by $-0.05 \times -0.10=0.005$ per day/epoch. $I_{L}$ will take 8 days to reach its maximum. + +TODO: Why we chose those min and max values. +TODO: Dt and It based on Chris proposal + +The ratio between staking and locking in the shielded pool is a trade off between security, privacy, and liveness. A higher staking ratio means more security, a higher locking ratio means more privacy, and if both are too high there wont be enough liquidity for transactions. It would be easier to consider these separately, for example, setting the target staking ratio to 50 % and the target locking ratio to 30 %. + +The funds minted for the treasury is a constant %, for example 1 %. Same goes for $D_T$. + +We need to define $I_{PoS}^{max}$, $I_{L}^{max}$, and $I_{T}$ to bound total inflation. + +$$I_{PoS}^{max}+I_{L}^{max}+I_T=< I^{max}$$ + +The sum of $I_L$ and other $I_L1, ..., I_Ln$ will also be limited. If their sum would exceed the limit, then we need to scale them down to stay within the limit. + +These bounds on $I_{PoS}$ and $I_L$ give us a min and max bound on the total inflation, where the total inflation depends on $L_{target}$ and $R_{target}$ independently. + + diff --git a/documentation/spec/src/economics/proof-of-stake.md b/documentation/spec/src/economics/proof-of-stake.md new file mode 100644 index 0000000000..7e9d01eba7 --- /dev/null +++ b/documentation/spec/src/economics/proof-of-stake.md @@ -0,0 +1,20 @@ +# Proof-of-stake (PoS) + +This section of the specification describes the proof-of-stake mechanism of Namada, which is largely modeled after [Cosmos bonded proof-of-stake](https://github.com/cosmos/cosmos-sdk/blob/master/x/staking/spec/README.md), but makes significant changes to bond storage representation, validator set change handling, reward distribution, and slashing, with the general aims of increased precision in reasoning about security, validator decentralisation, and avoiding unnecessary proof-of-stake-related transactions. + +This section is split into three subcomponents: the [bonding mechanism](./proof-of-stake/bonding-mechanism.md), [reward distribution](./proof-of-stake/reward-distribution.md), and [cubic slashing](./proof-of-stake/cubic-slashing.md). + +## Introduction + +Blockchain system rely on economic security to prevent abuse and for actors to behave according to protocol. The aim is that economic incentive promote correct and long-term operation of the system and economic punishments would discourage diverting from correct protocol execution either by mistake or with the intent to carrying out attacks. Many PoS blockcains rely on the 1/3 Byzantine rule, where they make the assumption the adversary cannot control more 2/3 of the total stake or 2/3 of the actors. + +## Goals of Rewards and Slashing: Liveness and Security + +* **Security: Delegation and Slashing**: we want to make sure validators backed by enough funds to make misbehaviour very expensive. Security is achieved by punishing (slashing) if they do. *Slashing* locked funds (stake) intends to disintensivize diverting from correct execution of protocol, which is this case is voting to finalize valid blocks. +* **Liveness: Paying Rewards**. For continued operation of Namada we want to incentivize participating in consensus and delegation, which helps security. + +### Security + +In blockchain system we do not rely on altruistic behavior but rather economic security. We expect the validators to execute the protocol correctly. They get rewarded for doing so and punished otherwise. Each validator has some self-stake and some stake that is delegated to it by other token holders. The validator and delegators share the reward and risk of slashing impact with each other. + +The total stake behind consensus should be taken into account when value is transferred via a transaction. The total value transferred cannot exceed 2/3 of the total stake. For example, if we have 1 billion tokens, we aim that 300 Million of these tokens is backing validators. This means that users should not transfer more than 200 million of this token within a block. \ No newline at end of file diff --git a/documentation/spec/src/economics/proof-of-stake/bonding-mechanism.md b/documentation/spec/src/economics/proof-of-stake/bonding-mechanism.md new file mode 100644 index 0000000000..5d7a25ef2e --- /dev/null +++ b/documentation/spec/src/economics/proof-of-stake/bonding-mechanism.md @@ -0,0 +1,302 @@ +# Bonding mechanism + +## Epoch + +An epoch is a range of blocks or time that is defined by the base ledger and made available to the PoS system. This document assumes that epochs are identified by consecutive natural numbers. All the data relevant to PoS are [associated with epochs](#epoched-data). + +### Epoched data + +Epoched data are data associated with a specific epoch that are set in advance. The data relevant to the PoS system in the ledger's state are epoched. Each data can be uniquely identified. These are: +- [System parameters](#system-parameters). A single value for each epoch. +- [Active validator set](#active-validator-set). A single value for each epoch. +- Total voting power. A sum of all active and inactive validators' voting power. A single value for each epoch. +- [Validators' consensus key, state and total bonded tokens](#validator). Identified by the validator's address. +- [Bonds](#bonds) are created by self-bonding and delegations. They are identified by the pair of source address and the validator's address. + +Changes to the epoched data do not take effect immediately. Instead, changes in epoch `n` are queued to take effect in the epoch `n + pipeline_length` for most cases and `n + unboding_length` for [unbonding](#unbond) actions. Should the same validator's data or same bonds (i.e. with the same identity) be updated more than once in the same epoch, the later update overrides the previously queued-up update. For bonds, the token amounts are added up. Once the epoch `n` has ended, the queued-up updates for epoch `n + pipeline_length` are final and the values become immutable. + +## Entities + +- [Validator](#validator): An account with a public consensus key, which may participate in producing blocks and governance activities. A validator may not also be a delegator. +- [Delegator](#delegator): An account that delegates some tokens to a validator. A delegator may not also be a validator. + +Additionally, any account may submit evidence for [a slashable misbehaviour](#slashing). + +### Validator + +A validator must have a public consensus key. Additionally, it may also specify optional metadata fields (TBA). + +A validator may be in one of the following states: +- *inactive*: + A validator is not being considered for block creation and cannot receive any new delegations. +- *candidate*: + A validator is considered for block creation and can receive delegations. + +For each validator (in any state), the system also tracks total bonded tokens as a sum of the tokens in their self-bonds and delegated bonds. The total bonded tokens determine their voting voting power by multiplication by the `votes_per_token` [parameter](#system-parameters). The voting power is used for validator selection for block creation and is used in governance related activities. + +#### Validator actions + +- *become validator*: + Any account that is not a validator already and that doesn't have any delegations may request to become a validator. It is required to provide a public consensus key and staking reward address. For the action applied in epoch `n`, the validator's state will be set to *candidate* for epoch `n + pipeline_length` and the consensus key is set for epoch `n + pipeline_length`. +- *deactivate*: + Only a validator whose state at or before the `pipeline_length` offset is *candidate* account may *deactivate*. For this action applied in epoch `n`, the validator's account is set to become *inactive* in the epoch `n + pipeline_length`. +- *reactivate*: + Only an *inactive* validator may *reactivate*. Similarly to *become validator* action, for this action applied in epoch `n`, the validator's state will be set to *candidate* for epoch `n + pipeline_length`. +- *self-bond*: + A validator may lock-up tokens into a [bond](#bonds) only for its own validator's address. +- *unbond*: + Any self-bonded tokens may be partially or fully [unbonded](#unbond). +- *withdraw unbonds*: + Unbonded tokens may be withdrawn in or after the [unbond's epoch](#unbond). +- *change consensus key*: + Set the new consensus key. When applied in epoch `n`, the key is set for epoch `n + pipeline_length`. + +#### Active validator set + +From all the *candidate* validators, in each epoch the ones with the most voting power limited up to the `max_validator_slots` [parameter](#system-parameters) are selected for the active validator set. The active validator set selected in epoch `n` is set for epoch `n + pipeline_length`. + +### Delegator + +A delegator may have any number of delegations to any number of validators. Delegations are stored in [bonds](#bonds). + +#### Delegator actions + +- *delegate*: + An account which is not a validator may delegate tokens to any number of validators. This will lock-up tokens into a [bond](#bonds). +- *undelegate*: + Any delegated tokens may be partially or fully [unbonded](#unbond). +- *withdraw unbonds*: + Unbonded tokens may be withdrawn in or after the [unbond's epoch](#unbond). + +## Bonds + +A bond locks-up tokens from validators' self-bonding and delegators' delegations. For self-bonding, the source address is equal to the validator's address. Only validators can self-bond. For a bond created from a delegation, the bond's source is the delegator's account. + +For each epoch, bonds are uniquely identified by the pair of source and validator's addresses. A bond created in epoch `n` is written into epoch `n + pipeline_length`. If there already is a bond in the epoch `n + pipeline_length` for this pair of source and validator's addresses, its tokens are incremented by the newly bonded amount. + +Any bonds created in epoch `n` increment the bond's validator's total bonded tokens by the bond's token amount and update the voting power for epoch `n + pipeline_length`. + +The tokens put into a bond are immediately deducted from the source account. + +### Unbond + +An unbonding action (validator *unbond* or delegator *undelegate*) requested by the bond's source account in epoch `n` creates an "unbond" with epoch set to `n + unbounding_length`. We also store the epoch of the bond(s) from which the unbond is created in order to determine if the unbond should be slashed if a fault occurred within the range of bond epoch (inclusive) and unbond epoch (exclusive). + +Any unbonds created in epoch `n` decrements the bond's validator's total bonded tokens by the bond's token amount and update the voting power for epoch `n + unbonding_length`. + +An "unbond" with epoch set to `n` may be withdrawn by the bond's source address in or any time after the epoch `n`. Once withdrawn, the unbond is deleted and the tokens are credited to the source account. + +Note that unlike bonding and unbonding where token changes are delayed to some future epochs (pipeline or unbonding offset), the token withdrawal applies immediately. This because when the tokens are withdrawable, they are already "unlocked" from the PoS system and do not contribute to voting power. + +### Staking rewards +Until we have programmable validity predicates, rewards can use the mechanism outlined in the [F1 paper](https://drops.dagstuhl.de/opus/volltexte/2020/11974/pdf/OASIcs-Tokenomics-2019-10.pdf), but it should use the exponential model, so that withdrawing rewards more frequently provides no additional benefit (this is a design constraint we should follow in general, we don't want to accidentally encourage transaction spam). This should be written in a way that allows for a natural upgrade to a validator-customisable rewards model (defaulting to this one) if possible. + +To a validator who proposed a block, the system rewards tokens based on the `block_proposer_reward` [system parameter](#system-parameters) and each validator that voted on a block receives `block_vote_reward`. + +### Slashing + +An important part of the security model of Namada is based on making attacking the system very expensive. To this end, the validator who has bonded stake will be slashed once an offence has been detected. + +These are the types of offences: +* Equivocation in consensus + * voting: meaning that a validator has submitted two votes that are confliciting + * block production: a block producer has created two different blocks for the same height +* Invalidity: + * block production: a block producer has produced invalid block + * voting: validators have voted on invalid block + +Unavailability is not considered an offense, but a validator who hasn't voted will not receive rewards. + +Once an offence has been reported: +1. Kicking out +2. Slashing + - Individual: Once someone has reported an offence it is reviewed by validarors and if confirmed the offender is slashed. + - [cubic slashing](./cubic-slashing.md): escalated slashing + + +Instead of absolute values, validators' total bonded token amounts and bonds' and unbonds' token amounts are stored as their deltas (i.e. the change of quantity from a previous epoch) to allow distinguishing changes for different epoch, which is essential for determining whether tokens should be slashed. However, because slashes for a fault that occurred in epoch `n` may only be applied before the beginning of epoch `n + unbonding_length`, in epoch `m` we can sum all the deltas of total bonded token amounts and bonds and unbond with the same source and validator for epoch equal or less than `m - unboding_length` into a single total bonded token amount, single bond and single unbond record. This is to keep the total number of total bonded token amounts for a unique validator and bonds and unbonds for a unique pair of source and validator bound to a maximum number (equal to `unbonding_length`). + +To disincentivize validators misbehaviour in the PoS system a validator may be slashed for any fault that it has done. An evidence of misbehaviour may be submitted by any account for a fault that occurred in epoch `n` anytime before the beginning of epoch `n + unbonding_length`. + +A valid evidence reduces the validator's total bonded token amount by the slash rate in and before the epoch in which the fault occurred. The validator's voting power must also be adjusted to the slashed total bonded token amount. Additionally, a slash is stored with the misbehaving validator's address and the relevant epoch in which the fault occurred. When an unbond is being withdrawn, we first look-up if any slash occurred within the range of epochs in which these were active and if so, reduce its token amount by the slash rate. Note that bonds and unbonds amounts are not slashed until their tokens are withdrawn. + +The invariant is that the sum of amounts that may be withdrawn from a misbehaving validator must always add up to the total bonded token amount. + + +## System parameters + +The default values that are relative to epoch duration assume that an epoch last about 24 hours. + +- `max_validator_slots`: Maximum active validators, default `128` +- `pipeline_len`: Pipeline length in number of epochs, default `2` (see ) +- `unboding_len`: Unbonding duration in number of epochs, default `6` +- `votes_per_token`: Used in validators' voting power calculation, default 100‱ (1 voting power unit per 1000 tokens) +- `block_proposer_reward`: Amount of tokens rewarded to a validator for proposing a block +- `block_vote_reward`: Amount of tokens rewarded to each validator that voted on a block proposal +- `duplicate_vote_slash_rate`: Portion of validator's stake that should be slashed on a duplicate vote +- `light_client_attack_slash_rate`: Portion of validator's stake that should be slashed on a light client attack + +## Storage + +The [system parameters](#system-parameters) are written into the storage to allow for their changes. Additionally, each validator may record a new parameters value under their sub-key that they wish to change to, which would override the systems parameters when more than 2/3 voting power are in agreement on all the parameters values. + +The validators' data are keyed by the their addresses, conceptually: + +```rust,ignore +type Validators = HashMap; +``` + +Epoched data are stored in the following structure: +```rust,ignore +struct Epoched { + /// The epoch in which this data was last updated + last_update: Epoch, + /// Dynamically sized vector in which the head is the data for epoch in which + /// the `last_update` was performed and every consecutive array element is the + /// successor epoch of the predecessor array element. For system parameters, + /// validator's consensus key and state, `LENGTH = pipeline_length + 1`. + /// For all others, `LENGTH = unbonding_length + 1`. + data: Vec> +} +``` + +Note that not all epochs will have data set, only the ones in which some changes occurred. + +To try to look-up a value for `Epoched` data with independent values in each epoch (such as the active validator set) in the current epoch `n`: + +1. let `index = min(n - last_update, pipeline_length)` +1. read the `data` field at `index`: + 1. if there's a value at `index` return it + 1. else if `index == 0`, return `None` + 1. else decrement `index` and repeat this sub-step from 1. + +To look-up a value for `Epoched` data with delta values in the current epoch `n`: + +1. let `end = min(n - last_update, pipeline_length) + 1` +1. sum all the values that are not `None` in the `0 .. end` range bounded inclusively below and exclusively above + +To update a value in `Epoched` data with independent values in epoch `n` with value `new` for epoch `m`: + +1. let `shift = min(n - last_update, pipeline_length)` +1. if `shift == 0`: + 1. `data[m - n] = new` +1. else: + 1. for `i in 0 .. shift` range bounded inclusively below and exclusively above, set `data[i] = None` + 1. rotate `data` left by `shift` + 1. set `data[m - n] = new` + 1. set `last_update` to the current epoch + +To update a value in `Epoched` data with delta values in epoch `n` with value `delta` for epoch `m`: + +1. let `shift = min(n - last_update, pipeline_length)` +1. if `shift == 0`: + 1. set `data[m - n] = data[m - n].map_or_else(delta, |last_delta| last_delta + delta)` (add the `delta` to the previous value, if any, otherwise use the `delta` as the value) +1. else: + 1. let `sum` to be equal to the sum of all delta values in the `i in 0 .. shift` range bounded inclusively below and exclusively above and set `data[i] = None` + 1. rotate `data` left by `shift` + 1. set `data[0] = data[0].map_or_else(sum, |last_delta| last_delta + sum)` + 1. set `data[m - n] = delta` + 1. set `last_update` to the current epoch + +The invariants for updates in both cases are that `m - n >= 0` and `m - n <= pipeline_length`. + +For the active validator set, we store all the active and inactive validators separately with their respective voting power: +```rust,ignore +type VotingPower = u64; + +/// Validator's address with its voting power. +#[derive(PartialEq, Eq, PartialOrd, Ord)] +struct WeightedValidator { + /// The `voting_power` field must be on top, because lexicographic ordering is + /// based on the top-to-bottom declaration order and in the `ValidatorSet` + /// the `WeighedValidator`s these need to be sorted by the `voting_power`. + voting_power: VotingPower, + address: Address, +} + +struct ValidatorSet { + /// Active validator set with maximum size equal to `max_validator_slots` + active: BTreeSet, + /// All the other validators that are not active + inactive: BTreeSet, +} + +type ValidatorSets = Epoched; + +/// The sum of all active and inactive validators' voting power +type TotalVotingPower = Epoched; +``` + +When any validator's voting power changes, we attempt to perform the following update on the `ActiveValidatorSet`: + +1. let `validator` be the validator's address, `power_before` and `power_after` be the voting power before and after the change, respectively +1. let `power_delta = power_after - power_before` +1. let `min_active = active.first()` (active validator with lowest voting power) +1. let `max_inactive = inactive.last()` (inactive validator with greatest voting power) +1. find whether the validator is active, let `is_active = power_before >= max_inactive.voting_power` + 1. if `is_active`: + 1. if `power_delta > 0 && power_after > max_inactive.voting_power`, update the validator in `active` set with `voting_power = power_after` + 1. else, remove the validator from `active`, insert it into `inactive` and remove `max_inactive.address` from `inactive` and insert it into `active` + 1. else (`!is_active`): + 1. if `power_delta < 0 && power_after < min_active.voting_power`, update the validator in `inactive` set with `voting_power = power_after` + 1. else, remove the validator from `inactive`, insert it into `active` and remove `min_active.address` from `active` and insert it into `inactive` + +Within each validator's address space, we store public consensus key, state, total bonded token amount and voting power calculated from the total bonded token amount (even though the voting power is stored in the `ValidatorSet`, we also need to have the `voting_power` here because we cannot look it up in the `ValidatorSet` without iterating the whole set): + +```rust,ignore +struct Validator { + consensus_key: Epoched, + state: Epoched, + total_deltas: Epoched, + voting_power: Epoched, +} + +enum ValidatorState { + Inactive, + Candidate, +} +``` + +The bonds and unbonds are keyed by their identifier: + +```rust,ignore +type Bonds = HashMap>; +type Unbonds = HashMap>; + +struct BondId { + validator: Address, + /// The delegator adddress for delegations, or the same as the `validator` + /// address for self-bonds. + source: Address, +} + +struct Bond { + /// A key is a the epoch set for the bond. This is used in unbonding, where + // it's needed for slash epoch range check. + deltas: HashMap, +} + +struct Unbond { + /// A key is a pair of the epoch of the bond from which a unbond was created + /// the epoch of unboding. This is needed for slash epoch range check. + deltas: HashMap<(Epoch, Epoch), token::Amount> +} +``` + +For slashes, we store the epoch and block height at which the fault occurred, slash rate and the slash type: + +```rust,ignore +struct Slash { + epoch: Epoch, + block_height: u64, + /// slash token amount ‱ (per ten thousand) + rate: u8, + r#type: SlashType, +} +``` + +## Initialization + +An initial validator set with self-bonded token amounts must be given on system initialization. + +This set is used to pre-compute epochs in the genesis block from epoch `0` to epoch `pipeline_length - 1`. diff --git a/documentation/spec/src/economics/proof-of-stake/cubic-slashing.md b/documentation/spec/src/economics/proof-of-stake/cubic-slashing.md new file mode 100644 index 0000000000..4d4ac07523 --- /dev/null +++ b/documentation/spec/src/economics/proof-of-stake/cubic-slashing.md @@ -0,0 +1,34 @@ +# Cubic slashing + +Namada implements cubic slashing, meaning that the amount of a slash is proportional to the cube of the voting power committing infractions within a particular interval. This is designed to make it riskier to operate larger or similarly configured validators, and thus encourage network resilience. + +When a slash is detected: +1. Using the height of the infraction, calculate the epoch just after which stake bonded at the time of infraction could have been fully unbonded. Enqueue the slash for processing at the end of that epoch (so that it will be processed before unbonding could have completed, and hopefully long enough for any other misbehaviour from around the same height as this misbehaviour to also be detected). +2. Jail the validator in question (this will apply at the end of the current epoch). While the validator is jailed, it should be removed from the validator set (also being effective from the end of the current epoch). Note that this is the only instance in our proof-of-stake model when the validator set is updated without waiting for the pipeline offset. +3. Prevent the delegators to this validator from altering their delegations in any way until the enqueued slash is processed. + +At the end of each epoch, in order to process any slashes scheduled for processing at the end of that epoch: +1. Iterate over all slashes for infractions committed within a range of (-1, +1) epochs worth of block heights (this may need to be a protocol parameter) of the infraction in question. +2. Calculate the slash rate according to the following function: + + +```haskell = +calculateSlashRate :: [Slash] -> Float + +calculateSlashRate slashes = + let votingPowerFraction = sum [ votingPowerFraction (validator slash) | slash <- slashes] + in max 0.01 (min 1 (votingPowerFraction**2)*9) + -- minimum slash rate is 1% + -- then exponential between 0 & 1/3 voting power + -- we can make this a more complex function later +``` + +> Note: The voting power of a slash is the voting power of the validator **when they violated the protocol**, not the voting power now or at the time of any of the other infractions. This does mean that these voting powers may not sum to 1, but this method should still be close to the incentives we want, and can't really be changed without making the system easier to game. + +3. Set the slash rate on the now "finalised" slash in storage. +4. Update the validators' stored voting power appropriately. +5. Delegations to the validator can now be redelegated / start unbonding / etc. + +Validator can later submit a transaction to unjail themselves after a configurable period. When the transaction is applied and accepted, the validator updates its state to "candidate" and is added back to the validator set starting at the epoch at pipeline offset (active or inactive, depending on its voting power). + +At present, funds slashed are sent to the governance treasury. In the future we could potentially reward the slash discoverer with part of the slash, for which some sort of commit-reveal mechanism will be required to prevent front-running. diff --git a/documentation/spec/src/economics/proof-of-stake/reward-distribution.md b/documentation/spec/src/economics/proof-of-stake/reward-distribution.md new file mode 100644 index 0000000000..70f662f97a --- /dev/null +++ b/documentation/spec/src/economics/proof-of-stake/reward-distribution.md @@ -0,0 +1,167 @@ +# Reward distribution + +Namada uses the automatically-compounding variant of [F1 fee distribution](https://drops.dagstuhl.de/opus/volltexte/2020/11974/pdf/OASIcs-Tokenomics-2019-10.pdf). + +Rewards are given to validators for voting on finalizing blocks: the fund for these rewards can come from **minting** (creating new tokens). The amount that is minted depends on how much is staked and our desired yearly inflation. When the total of the tokens staked is very low, the return rate per validator needs to increase, but as the total amount of stake rises, validators will receive less rewards. Once we have acquired the desired stake percentage, the amount minted will just be the desired yearly inflation. + +The validator and the delegator must have agreed on a commission rate between themselves. Delegators pay out rewards to validators based on a mutually-determined commission rate that both parties must have agreed upon beforehand. The minted rewards are auto-bonded and only transferred when the funds are unbonded. Once we have calculated the total that needs to be minted at the end of the epoch, we split the minted tokens according to the stake the relevant validators and delegators contributed and distribute them to validators and their delegators. This is similar to what Cosmos does. + +## Basic algorithm + +Consider a system with + +- a canonical singular staking unit of account. +- a set of validators $V_i$. +- a set of delegations $D_{i, j}$, each to a particular validator and in a particular (initial) amount. +- epoched proof-of-stake, where changes are applied as follows: + - bonding after the pipeline length + - unbonding after the unbonding length + - rewards are paid out at the end of each epoch, to wit, in each epoch $e$, $R_{e,i}$ is paid out to validator $V_i$ + - slashing is applied as described in [slashing](cubic-slashing.md). + +We wish to approximate as exactly as possible the following ideal delegator reward distribution system: + +- At each epoch, for a validator $V$, iterate over all of the delegations to that validator. Update each delegation $D$, as follows. +$$ +D \rightarrow D( 1 + r_V(e)/s_V(e)) +$$ +where $r_V(e)$ and $s_V(e)$ respectively denote the reward and stake of validator $V$ at epoch $e$. +- Similarly, multiply the validator's voting power by the same factor $(1 + r_V(e)/s_V(e))$, which should now equal the sum of their revised-amount delegations. + +In this system, rewards are automatically rebonded to delegations, increasing the delegation amounts and validator voting powers accordingly. + +However, we wish to implement this without actually needing to iterate over all delegations each block, since this is too computationally expensive. We can exploit this constant multiplicative factor $(1 + r_V(e) / s_V(e))$ which does not vary per delegation to perform this calculation lazily, storing only a constant amount of data per validator per epoch, and calculate revised amounts for each individual delegation only when a delegation changes. + +We will demonstrate this for a delegation $D$ to a validator $V$. Let $s_D(e)$ denote the stake of $D$ at epoch $e$. + +For two epochs $m$ and $n$ with $m> +-> HashSet
+-> Epoch +-> HashMap> + +updateProducts validatorProducts activeSet currentEpoch = + let stake = PoS.readValidatorTotalDeltas validator currentEpoch + reward = PoS.reward stake currentEpoch + entries = lookup validatorProducts validator + lastProduct = lookup entries (Epoch (currentEpoch - 1)) + in insert currentEpoch (product*(1+rsratio)) entries + +``` + + + + +In case a delegator wishes to withdraw delegation(s), then the proportionate rewards are appropriated using the aforementioned scheme, which is implemented by the following function. + +```haskell= +withdrawalAmount +:: HashMap> +-> BondId +-> [(Epoch, Delegation)] +-> Token::amount + +withdrawalAmount validatorProducts bondId unbonds = + sum [stake * endp/startp | (endEpoch, unbond) <- unbonds, + let epochProducts = lookup (validator bondId) + validatorProducts, + let startp = lookup (startEpoch unbond) + epochProducts, + let endp = lookup endEpoch epochProducts, + let stake = delegation unbond] + +``` + + +## Commission + +Commission is charged by a validator on the rewards coming from delegations. These are set as percentages by the validator, who may charge any commission they wish between 0-100%. + +Let $c_V(e)$ be the commission rate for a delegation $D$ to a validator $V$ at epoch $e$. The expression for the product $p_n$ we have introduced earlier can be modified as + +$$ p_n = \prod_{e = 0}^{n} \Big(1 + (1-c_V(e))\frac{r_V(e)} {s_V(e)} \Big). $$ + +in order to calculate the new rewards given out to delegators during withdrawal. Thus the commission charged per epoch is retained by the validator and remains untouched upon withdrawal by the delegator. + +The commission rate $c_V(e)$ is the same for all delegations to a validator $V$ in a given epoch $e$, including for self-bonds. The validator can change the commission rate at any point, subject to a maximum rate of change per epoch, which is a constant specified when the validator is created and immutable once validator creation has been accepted. + +While rewards are given out at the end of every epoch, voting power is only updated after the pipeline offset. According to the [proof-of-stake system](bonding-mechanism.md#epoched-data), at the current epoch `e`, the validator sets an only be updated for epoch `e + pipeline_offset`, and it should remain unchanged from epoch `e` to `e + pipeline_offset - 1`. Updating voting power in the current epoch would violate this rule. + + +## Slashes + +Slashes should lead to punishment for delegators who were contributing voting power to the validator at the height of the infraction, _as if_ the delegations were iterated over and slashed individually. + +This can be implemented as a negative inflation rate for a particular block. + +Instant redelegation is not supported. Redelegations must wait the unbonding period. + + + + diff --git a/documentation/spec/src/economics/public-goods-funding.md b/documentation/spec/src/economics/public-goods-funding.md new file mode 100644 index 0000000000..72c415ad2b --- /dev/null +++ b/documentation/spec/src/economics/public-goods-funding.md @@ -0,0 +1,43 @@ +### Motivation + +**Public goods** are non-excludable non-rivalrous items which provide benefits of some sort to their users. Examples include languages, open-source software, research, designs, Earth's atmosphere, and art (conceptually - a physical painting is excludable and rivalrous, but the painting as-such is not). Namada's software stack, supporting research, and ecosystem tooling are all public goods, as are the information ecosystem and education which provide for the technology to be used safety, the hardware designs and software stacks (e.g. instruction set, OS, programming language) on which it runs, and the atmosphere and biodiverse environment which renders its operation possible. Without these things, Namada could not exist, and without their continued sustenance it will not continue to. Public goods, by their nature as non-excludable and non-rivalrous, are mis-modeled by economic systems (such as payment-for-goods) built upon the assumption of scarcity, and are usually either under-funded (relative to their public benefit) or funded in ways which require artificial scarcity and thus a public loss. For this reason, it is in the interest of Namada to help out, where possible, in funding the public goods upon which its existence depends in ways which do not require the introduction of artificial scarcity, balancing the costs of available resources and operational complexity. This is a proposal for a mechanism to be built into the Namada protocol, community governance precedent, and surrounding social structures in order to fund public goods while balancing these constraints. + +### Design precedent + +There is a lot of existing research into public-goods funding to which justice cannot be done here. Most mechanisms fall into two categories: need-based and results-based, where need-based allocation schemes attempt to pay for particular public goods on the basis of cost-of-resources, and results-based allocation schemes attempt to pay (often retroactively) for particular public goods on the basis of expected or assessed benefits to a community and thus create incentives for the production of public goods providing substantial benefits (for a longer exposition on retroactive PGF, see [here](https://medium.com/ethereum-optimism/retroactive-public-goods-funding-33c9b7d00f0c), although the idea is [not new](https://astralcodexten.substack.com/p/lewis-carroll-invented-retroactive)). Additional constraints to consider include the cost-of-time of governance structures (which renders e.g. direct democracy on all funding proposals very inefficient), the necessity of predictable funding in order to make long-term organisational decision-making, the propensity for bike-shedding and damage to the information commons in large-scale public debate (especially without an identity layer or Sybil resistance), and the engineering costs of implementations. + +### Mechanism + +Namada instantiates a dual proactive/retroactive public-goods funding model, stewarded by a public-goods council elected by limited liquid democracy. + +This proposal requires the following protocol components: +- Limited liquid democracy / targeted delegation: Namada's current voting mechanism is altered to add targeted delegation. By default, each delegator delegates their vote in governance to their validator, but they can set an alternative governance delegate who can instead vote on their behalf (but whose vote can be overridden as usual). Validators can also set governance delegates, in which case those delegates can vote on their behalf, and on the behalf of all delegators to that validator who do not override the vote, unless the validator overrides the vote. This is a limited form of liquid democracy which could be extended in the future. +- Funding council: bi-annually (every six months), Namada governance elects a public goods funding council by stake-weighted approval vote (see below). Public goods funding councils run as groups. The public goods funding council decides according to internal decision-making procedures (practically probably limited to a k-of-n multisignature) how to allocate continuous funding and retroactive funding during their term. Namada genesis includes an initial funding council, and the next election will occur six months after launch. +- Continuous funding: Namada prints an amount of inflation fixed on a percentage basis dedicated to continuous funding. Each quarter, the public goods funding council selects recipients and amounts (which in total must receive all of the funds, although they could burn some) and submits this list to the protocol. Inflation is distributed continuously by the protocol to these recipients during that quarter. +- Retroactive funding: Namada prints an amount of inflation fixed on a percentage basis dedicated to retroactive funding. Each quarter, the public goods funding council selects recipients and amounts (which in total must receive all of the funds) and submits this list to the protocol. Amounts are distributed immediately as lump sums. The public goods funding council is instructed to use this funding to fund public goods retroactively, proportional to assessed benefit. +- Privacy of council votes: in order to prevent targeting of individual public goods council members, it is important that council acts only as a group. Whatever internal decision-making structure it uses is up the council; Namada governance should evaluate councils as opaque units. We may need a simple threshold public key to provide this kind of privacy - can we evaluate the implementation difficulty of that? +- Stake-weighted approval voting: as public goods councils are exclusive, we can use a stake-weighted form of approval voting. Governance voters include all public goods council candidates of which they approve, and the council candidate with the most stake approving it wins. This doesn't have game-theoretic properties as nice as ranked-choice voting (especially when votes are public, as they are at the moment), but it is _much_ simpler ([background](https://en.wikipedia.org/wiki/Condorcet_method)), and in practice I do not think there will be too many public goods council candidates. +- Interface support: the interface should support limited liquid democracy for delegate selection and approval voting for public goods council candidates. The interface or explorer should display past retroactive PGF winners and past/current continuous funding recipients. Proposal submission for continuous and retroactive funding will happen separately, in whatever manner the public goods council deems fit. + +--- + +Please note that the following is _social consensus_, precedent which can be set at genesis and ratified by governance but does not require any protocol changes. + +_Categories of public-goods funding_ + +I propose that the Namada public-goods funding council group public goods into four categories, with earmarked pools of funding: + +- Technical research + _Technical research_ covers funding for technical research topics related to Namada and Anoma, such as cryptography, distributed systems, programming language theory, and human-computer interface design, both inside and outside the academy. Possible funding forms could include PhD sponsorships, independent researcher grants, institutional funding, funding for experimental resources (e.g. compute resources for benchmarking), funding for prizes (e.g. theoretical cryptography optimisations), and similar. +- Engineering + _Engineering_ covers funding for engineering projects related to Namada and Anoma, including libraries, optimisations, tooling, alternative interfaces, alternative implementations, integrations, etc. Possible funding forms could include independent developer grants, institutional funding, funding for bug bounties, funding for prizes (e.g. practical performance optimisations), and similar. +- Social research, art, and philosophy + _Social research, art, and philosophy_ covers funding for artistic expression, philosophical investigation, and social/community research (_not_ marketing) exploring the relationship between humans and technology. Possible funding forms could include independent artist grants, institutional funding, funding for specific research resources (e.g. travel expenses to a location to conduct a case study), and similar. +- External public goods + _External public goods_ covers funding for public goods explicitly external to the Namada and Anoma ecosystem, including carbon sequestration, independent journalism, direct cash transfers, legal advocacy, etc. Possible funding forms could include direct purchase of tokenised assets such as carbon credits, direct cash transfers (e.g. GiveDirectly), institutional funding (e.g. Wikileaks), and similar. + +_Amounts_ + +I propose 10% total per annum inflation, 5% to continuous funding and 5% to retroactive funding (this is chosen in-protocol, so this suggestion is merely a genesis default and can be altered by governance). + +I proposal a social consensus of an equal split between categories, meaning 1.25% per annum inflation for each category (e.g. 1.25% for technical research continuous funding, 1.25% for technical research retroactive PGF). \ No newline at end of file diff --git a/documentation/spec/src/economics/shielded-pool-incentives.md b/documentation/spec/src/economics/shielded-pool-incentives.md new file mode 100644 index 0000000000..1dce6cc5be --- /dev/null +++ b/documentation/spec/src/economics/shielded-pool-incentives.md @@ -0,0 +1,16 @@ +# Shielded pool incentives + +Private transactions made by individual users using the MASP increase the privacy set for other users, so even if the individual doesn't care whether a particular transaction is private, others benefit from their choice to do the transaction in private instead of in public. In the absence of a subsidy (the computation required for private state transitions is likely more expensive) orother incentives, users may not elect to make their transactions private when they do not need to because the benefits do not directly accrue to them. This provides grounds for a protocol subsidy of shielded transactions (relative to the computatation required), so that users who do not have a strong preference on whether or not to make their transaction private will be "nudged" by the fee difference to do so. + +Separately, and additionally, a privacy set which is very small in absolute terms does not provide much privacy, and transactions increasing the privacy set provide more additional privacy if the privacy set is small. Compare, for example, the doubled privacy set from 10 to 20 transactions to the minor increase from 1010 to 1020 transactions. This provides grounds for some sort of incentive mechanism for _making_ shielded transactions which pays in inverse proportion to the size of the current privacy set (so shielded transactions when the privacy set is small receive increased incentives in accordance with their increased contributions to privacy). + +Incentive mechanisms are also dangerous, as they give users reason to craft particular transactions when they might not otherwise have done so, and they must satisfy certain constraints in order not to compromise state machine throughput, denial-of-service resistance, etc. A few constraints to keep in mind: + +- Fee subsidies cannot reduce fees to zero, or reduce fees so much that inexpensive transaction spam can fill blocks and overload validators. +- Incentives for contributing to the privacy set should not incentivise transactions which do not meaningfully contribute to the privacy set or merely repeat a previous action (shielded and unshielding the same assets, repeatedly transferring the same assets, etc.) +- Incentives for contributing to the privacy set, since the MASP supports many assets, will need to be adjusted over time according to actual conditions of use. + +(to be written up: formal definition of "privacy set" used for the incentives here) + + +The total incetives that are paid out, $I_L$, is minted each epoch based on the current parameters and are calculated according to the [inflation model](./inflation-system.md). This total is then distributed evenly among recipients. diff --git a/documentation/spec/src/further-reading.md b/documentation/spec/src/further-reading.md new file mode 100644 index 0000000000..971007c530 --- /dev/null +++ b/documentation/spec/src/further-reading.md @@ -0,0 +1,3 @@ +## Further reading + +Thanks for reading! You can find further information about the state of Namada at [namada.net](https://namada.net). \ No newline at end of file diff --git a/documentation/spec/src/index.md b/documentation/spec/src/index.md new file mode 100644 index 0000000000..df8cd94182 --- /dev/null +++ b/documentation/spec/src/index.md @@ -0,0 +1,59 @@ +## Namada + +Welcome to the Namada specifications! + +Namada is a sovereign proof-of-stake blockchain, using Tendermint BFT consensus, +that enables multi-asset private transfers for any native or non-native asset +using a multi-asset shielded pool derived from the Sapling circuit. Namada features +full IBC protocol support, a natively integrated Ethereum bridge, a modern proof-of-stake +system with automatic reward compounding and cubic slashing, a stake-weighted governance +signalling mechanism, and a proactive/retroactive public goods funding system. +Users of shielded transfers are rewarded for their contributions +to the privacy set in the form of native protocol tokens. A multi-asset shielded transfer wallet +is provided in order to facilitate safe and private user interaction with the protocol. + +### How does Namada relate to Anoma? + +Namada is _two things_: +- The first major release _version_ of the Anoma protocol. +- The first _fractal instance_ launched as part of the Anoma network. + +The Anoma protocol is designed to facilitate the operation of networked fractal instances, +which intercommunicate but can utilise varied state machines and security models. Different +fractal instances may specialise in different tasks and serve different communities. The Namada +instance will be the first such fractal instance, and it will be focused exclusively on the use-case of private asset transfers. + +### Raison d'être + +Safe and user-friendly multi-asset privacy doesn't yet exist in the blockchain ecosystem. +Up until now users have had the choice of either a sovereign chain that reissues assets (e.g. Zcash) +or a privacy preserving solution build on an existing smart contract chain (e.g. Tornado Cash on +Ethereum). Both have large trade-offs: in the former case users don't have +assets that they actually want to transact with and in the latter case the restrictions +of existing platforms mean that users leak a ton of metadata +and the protocols are expensive and clunky to use. + +Namada can support any fungible or non-fungible asset on an IBC-compatible blockchain +and fungible or non-fungible assets (such as ERC20 tokens) sent over a custom Ethereum bridge that +reduces transfer costs and streamlines UX as much as possible. Once assets are on Namada, +shielded transfers are cheap and all assets contribute to the same anonymity set. + +Namada is also a helpful stepping stone to finalise, test, +and launch a protocol version that is simpler than the full +Anoma protocol but still encapsulates a unified and useful +set of features. There are reasons to expect that it may +make sense for a fractal instance focused exclusively on +shielded transfers to exist in the long-term, as it can +provide throughput and user-friendliness guarantees which +are more difficult to provide with a more general platform. +Namada is designed to be such an instance. + +### Layout of this specification + +The Namada specification documents are organised into five sub-sections: + +- [Base ledger](./base-ledger.md) +- [Multi-asset shielded pool](./masp.md) +- [Interoperability](./interoperability.md) +- [Economics](./economics.md) +- [User interfaces](./user-interfaces.md) \ No newline at end of file diff --git a/documentation/spec/src/interoperability.md b/documentation/spec/src/interoperability.md new file mode 100644 index 0000000000..0ed8007ee6 --- /dev/null +++ b/documentation/spec/src/interoperability.md @@ -0,0 +1,3 @@ +## Interoperability + +Namada can interoperate permissionlessly with other chains through [integration of the IBC protocol](./interoperability/ibc.md). Namada also includes a bespoke [Ethereum bridge](./interoperability/ethereum-bridge.md) operated by the Namada validator set. \ No newline at end of file diff --git a/documentation/spec/src/interoperability/ethereum-bridge.md b/documentation/spec/src/interoperability/ethereum-bridge.md new file mode 100644 index 0000000000..d85929bc03 --- /dev/null +++ b/documentation/spec/src/interoperability/ethereum-bridge.md @@ -0,0 +1,378 @@ +# Ethereum bridge + +The Namada - Ethereum bridge exists to mint ERC20 tokens on Namada +which naturally can be redeemed on Ethereum at a later time. Furthermore, it +allows the minting of wrapped tokens on Ethereum backed by escrowed assets on +Namada. + +The Namada Ethereum bridge system consists of: +* An Ethereum full node run by each Namada validator, for including relevant + Ethereum events into Namada. +* A set of validity predicates on Namada which roughly implements + [ICS20](https://docs.cosmos.network/v0.42/modules/ibc/) fungible token + transfers. +* A set of Ethereum smart contracts. +* A relayer for submitting transactions to Ethereum + +This basic bridge architecture should provide for almost-Namada consensus +security for the bridge and free Ethereum state reads on Namada, plus +bidirectional message passing with reasonably low gas costs on the +Ethereum side. + +## Security +On Namada, the validators are full nodes of Ethereum and their stake is also +accounting for security of the bridge. If they carry out a forking attack +on Namada to steal locked tokens of Ethereum their stake will be slashed on Namada. +On the Ethereum side, we will add a limit to the amount of assets that can be +locked to limit the damage a forking attack on Namada can do. To make an attack +more cumbersome we will also add a limit on how fast wrapped Ethereum assets can +be redeemed from Namada. This will not add more security, but rather make the +attack more inconvenient. + +## Ethereum Events Attestation +We want to store events from the smart contracts of our bridge onto Namada. +We need to have consensus on these events, we will only include those that +have been seen and validated by at least 2/3 of the staking validators in +the blockchain storage. + +There will be multiple types of events emitted. Validators should +ignore improperly formatted events. Raw events from Ethereum are converted to a +Rust enum type (`EthereumEvent`) by Namada validators before being included +in vote extensions or stored on chain. + +```rust +pub enum EthereumEvent { + // we will have different variants here corresponding to different types + // of raw events we receive from Ethereum + TransfersToNamada(Vec) + // ... +} +``` + +Each event should have a list of the validators that have seen +this event and the current amount of stake associated with it. This +will need to be appropriately adjusted across epoch boundaries. However, +once an event has been seen by 2/3 of the voting power, it is locked into a +`seen` state. Thus, even if after an epoch that event has no longer been +reported as seen by 2/3 of the new staking validators voting power, it is still +considered as `seen`. + +Each event from Ethereum should include the minimum number of confirmations +necessary to be considered seen. Validators should not vote to include events +that have not met the required number of confirmations. Furthermore, validators +should not look at events that have not reached protocol specified minimum +number of confirmations (regardless of what is specified in an event). This +constant may be changeable via governance. Voting on unconfirmed events is +considered a slashable offence. + +### Storage +To make including new events easy, we take the approach of always overwriting +the state with the new state rather than applying state diffs. The storage +keys involved are: +``` +# all values are Borsh-serialized +/eth_msgs/$msg_hash/body : EthereumEvent +/eth_msgs/$msg_hash/seen_by : Vec
+/eth_msgs/$msg_hash/voting_power: (u64, u64) # reduced fraction < 1 e.g. (2, 3) +/eth_msgs/$msg_hash/seen: bool +``` + +`$msg_hash` is the SHA256 digest of the Borsh serialization of the relevant +`EthereumEvent`. + +Changes to this `/eth_msgs` storage subspace are only ever made by internal transactions crafted +and applied by all nodes based on the aggregate of vote extensions for the last Tendermint round. That is, changes to `/eth_msgs` happen +in block `n+1` in a deterministic manner based on the vote extensions of the Tendermint +round for block `n`. + +The `/eth_msgs` storage subspace does not belong to any account and cannot be +modified by transactions submitted from outside of the ledger via Tendermint. +The storage will be guarded by a special validity predicate - `EthSentinel` - +that is part of the verifier set by default for every transaction, but will be +removed by the ledger code for the specific permitted transactions that are +allowed to update `/eth_msgs`. + +### Including events into storage +For every Namada block proposal, the vote extension of a validator should include +the events of the Ethereum blocks they have seen via their full node such that: +1. The storage value `/eth_msgs/$msg_hash/seen_by` does not include their + address. +2. It's correctly formatted. +3. It's reached the required number of confirmations on the Ethereum chain + +Each event that a validator is voting to include must be individually signed by +them. The vote extension data field will be a Borsh-serialization of something +like the following. +```rust +pub struct VoteExtension(Vec); + +/// A struct used by validators to sign that they have seen a particular +/// ethereum event. These are included in vote extensions +#[derive(Debug, Clone, BorshSerialize, BorshDeserialize, BorshSchema)] +pub struct SignedEthEvent { + /// The address of the signing validator + signer: Address, + /// The proportion of the total voting power held by the validator + power: FractionalVotingPower, + /// The event being signed and the block height at which + /// it was seen. We include the height as part of enforcing + /// that a block proposer submits vote extensions from + /// **the previous round only** + event: Signed<(EthereumEvent, BlockHeight)>, +} +``` + +These vote extensions will be given to the next block proposer who will +aggregate those that it can verify and will inject a protocol transaction +(the "vote extensions" transaction). + +```rust +pub struct MultiSigned { + /// Arbitrary data to be signed + pub data: T, + /// The signature of the data + pub sigs: Vec, +} + +pub struct MultiSignedEthEvent { + /// Address and voting power of the signing validators + pub signers: Vec<(Address, FractionalVotingPower)>, + /// Events as signed by validators + pub event: MultiSigned<(EthereumEvent, BlockHeight)>, +} + +pub enum ProtocolTxType { + EthereumEvents(Vec) +} +``` + +This vote extensions transaction will be signed by the block proposer. +Validators will check this transaction and the validity of the new votes as +part of `ProcessProposal`, this includes checking: +- signatures +- that votes are really from active validators +- the calculation of backed voting power + +It is also checked that each vote extension came from the previous round, +requiring validators to sign over the Namada block height with their vote +extension. Furthermore, the vote extensions included by the block proposer +should have at least 2 / 3 of the total voting power backing it. Otherwise +the block proposer would not have passed the `FinalizeBlock` phase of the +last round. These checks are to prevent censorship of events from validators +by the block proposer. + +In `FinalizeBlock`, we derive a second transaction (the "state update" +transaction) from the vote extensions transaction that: +- calculates the required changes to `/eth_msgs` storage and applies it +- acts on any `/eth_msgs/$msg_hash` where `seen` is going from `false` to `true` + (e.g. appropriately minting wrapped Ethereum assets) + +This state update transaction will not be recorded on chain but will be +deterministically derived from the vote extensions transaction, which is +recorded on chain. All ledger nodes will derive and apply this transaction to +their own local blockchain state, whenever they receive a block with a vote +extensions transaction. No signature is required. + +The value of `/eth_msgs/$msg_hash/seen` will also indicate if the event +has been acted on on the Namada side. The appropriate transfers of tokens to the +given user will be included on chain free of charge and requires no +additional actions from the end user. + +## Namada Validity Predicates + +There will be three internal accounts with associated native validity predicates: +- `#EthSentinel` - whose validity predicate will verify the inclusion of events from Ethereum. This validity predicate will control the `/eth_msgs` storage subspace. +- `#EthBridge` - the storage of which will contain ledgers of balances for wrapped Ethereum assets (ETH and ERC20 tokens) structured in a ["multitoken"](https://github.com/anoma/anoma/issues/1102) hierarchy +- `#EthBridgeEscrow` which will hold in escrow wrapped Namada tokens which have been sent to Ethereum. + +### Transferring assets from Ethereum to Namada + +#### Wrapped ERC20 +The "transfer" transaction mints the appropriate amount to the corresponding +multitoken balance key for the receiver, based on the specifics of a +`TransferToNamada` Ethereum event. + +```rust +pub struct EthAddress(pub [u8; 20]); + +/// Represents Ethereum assets on the Ethereum blockchain +pub enum EthereumAsset { + /// An ERC20 token and the address of its contract + ERC20(EthAddress), +} + +/// An event transferring some kind of value from Ethereum to Anoma +pub struct TransferToNamada { + /// Quantity of ether in the transfer + pub amount: Amount, + /// Address on Ethereum of the asset + pub asset: EthereumAsset, + /// The Namada address receiving wrapped assets on Anoma + pub receiver: Address, +} +``` + +##### Example + +For 10 DAI i.e. ERC20([0x6b175474e89094c44da98b954eedeac495271d0f](https://etherscan.io/token/0x6b175474e89094c44da98b954eedeac495271d0f)) to `atest1v4ehgw36xue5xvf5xvuyzvpjx5un2v3k8qeyvd3cxdqns32p89rrxd6xx9zngvpegccnzs699rdnnt` +``` +#EthBridge + /erc20 + /0x6b175474e89094c44da98b954eedeac495271d0f + /balances + /atest1v4ehgw36xue5xvf5xvuyzvpjx5un2v3k8qeyvd3cxdqns32p89rrxd6xx9zngvpegccnzs699rdnnt + += 10 +``` + +#### Namada tokens +Any wrapped Namada tokens being redeemed from Ethereum must have an equivalent amount of the native token held in escrow by `#EthBridgeEscrow`. +The protocol transaction should simply make a transfer from `#EthBridgeEscrow` to the `receiver` for the appropriate amount and asset. + +### Transferring from Namada to Ethereum + +To redeem wrapped Ethereum assets, a user should make a transaction to burn +their wrapped tokens, which the `#EthBridge` validity predicate will accept. + +Once this burn is done, it is incumbent on the end user to +request an appropriate "proof" of the transaction. This proof must be +submitted to the appropriate Ethereum smart contract by the user to +redeem their native Ethereum assets. This also means all Ethereum gas costs +are the responsibility of the end user. + +The proofs to be used will be custom bridge headers that are calculated +deterministically from the block contents, including messages sent by Namada and +possibly validator set updates. They will be designed for maximally +efficient Ethereum decoding and verification. + +For each block on Namada, validators must submit the corresponding bridge +header signed with a special secp256k1 key as part of their vote extension. +Validators must reject votes which do not contain correctly signed bridge +headers. The finalized bridge header with aggregated signatures will appear in the +next block as a protocol transaction. Aggregation of signatures is the +responsibility of the next block proposer. + +The bridge headers need only be produced when the proposed block contains +requests to transfer value over the bridge to Ethereum. The exception is +when validator sets change. Since the Ethereum smart contract should +accept any header signed by bridge header signed by 2 / 3 of the staking +validators, it needs up-to-date knowledge of: +- The current validators' public keys +- The current stake of each validator + +This means the at the end of every Namada epoch, a special transaction must be +sent to the Ethereum contract detailing the new public keys and stake of the +new validator set. This message must also be signed by at least 2 / 3 of the +current validators as a "transfer of power". It is to be included in validators +vote extensions as part of the bridge header. Signing an invalid validator +transition set will be consider a slashable offense. + +Due to asynchronicity concerns, this message should be submitted well in +advance of the actual epoch change, perhaps even at the beginning of each +new epoch. Bridge headers to ethereum should include the current Namada epoch +so that the smart contract knows how to verify the headers. In short, there +is a pipelining mechanism in the smart contract. + +Such a message is not prompted by any user transaction and thus will have +to be carried out by a _bridge relayer_. Once the transfer of power +message is on chain, any time afterwards a Namada bridge process may take +it to craft the appropriate message to the Ethereum smart contracts. + +The details on bridge relayers are below in the corresponding section. + +Signing incorrect headers is considered a slashable offense. Anyone witnessing +an incorrect header that is signed may submit a complaint (a type of transaction) +to initiate slashing of the validator who made the signature. + +#### Namada tokens + +Mints of a wrapped Namada token on Ethereum (including NAM, Namada's native token) +will be represented by a data type like: + +```rust +struct MintWrappedNam { + /// The Namada address owning the token + owner: NamadaAddress, + /// The address on Ethereum receiving the wrapped tokens + receiver: EthereumAddress, + /// The address of the token to be wrapped + token: NamadaAddress, + /// The number of wrapped Namada tokens to mint on Ethereum + amount: Amount, +} +``` + +If a user wishes to mint a wrapped Namada token on Ethereum, they must submit a transaction on Namada that: +- stores `MintWrappedNam` on chain somewhere - TBD +- sends the correct amount of Namada token to `#EthBridgeEscrow` + +Just as in redeeming Ethereum assets above, it is incumbent on the end user to +request an appropriate proof of the transaction. This proof must be +submitted to the appropriate Ethereum smart contract by the user. +The corresponding amount of wrapped NAM tokens will be transferred to the +`receiver` on Ethereum by the smart contract. + +## Namada Bridge Relayers + +Validator changes must be turned into a message that can be communicated to +smart contracts on Ethereum. These smart contracts need this information +to verify proofs of actions taken on Namada. + +Since this is protocol level information, it is not user prompted and thus +should not be the responsibility of any user to submit such a transaction. +However, any user may choose to submit this transaction anyway. + +This necessitates a Namada node whose job it is to submit these transactions on +Ethereum at the conclusion of each Namada epoch. This node is called the +__Designated Relayer__. In theory, since this message is publicly available on the blockchain, +anyone can submit this transaction, but only the Designated Relayer will be +directly compensated by Namada. + +All Namada validators will have an option to serve as bridge relayer and +the Namada ledger will include a process that does the relaying. Since all +Namada validators are running Ethereum full nodes, they can monitor +that the message was relayed correctly by the Designated Relayer. + +During the `FinalizeBlock` call in the ledger, if the epoch changes, a +flag should be set alerting the next block proposer that they are the +Designated Relayer for this epoch. If their message gets accepted by the +Ethereum state inclusion onto Namada, new NAM tokens will be minted to reward +them. The reward amount shall be a protocol parameter that can be changed +via governance. It should be high enough to cover necessary gas fees. + +## Ethereum Smart Contracts +The set of Ethereum contracts should perform the following functions: +- Verify bridge header proofs from Namada so that Namada messages can + be submitted to the contract. +- Verify and maintain evolving validator sets with corresponding stake + and public keys. +- Emit log messages readable by Namada +- Handle ICS20-style token transfer messages appropriately with escrow & + unescrow on the Ethereum side +- Allow for message batching + +Furthermore, the Ethereum contracts will whitelist ETH and tokens that +flow across the bridge as well as ensure limits on transfer volume per epoch. + +An Ethereum smart contract should perform the following steps to verify +a proof from Namada: +1. Check the epoch included in the proof. +2. Look up the validator set corresponding to said epoch. +3. Verify that the signatures included amount to at least 2 / 3 of the + total stake. +4. Check the validity of each signature. + +If all the above verifications succeed, the contract may affect the +appropriate state change, emit logs, etc. + +## Starting the bridge + +Before the bridge can start running, some storage will need to be initialized in Namada. For example, the `#EthBridge/queue` storage key should be initialized to an empty `Vec`. TBD. + +## Resources which may be helpful: +- [Gravity Bridge Solidity contracts](https://github.com/Gravity-Bridge/Gravity-Bridge/tree/main/solidity) +- [ICS20](https://github.com/cosmos/ibc/tree/master/spec/app/ics-020-fungible-token-transfer) +- [Rainbow Bridge contracts](https://github.com/aurora-is-near/rainbow-bridge/tree/master/contracts) +- [IBC in Solidity](https://github.com/hyperledger-labs/yui-ibc-solidity) + +Operational notes: +1. We should bundle the Ethereum full node with the `namada` daemon executable. diff --git a/documentation/spec/src/interoperability/ibc.md b/documentation/spec/src/interoperability/ibc.md new file mode 100644 index 0000000000..8b0446575e --- /dev/null +++ b/documentation/spec/src/interoperability/ibc.md @@ -0,0 +1,65 @@ +# IBC integration + +* [IBC (Inter-blockchain communication protocol) spec](https://github.com/cosmos/ibc) +* [IBC integration in Anoma](https://github.com/anoma/anoma/blob/yuji/design_ibc/docs/src/explore/design/ledger/ibc.md) (Need to be updated) + +## IBC transaction +An IBC transaction [`tx_ibc.wasm`](https://github.com/anoma/anoma/blob/fd4b7ab36929f47369ae82c82966891cb0ccc625/wasm/wasm_source/src/lib.rs#L224-L233) is provided. We have to set an IBC message to the transaction data corresponding to execute an IBC operation. + +The transaction decodes the data to an IBC message and handles IBC-related data, e.g. it makes a new connection ID and writes a new connection end for `MsgConnectionOpenTry`. The operations are implemented in [`IbcActions`](https://docs.anoma.network/master/rustdoc/anoma/ledger/ibc/handler/trait.IbcActions.html).The transaction doesn't check the validity for the state changes. IBC validity predicate is in charge of the validity. + +## IBC validity predicate +[IBC validity predicate](https://docs.anoma.network/master/rustdoc/anoma/ledger/ibc/vp/struct.Ibc.html#impl-NativeVp) checks if an IBC-related transaction satisfies IBC protocol. When an IBC-related transaction is executed, i.e. a transaction changes the state of the key that contains [`InternalAddress::Ibc`](https://docs.anoma.network/master/rustdoc/anoma/types/address/enum.InternalAddress.html#variant.Ibc), IBC validity predicate (one of the native validity predicates) is executed. For example, if an IBC connection end is created in the transaction, IBC validity predicate validates the creation. If the creation with `MsgConnectionOpenTry` is invalid, e.g. the counterpart connection end doesn't exist, the validity predicate makes the transaction fail. + +## Fungible Token Transfer +The transfer of fungible tokens over an IBC channel on separate chains is defined in [ICS20](https://github.com/cosmos/ibc/blob/master/spec/app/ics-020-fungible-token-transfer/README.md). + +In Anoma, the sending tokens is triggered by a transaction having [MsgTransfer](https://github.com/informalsystems/ibc-rs/blob/0a952b295dbcf67bcabb79ce57ce92c9c8d7e5c6/modules/src/applications/ics20_fungible_token_transfer/msgs/transfer.rs#L20-L37) as transaction data. A packet including [`FungibleTokenPacketData`](https://docs.anoma.network/master/rustdoc/anoma/types/ibc/data/struct.FungibleTokenPacketData.html) is made from the message in the transaction execution. + +Anoma chain receives the tokens by a transaction having [MsgRecvPacket](https://github.com/informalsystems/ibc-rs/blob/0a952b295dbcf67bcabb79ce57ce92c9c8d7e5c6/modules/src/core/ics04_channel/msgs/recv_packet.rs#L19-L23) which has the packet including `FungibleTokenPacketData`. + +The sending and receiving tokens in a transaction are validated by not only IBC validity predicate but also [IBC token validity predicate](https://docs.anoma.network/master/rustdoc/anoma/ledger/ibc/vp/struct.IbcToken.html#impl-NativeVp). IBC validity predicate validates if sending and receiving the packet is proper. IBC token validity predicate is also one of the native validity predicates and checks if the token transfer is valid. If the transfer is not valid, e.g. the unexpected amount is minted, the validity predicate makes the transaction fail. + +A transaction escrowing/unescrowing a token changes the escrow account's balance of the token. The key is `{token_addr}/balance/{escrow_addr}`. A transaction burning a token changes the burn account's balance of the token. The key is `{token_addr}/balance/BURN_ADDR`. A transaction minting a token changes the mint account's balance of the token. The key is `{token_addr}/balance/MINT_ADDR`. `{escrow_addr}`, `{BURN_ADDR}`, and `{MINT_ADDR}` are addresses of [`InternalAddress`](https://docs.anoma.network/master/rustdoc/anoma/types/address/enum.InternalAddress.html). When these address are included of the change keys after transaction execution, IBC token validity predicate is executed. + +## IBC message + +IBC messages are defined in `ibc-rs`. The message should be encoded with Protobuf (NOT with Borsh) as the following code to set it as a transaction data. + +```rust +use ibc::tx_msg::Msg; + +pub fn make_ibc_data(message: impl Msg) -> Vec { + let msg = message.to_any(); + let mut tx_data = vec![]; + prost::Message::encode(&msg, &mut tx_data).expect("encoding IBC message shouldn't fail"); + tx_data +} +``` + +* Client + - [MsgCreateAnyClient](https://github.com/informalsystems/ibc-rs/blob/5ddec6d2571b1376de7d9ebe7e353b3cd726c2d3/modules/src/core/ics02_client/msgs/create_client.rs#L19-L23) + - [MsgSubmitAnyMisbehaviour](https://github.com/informalsystems/ibc-rs/blob/1448a2bbc817da10b183b8479548a12344ba0e9c/modules/src/core/ics02_client/msgs/misbehavior.rs#L17-L24) (NOT supported yet) + - [MsgUpdateAnyClient](https://github.com/informalsystems/ibc-rs/blob/5ddec6d2571b1376de7d9ebe7e353b3cd726c2d3/modules/src/core/ics02_client/msgs/update_client.rs#L20-L24) + - [MsgUpgradeAnyClient](https://github.com/informalsystems/ibc-rs/blob/1448a2bbc817da10b183b8479548a12344ba0e9c/modules/src/core/ics02_client/msgs/upgrade_client.rs#L24-L31) + +* Connection + - [MsgConnectionOpenInit](https://github.com/informalsystems/ibc-rs/blob/1448a2bbc817da10b183b8479548a12344ba0e9c/modules/src/core/ics03_connection/msgs/conn_open_init.rs#L21-L27) + - [MsgConnectionOpenTry](https://github.com/informalsystems/ibc-rs/blob/1448a2bbc817da10b183b8479548a12344ba0e9c/modules/src/core/ics03_connection/msgs/conn_open_try.rs#L29-L38) + - [MsgConnectionOpenAck](https://github.com/informalsystems/ibc-rs/blob/1448a2bbc817da10b183b8479548a12344ba0e9c/modules/src/core/ics03_connection/msgs/conn_open_ack.rs#L20-L27) + - [MsgConnectionOpenConfirm](https://github.com/informalsystems/ibc-rs/blob/1448a2bbc817da10b183b8479548a12344ba0e9c/modules/src/core/ics03_connection/msgs/conn_open_confirm.rs#L19-L23) + +* Channel + - [MsgChannelOpenInit](https://github.com/informalsystems/ibc-rs/blob/1448a2bbc817da10b183b8479548a12344ba0e9c/modules/src/core/ics04_channel/msgs/chan_open_init.rs#L17-L21) + - [MsgChannelOpenTry](https://github.com/informalsystems/ibc-rs/blob/1448a2bbc817da10b183b8479548a12344ba0e9c/modules/src/core/ics04_channel/msgs/chan_open_try.rs#L22-L29) + - [MsgChannelOpenAck](https://github.com/informalsystems/ibc-rs/blob/1448a2bbc817da10b183b8479548a12344ba0e9c/modules/src/core/ics04_channel/msgs/chan_open_ack.rs#L18-L25) + - [MsgChannelOpenConfirm](https://github.com/informalsystems/ibc-rs/blob/1448a2bbc817da10b183b8479548a12344ba0e9c/modules/src/core/ics04_channel/msgs/chan_open_confirm.rs#L18-L23) + - [MsgRecvPacket](https://github.com/informalsystems/ibc-rs/blob/1448a2bbc817da10b183b8479548a12344ba0e9c/modules/src/core/ics04_channel/msgs/recv_packet.rs#L19-L23) + - [MsgAcknowledgement](https://github.com/informalsystems/ibc-rs/blob/1448a2bbc817da10b183b8479548a12344ba0e9c/modules/src/core/ics04_channel/msgs/acknowledgement.rs#L19-L24) + - [MsgChannelCloseInit](https://github.com/informalsystems/ibc-rs/blob/1448a2bbc817da10b183b8479548a12344ba0e9c/modules/src/core/ics04_channel/msgs/chan_close_init.rs#L18-L22) + - [MsgChannelCloseConfirm](https://github.com/informalsystems/ibc-rs/blob/1448a2bbc817da10b183b8479548a12344ba0e9c/modules/src/core/ics04_channel/msgs/chan_close_confirm.rs#L20-L25) + - [MsgTimeout](https://github.com/informalsystems/ibc-rs/blob/1448a2bbc817da10b183b8479548a12344ba0e9c/modules/src/core/ics04_channel/msgs/timeout.rs#L19-L24) + - [MsgTimeoutOnClose](https://github.com/informalsystems/ibc-rs/blob/1448a2bbc817da10b183b8479548a12344ba0e9c/modules/src/core/ics04_channel/msgs/timeout_on_close.rs#L18-L23) + +* ICS20 FungibleTokenTransfer + - [MsgTransfer](https://github.com/informalsystems/ibc-rs/blob/1448a2bbc817da10b183b8479548a12344ba0e9c/modules/src/applications/ics20_fungible_token_transfer/msgs/transfer.rs#L20-L37) diff --git a/documentation/spec/src/masp.md b/documentation/spec/src/masp.md new file mode 100644 index 0000000000..4e1fadc087 --- /dev/null +++ b/documentation/spec/src/masp.md @@ -0,0 +1,10 @@ +## Multi-asset shielded pool + +The multi-asset shielded pool (MASP) is an extension to the Sapling circuit which adds support for sending arbitrary assets. + +See the following documents: +- [Cryptographic specification](https://github.com/anoma/masp/blob/main/docs/multi-asset-shielded-pool.pdf) +- [Ledger integration](./masp/ledger-integration.md) +- [Asset type schema](./masp/asset-type.md) +- [Burn and mint](./masp/burn-and-mint.md) +- [Convert circuit](./masp/convert-circuit.md) \ No newline at end of file diff --git a/documentation/spec/src/masp/asset-type.md b/documentation/spec/src/masp/asset-type.md new file mode 100644 index 0000000000..7504c7ef0f --- /dev/null +++ b/documentation/spec/src/masp/asset-type.md @@ -0,0 +1,60 @@ +## Asset name schema + +MASP notes carry balances that are some positive integer amount of an +asset type. Per both the MASP specification and the implementation, the +asset *identifier* is an 32-byte Blake2s hash of an arbitrary asset +*name* string, although the full 32-byte space is not used because the +identifier must itself hash to an elliptic curve point (currently +guaranteed by incrementing a nonce until the hash is a curve point). The +final curve point is the asset *type* proper, used in computations. + +The following is a schema for the arbitrary asset name string intended +to support various uses; at least fungible tokens and NFTs, but possibly +others. + +The asset name string is built up from a number of segments, joined by a +separator. We use `/` as the separator. + +Segments may be one of the following: + +- **Controlling address** segment: an Anoma address which controls the + asset. For example, this is the fungible token address for a fungible + token. This segment must be present, and must be first; it should in + theory be an error to transparently transact in assets of this type + without invoking the controlling address's VP. This should be achieved + automatically by all transparent changes involving storage keys under + the controlling address. + +- **Epoch** segment: An integer greater than zero, representing an epoch + associated with an asset type. Mainly for use by the incentive + circuit. This segment must be second if present. (should it be + required? could be 0 if the asset is unepoched) (should it be first so + we can exactly reuse storage keys?) This must be less than or equal to + the current epoch. + +- **Address** segment: An ancillary address somehow associated with the + asset. This address probably should have its VP invoked, and is + probably in the transparent balance storage key. + +- **ID** segment: A nonnegative (?) integer identifying something, i.e., + a NFT id. (should probably not be a u64 exactly - for instance, I + think ERC721 NFTs are u256) + +- **Text** segment: A piece of text, normatively but not necessarily + short (50 characters or less), identifying something. For + compatibility with non-numeric storage keys used in transparent assets + generally; an example might be a ticker symbol for a specific + sub-asset. The valid character set is the same as for storage keys. + +For example, suppose there is a virtual stock certificate asset, +incentivized (somehow), at transparent address `addr123`, which uses +storage keys like `addr123/[owner address]/[ticker symbol]/[id]`. The +asset name segments would be: + +- Controlling address: just `addr123` +- Epoch: the epoch when the note was created +- Owner address: an address segment +- Ticker symbol: a text segment +- ID: an ID segment + +This could be serialized to, e.g., `addr123/addr456/tSPY/i12345`. diff --git a/documentation/spec/src/masp/burn-and-mint.md b/documentation/spec/src/masp/burn-and-mint.md new file mode 100644 index 0000000000..67699657f7 --- /dev/null +++ b/documentation/spec/src/masp/burn-and-mint.md @@ -0,0 +1,69 @@ +# Burn and Mint conversion transactions in MASP + +## Introduction + +Ordinarily, a MASP transaction that does not shield or unshield assets must achieve a homomorphic net value balance of 0. Since every asset type has a pseudorandomly derived asset generator, it is not ordinarily feasible to achieve a net value balance of 0 for the transaction without each asset type independently having a net value balance of 0. Therefore, intentional burning and minting of assets typically requires a public "turnstile" where some collection of assets are unshielded, burned or minted in a public transaction, and then reshielded. Since this turnstile publicly reveals asset types and amounts, privacy is affected. + +The goal is to design an extension to MASP that allows for burning and minting assets according to a predetermined, fixed, public ratio, but without explicitly publicly revealing asset types or amounts in individual transactions. + +## Approach + +In the MASP, each Spend or Output circuit only verifies the integrity of spending or creation of a specific note, and does not verify the integrity of a transaction as a whole. To ensure that a transaction containing Spend and Output descriptions does not violate the invariants of the shielded pool (such as the total unspent balance of each asset in the pool) the value commitments are added homomorphically and this homomorphic sum is opened to reveal the transaction has a net value balance of 0. When assets are burned or minted in a MASP transaction, the homomorphic net value balance must be nonzero, and offset by shielding or unshielding a corresponding amount of each asset. + +Instead of requiring the homomorphic sum of Spend and Output value commitments to sum to 0, burning and minting of assets can be enabled by allowing the homomorphic sum of Spend and Output value commitments to sum to either 0 or a multiple of an allowed conversion ratio. For example, if distinct assets A and B can be converted in a 1-1 ratio (meaning one unit of A can be burned to mint one unit of B) then the Spend and Output value commitments may sum to a nonzero value. + +## Allowed conversions + +Let $A_1, A_2, \ldots, A_n$ be distinct asset types. An _allowed conversion_ is a list of tuples $\{(A_1, V_1), (A_2, V_2), \ldots (A_n, V_n)\}$ where $V_1, \ldots, V_n$ are signed 64-bit integers. + +The _asset generator_ of an allowed conversion is defined to be: $vb = [V_1] vb_1 + \ldots + [V_n] vb_n$ where $vb_i$ is the asset generator of asset $A_i$. + +Each allowed conversion is committed to a Jubjub point using a binding Bowe-Hopwood commitment of its asset generator (it is not necessary to be hiding). All allowed conversion commitments are stored in a public Merkle tree, similar to the Note commitment tree. Since the contents of this tree are entirely public, allowed conversions may be added, removed, or modified at any time. + +## Convert circuit + +In order for an unbalanced transaction containing burns and mints to get a net value balance of zero, one or more value commitments burning and minting assets must be added to the value balance. Similar to how Spend and Output circuits check the validity of their respective value commitments, the Convert circuit checks the validity and integrity of: + +1. There exists an allowed conversion commitment in the Merkle tree, and +1. The imbalance in the value commitment is a multiple of an allowed conversion's asset generator + +In particular, the Convert circuit takes public input: + +$$(rt, cv^{mint})$$ + +and private input: + +$$(path, pos, cm, v, rcv, vb)$$ + +and the circuit checks: + +1. Merkle Path validity: $path, pos$ is a valid Merkle path from $cm$ to $rt$. +2. Allowed conversion commitment integrity: $cm$ opens to $repr(vb)$ +3. Value commitment integrity: $cv^{mint} = [8*v] vb + [rcv] R$ where $R$ is the value commitment randomness base + +Note that 8 is the cofactor of the Jubjub curve. + +## Balance check + +Previously, the transaction consisted of Spend and Output descriptions, and a value balance check that the value commitment $cv^{in} - cv^{out}$ opens to 0. Now, the transaction validity includes: + +1. Checking the Convert description includes a valid and current $rt$ +2. Checking the value commitment $cv^{in} + cv^{mint} - cv^{out}$ opens to 0 + +### Directionality + +Directionality of allowed conversions must be enforced as well. That is, $v$ must be a non-negative 64 bit integer. If negative values of $v$ are allowed (or equivalently, unbounded large values of $v$ in the prime order scalar field of the Jubjub curve) then an allowed conversion could happen in the reverse direction, burning the assets intended to be minted and vice versa. + +### Cycles + +It is also critical not to allow cycles. For example, if $\{(A_1, -1), (A_2, 2)\}$ and $\{(A_1, 1), (A_2, -1)\}$ are allowed conversions, then an unlimited amount of $A_2$ may be minted from a nonzero amount of $A_1$. Since + +## Alternative approaches + +It may theoretically be possible to implement similar mechanisms with only the existing Spend and Output circuits. For example, a Merkle tree of many Notes could be created with asset generator $[-1] vb_1 + vb_2$ and many different values, allowing anyone to Spend these public Notes, which will only balance if proper amounts of asset type 1 are Spent and asset type 2 are Output. + +However, the Nullifier integrity check of the Spend circuit reveals the nullifier of each of these Notes, which removes the privacy of the conversion as the public nullifier is linkable to the allowed conversion. In addition, each Note has a fixed value, preventing arbitrary value conversions. + +## Conclusion + +In principle, as long as the Merkle tree only contains allowed conversions, this should permit the allowed conversions while maintaining other invariants. Note that since the asset generators are not derived in the circuit, all sequences of values and asset types are allowed. \ No newline at end of file diff --git a/documentation/spec/src/masp/convert-circuit.md b/documentation/spec/src/masp/convert-circuit.md new file mode 100644 index 0000000000..ffb94e41b1 --- /dev/null +++ b/documentation/spec/src/masp/convert-circuit.md @@ -0,0 +1,123 @@ +# Convert Circuit + +## Convert Circuit Description +The high-level description of `Convert` can be found [Brun and mint](./burn-and-mint.html). + +The `Convert` provides a mechanism that burning and minting of assets can be enabled by adding `Convert Value Vommitments` in transaction and ensuring the homomorphic sum of `Spend`, `Output` and `Convert` value commitments to be zero. + +The Convert value commitment is constructed from `AllowedConversion` which was published earlier in `AllowedConversion Tree`. The `AllowedConversion` defines the allowed conversion assets. The `AllowedConversion Tree` is a merkle hash tree stored in the ledger. + +## AllowedConversion +An `AllowedConversion` is a compound asset type in essence, which contains distinct asset types and the corresponding conversion ratios. + +`AllowedConversion` is an array of tuple $\{(t_1, v_1^{ratio}),(t_2, v_2^{ratio})...(t_n, v_n^{ratio})\}$ +* $t$: $\mathbb{B}^{\mathcal{l}_t}$ is a bytestring representing the asset identifier of the note. +* $v^{ratio}$: $v^{ratio}$ is a signed 64-bit integer in the range $\{−2^{63} .. 2^{63} − 1\}$. + +Calculate: + +* $vb_i := repr_{\mathbb{J}}(PRF^{vcgMASP}(t_i))$ +* $vb^{allowedconversion} = \Sigma_1^n([v_i^{ratio}]vb_i)$ +* $cm^{allowedconversion} = \mathsf{PedersenHashToPoint}(''MASP\_\_PH'', [1]^6||vb^{allowedconversion})$ + +Note that `PedersenHashToPoint` is used the same as in `NoteCommitment` for now. + +An `AllowedConversion` can be issued, removed and modified as public conversion rule by consensus authorization and stored in `AllowedConversion Tree` as leaf node. + +An `AllowedConversion` can be used by proving the existence in `AllowedConversion Tree`(must use the latest root anchor), and then generating a `Convert Value Commitment` to be used in transaction. + +## Convert Value Commitment +`Convert Value Commitment` is a tuple $(vb^{allowedconversion}, v^{convert}, rcv^{convert})$ +* $v^{convert}$: $v^{convert}$ is an unsigned integer representing the value of conversion in range $\{0 .. 2^{64} − 1\}$. + +Choose independent uniformly random commitment trapdoors: +* $rcv^{convert}$ $\leftarrow \mathsf{ValueCommit}\mathsf{.GenTrapdoor}()$ + +Check that $h_\mathbb{J}repr_{\mathbb{J}}(PRF^{vcgMASP}(vb^{allowedconversion}))$ is of type $KA^{Sapling}.PublicPrimeOrder$, i.e. it is a valid ctEdwards Curve point on the JubjubCurve (as defined in the original Sapling specification) not equal to $O_{\mathbb{J}}$. If it is equal to $O_{\mathbb{J}}$, $vb^{allowedconversion}$ is an invalid asset identifier. + +Calculate +* $cv^{convert} = [v^{convert} h_\mathbb{J}]vb^{allowedconversion} + [rcv^{convert}]\mathsf{GroupHash}_{\mathsf{URS}}^{\mathsf{\mathbb{J}^{(r)*}}}(''MASP\_\_r\_'',''r'')$ + +Note that $\mathsf{GroupHash}_{\mathsf{URS}}^{\mathsf{\mathbb{J}^{(r)*}}}(''MASP\_\_r\_'',''r'')$ is used the same as in `NoteCommitment` for now. + + +## AllowedConversion Tree +`AllowedConversion Tree` has the same structure as `Note Commitment Tree` and is an independent tree stored in ledger. +* $\mathsf{MerkleDepth^{Convert}}$: 32(for now) +* leaf node: $cm^{allowedconversion}$ + +## Convert Statement +The Convert circuit has 47358 constraints. + +Let $l_{MerkleSapling}$, $l_{scalar}$, $\mathsf{ValueCommit}$, $\mathsf{PedersenHashToPoint}$, $\mathsf{GroupHash}_{\mathsf{URS}}^{\mathsf{\mathbb{J}^{(r)*}}}$, $\mathbb{J}$ be as defined in the original Sapling specification. + +A valid instance of $\pi_{convert}$ assures that given a primary input: +* $rt^{convert}: \mathbb{B}^{l_{MerkleSapling}}$ +* $cv^{convert}: \mathsf{ValueCommit.Output}$ + +the prover knows an auxiliary input: +* $path: \mathbb{B}^{[l_{Merkle}][MerkleDepth^{Convert}]}$ +* $pos: (0..2^{MerkleDepth^{Convert}}-1)$ +* $cm^{allowedconversion}: \mathbb{B}^{MerkleSapling}$ +* $vb^{allowedconversion}: \mathbb{J}$ +* $rcv^{convert}: \{0..2^{l_{scalar}}-1\})$ +* $v^{convert}: \{0..2^{l_{convert\_value}}-1\}$ + +such that the following conditions hold: +* AllowedConversion cm integrity: $cm^{allowedconversion} = \mathsf{PedersenHashToPoint}(''MASP\_\_PH'', [1]^6||vb^{allowedconversion})$ + +* Merkle path validity: Either $v^{convert}$ is 0; or $(path, pos)$ is a valid Merkle path of depth $MerkleDepth^{Convert}$, as as defined in the original Sapling specification, from $cm^{allowedconversion}$ to the anchor $rt^{convert}$ + +* Small order checks: $vb^{allowedconversion}$ is not of small order, i.e.$[h_\mathbb{J}]vb^{allowedconversion} \neq O_\mathbb{J}$. + +* Convert Value Commitment integrity: $cv^{convert} = [v^{convert} h_\mathbb{J}]vb^{allowedconversion} + [rcv^{convert}]\mathsf{GroupHash}_{\mathsf{URS}}^{\mathsf{\mathbb{J}^{(r)*}}}(''MASP\_\_r\_'',''r'')$ + +Return $(cv^{convert}, rt^{convert},\pi_{convert})$ + +Notes: +* Public and auxiliary inputs MUST be constrained to have the types specified. In particular, see the original Sapling specification, for required validity checks on compressed representations of Jubjub curve points. The ValueCommit.Output type also represents points, i.e. $\mathbb{J}$. +* In the Merkle path validity check, each layer does not check that its input bit sequence is a canonical encoding(in {${0 .. r_{\mathbb{S}} − 1}$}) of the integer from the previous layer. + +## Incentive Description + +Incentive system provide a mechanism in which the old asset(input) is burned, the new asset(output) is minted with the same quantity and incentive asset(reward) is minted with the convert ratio meanwhile. + +### Incentive AllowedConversion Tree +As described in Convert circuit, the `AllowedConversion Tree` is an independent merkle tree in the ledger and contains all the Incentive AllowedConversions. + +### Incentive AllowedConversion Struct +In general, there are three items in `Incentive AllowedConversion Struct`(but not mandatory?),i.e. input, output and reward. And each item has an asset type and a quantity(i64, for the convert ratio). + +Note that the absolute value of input and output must be consistent in incentive system. The quantity of input is negative and the quantity of output is positive. + +To guarantee the input and output to be open as the same asset type in future unshielding transactions, the input and output assets have the same prefix description(e.g. BTC_1, BTC_2...BTC_n). And to prevent repeated shielding and unshielding and encourage long-term contribution to privacy pool, the postfix `timestamp` is used to distinct the input and output assets. The `timestamp` is depended on the update period and can be defined flexibly(e.g. date, epoch num). When new `timestamp` occurs, the `AllowedConversion` will be updated to support all the "history asset" conversion to the latest one. + +### Incentive AllowedConversion Operatioin +`Incentive AllowedConversion` is governed by incentive system, who will be in charge of issuing new incentive plan, updating(modifying) to the latest `timestamp` and destroying the disabled. + +* Issue + * Issue a new incentive plan for new asset. + * Issue for the last latest `AllowedConversion` when new `timestamp` occurs. +* Update + * For every new `timestamp` occurs, updating the existing `AllowedConversion`. Keep the input still, update the output to the latest asset and modify the reward quantity according to the ratio. +* Destroy + * Detele the `AllowedConversion` from the tree. +* Query Service + * A service for querying the latest `AllowedConversion`, return (anchor, path, AllowedConversion). + + +### Workflow from User's Perspective +* Shielding transaction + * Query the latest `timestamp` for target asset(non-latest will be rejected in tx execution) + * Construct a target shielded note and shielding tx + * Add the note to shielded pool if tx executes successfully(check the prefix and the latest `timestamp`). +* Converting transaction + * Construct spend notes from shielded notes + * Construct convert notes(query the latest `AllowedConversion`) + * Construct output notes + * Construct convert tx + * Get incentive output notes with latest `timestamp` and rewards if tx executes successfully +* Unshielding transaction + * Construct unshielding transaction + * Get unshielded note if tx executes successfully(check the prefix) + diff --git a/documentation/spec/src/masp/ledger-integration.md b/documentation/spec/src/masp/ledger-integration.md new file mode 100644 index 0000000000..ebd0294af1 --- /dev/null +++ b/documentation/spec/src/masp/ledger-integration.md @@ -0,0 +1,440 @@ +# MASP integration spec + +## Overview + +The overall aim of this integration is to have the ability to provide a +multi-asset shielded pool following the MASP spec as an account on the +current Anoma blockchain implementation. + +## Shielded pool VP + +The shielded value pool can be an Anoma "established account" with a +validity predicate which handles the verification of shielded +transactions. Similarly to zcash, the asset balance of the shielded pool +itself is transparent - that is, from the transparent perspective, the +MASP is just an account holding assets. The shielded pool VP has the +following functions: + +- Accept only valid transactions involving assets moving in or out of + the pool. +- Accept valid shielded-to-shielded transactions, which don't move + assets from the perspective of transparent Anoma. +- Publish the note commitment and nullifier reveal Merkle trees. + +To make this possible, the host environment needs to provide +verification primitives to VPs. One possibility is to provide a single +high-level "verify transaction output descriptions and proofs" +operation, but another is to provide cryptographic functions in the host +environment and implement the verifier as part of the VP. + +The shielded pool needs the ability to update the commitment and +nullifier Merkle trees as it receives transactions. This may possibly be +achievable via the temporary storage mechanism added for IBC, with the +trees finalized with each block. + +The input to the VP is the following set of state changes: + +- updates to the shielded pool's asset balances +- new encrypted notes +- updated note and nullifier tree states (partial, because we only have + the last block's anchor?) + +and the following data which is ancillary from the ledger's perspective: + +- spend descriptions, which destroy old notes: +``` +struct SpendDescription { + // Value commitment to amount of the asset in the note being spent + cv: jubjub::ExtendedPoint, + // Last block's commitment tree root + anchor: bls12_381::Scalar, + // Nullifier for the note being nullified + nullifier: [u8; 32], + // Re-randomized version of the spend authorization key + rk: PublicKey, + // Spend authorization signature + spend_auth_sig: Signature, + // Zero-knowledge proof of the note and proof-authorizing key + zkproof: Proof, +} +``` +- output descriptions, which create new notes: +``` +struct OutputDescription { + // Value commitment to amount of the asset in the note being created + cv: jubjub::ExtendedPoint, + // Derived commitment tree location for the output note + cmu: bls12_381::Scalar, + // Note encryption public key + epk: jubjub::ExtendedPoint, + // Encrypted note ciphertext + c_enc: [u8; ENC_CIPHERTEXT_SIZE], + // Encrypted note key recovery ciphertext + c_out: [u8; OUT_CIPHERTEXT_SIZE], + // Zero-knowledge proof of the new encrypted note's location (?) + zkproof: Proof, +} +``` + +Given these inputs: + +The VP must verify the proofs for all spend and output descriptions +(`bellman::groth16`), as well as the signature for spend notes. + +Encrypted notes from output descriptions must be published in the +storage so that holders of the viewing key can view them; however, the +VP does not concern itself with plaintext notes. + +Nullifiers and commitments must be appended to their respective Merkle +trees in the VP's storage as well, which is a transaction-level rather +than a block-level state update. + +Additionally to the individual spend and output description +verifications, the final transparent asset value change described in the +transaction must equal the pool asset value change, and as an additional +sanity check, the pool's balance of any asset may not end up negative +(this may already be impossible?). (needs more input) + +NB: Shielded-to-shielded transactions in an asset do not, from the +ledger's perspective, transact in that asset; therefore, the asset's own +VP is not run as described above, and cannot be because the shielded +pool is asset-hiding. + +## Client capabilities +The client should be able to: +* Make transactions with a shielded sender and/or receiver +* Scan the blockchain to determine shielded assets in one's possession +* Generate payment addresses from viewing keys from spending keys + +To make shielded transactions, the client has to be capable of creating +and spending notes, and generating proofs which the pool VP verifies. + +Unlike the VP, which must have the ability to do complex verifications, +the transaction code for shielded transactions can be comparatively +simple: it delivers the transparent value changes in or out of the pool, +if any, and proof data computed offline by the client. + +The client and wallet must be extended to support the shielded pool and +the cryptographic operations needed to interact with it. From the +perspective of the transparent Anoma protocol, a shielded transaction is +just a data write to the MASP storage, unless it moves value in or out +of the pool. The client needs the capability to create notes, +transactions, and proofs of transactions, but it has the advantage of +simply being able to link against the MASP crates, unlike the VP. + +### Shielded Address/Key Generation +#### Spending Key Generation +The client should be able to generate a spending key and automatically +derive a viewing key for it. The spending key should be usable as the +source of a transfer. The viewing key should be usable to determine the +total unspent notes that the spending key is authorized to spend. It +should not be possible to directly or indirectly use the viewing key to +spend funds. Below is an example of how spending keys should be +generated: +``` +anomaw --masp gen-key --alias my-sk +``` +#### Payment Address Generation +The client should be able to generate a payment address from a +spending key or viewing key. This payment address should be usable +to send notes to the originating spending key. It should not be +directly or indirectly usable to either spend notes or view shielded +balances. Below are examples of how payment addresses should be +generated: +``` +anomaw masp gen-addr --alias my-pa1 --key my-sk +anomaw masp gen-addr --alias my-pa2 --key my-vk +``` +#### Manual Key/Address Addition +The client should be able to directly add raw spending keys, viewing +keys, and payment addresses. Below are examples of how these objects +should be added: +``` +anomaw masp add --alias my-sk --value xsktest1qqqqqqqqqqqqqq9v0sls5r5de7njx8ehu49pqgmqr9ygelg87l5x8y4s9r0pjlvu69au6gn3su5ewneas486hdccyayx32hxvt64p3d0hfuprpgcgv2q9gdx3jvxrn02f0nnp3jtdd6f5vwscfuyum083cvfv4jun75ak5sdgrm2pthzj3sflxc0jx0edrakx3vdcngrfjmru8ywkguru8mxss2uuqxdlglaz6undx5h8w7g70t2es850g48xzdkqay5qs0yw06rtxcvedhsv +anomaw masp add --alias my-vk --value xfvktest1qqqqqqqqqqqqqqpagte43rsza46v55dlz8cffahv0fnr6eqacvnrkyuf9lmndgal7erg38awgq60r259csg3lxeeyy5355f5nj3ywpeqgd2guqd73uxz46645d0ayt9em88wflka0vsrq29u47x55psw93ly80lvftzdr5ccrzuuedtf6fala4r4nnazm9y9hq5yu6pq24arjskmpv4mdgfn3spffxxv8ugvym36kmnj45jcvvmm227vqjm5fq8882yhjsq97p7xrwqt7n63v +anomaw masp add --alias my-pa --value patest10qy6fuwef9leccl6dfm7wwlyd336x4y32hz62cnrvlrl6r5yk0jnw80kus33x34a5peg2xc4csn +``` +### Making Shielded Transactions +#### Shielding Transactions +The client should be able to make shielding transactions by providing a +transparent source address and a shielded payment address. The +main transparent effect of such a transaction should be a deduction of +the specified amount from the source address, and a corresponding +increase in the balance of the MASP validity predicate's address. The +gas fee is charged to the source address. Once the transaction is +completed, the spending key that was used to generate the payment address +will have the authority to spend the amount that was send. Below is an +example of how a shielding transacion should be made: +``` +anomac transfer --source Bertha --amount 50 --token BTC --target my-pa +``` +#### Unshielding Transactions +The client should be able to make unshielding transactions by providing +a shielded spending key and a transparent target address. The main +transparent effect of such a transaction should be a deduction of the +specified amount from the MASP validity predicate's address and a +corresponding increase in the transparent target address. The gas fee +is charged to the signer's address (which should default to the target +address). Once the transaction is complete, the spending key will no +longer be able to spend the transferred amount. Below is an example of +how an unshielding transaction should be made: +``` +anomac transfer --target Bertha --amount 45 --token BTC --source my-sk +``` +#### Shielded Transactions +The client should be able to make shielded transactions by providing a +shielded spending key and a shielded payment address. There should be +no change in the transparent balance of the MASP validity predicate's +address. The gas fee is charged to the signer's address. Once the +transaction is complete, the spending key will no longer be able to +spend the transferred amount, but the spending key that was used to +(directly or indirectly) generate the payment address will. Below is +an example of how a shielded transaction should be made: +``` +anomac transfer --source my-sk --amount 5 --token BTC --target your-pa +``` +### Viewing Shielded Balances +The client should be able to view shielded balances. The most +general output should be a list of pairs, each denoting a token +type and the unspent amount of that token present at each shielded +address whose viewing key is represented in the wallet. Note that +it should be possible to restrict the balance query to check only +a specific viewing key or for a specific token type. Below are +examples of how balance queries should be made: +``` +anomac balance +anomac balance --owner my-key +anomac balance --owner my-key --token BTC +anomac balance --token BTC +``` +### Listing Shielded Keys/Addresses +The wallet should be able to list all the spending keys, viewing keys, +and payment addresses that it stores. Below are examples of how the +wallet's storage should be queried: +``` +anomaw masp list-keys +anomaw masp list-keys --unsafe-show-secret +anomaw masp list-keys --unsafe-show-secret --decrypt +anomaw masp list-addrs +``` +### Finding Shielded Keys/Addresses +The wallet should be able to find any spending key, viewing key or +payment address when given its alias. Below are examples of how the +wallet's storage should be queried: +``` +anomaw masp find --alias my-alias +anomaw masp find --alias my-alias --unsafe-show-secret +``` + +## Protocol + +### Note Format +The note structure encodes an asset's type, its quantity and its owner. +More precisely, it has the following format: +``` +struct Note { + // Diversifier for recipient address + d: jubjub::SubgroupPoint, + // Diversified public transmission key for recipient address + pk_d: jubjub::SubgroupPoint, + // Asset value in the note + value: u64, + // Pedersen commitment trapdoor + rseed: Rseed, + // Asset identifier for this note + asset_type: AssetType, + // Arbitrary data chosen by note sender + memo: [u8; 512], +} +``` +For cryptographic details and further information, see +[Note Plaintexts and Memo Fields](https://zips.z.cash/protocol/protocol.pdf#noteptconcept). +Note that this structure is required only by the client; the VP only +handles commitments to this data. + +Diversifiers are selected (randomly?) by the client and used to +diversify addresses and their associated keys. `v` and `t` identify the +asset type and value. Asset identifiers are derived from asset names, +which are arbitrary strings (in this case, token/other asset VP +addresses). The derivation must deterministically result in an +identifier which hashes to a valid curve point. + +### Transaction Format +The transaction data structure comprises a list of transparent inputs +and outputs as well as a list of shielded inputs and outputs. More +precisely: +``` +struct Transaction { + // Transaction version + version: u32, + // Transparent inputs + tx_in: Vec, + // Transparent outputs + tx_out: Vec, + // The net value of Sapling spends minus outputs + value_balance_sapling: Vec<(u64, AssetType)>, + // A sequence ofSpend descriptions + spends_sapling: Vec, + // A sequence ofOutput descriptions + outputs_sapling: Vec, + // A binding signature on the SIGHASH transaction hash, + binding_sig_sapling: [u8; 64], +} +``` +For the cryptographic constraints and further information, see +[Transaction Encoding and Consensus](https://zips.z.cash/protocol/protocol.pdf#txnencoding). +Note that this structure slightly deviates from Sapling due to +the fact that `value_balance_sapling` needs to be provided for +each asset type. + +### Transparent Input Format +The input data structure decribes how much of each asset is +being deducted from certain accounts. More precisely, it is as follows: +``` +struct TxIn { + // Source address + address: Address, + // Asset identifier for this input + token: AssetType, + // Asset value in the input + amount: u64, + // A signature over the hash of the transaction + sig: Signature, + // Used to verify the owner's signature + pk: PublicKey, +} +``` +Note that the signature and public key are required to authenticate +the deductions. +### Transparent Output Format +The output data structure decribes how much is being added to +certain accounts. More precisely, it is as follows: +``` +struct TxOut { + // Destination address + address: Address, + // Asset identifier for this output + token: AssetType, + // Asset value in the output + amount: u64, +} +``` +Note that in contrast to Sapling's UTXO based approach, our +transparent inputs/outputs are based on the account model used +in the rest of Anoma. + +# Shielded Transfer Specification +## Transfer Format +Shielded transactions are implemented as an optional extension to transparent ledger transfers. The optional `shielded` field in combination with the `source` and `target` field determine whether the transfer is shielding, shielded, or unshielded. See the transfer format below: +``` +/// A simple bilateral token transfer +#[derive(..., BorshSerialize, BorshDeserialize, ...)] +pub struct Transfer { + /// Source address will spend the tokens + pub source: Address, + /// Target address will receive the tokens + pub target: Address, + /// Token's address + pub token: Address, + /// The amount of tokens + pub amount: Amount, + /// Shielded transaction part + pub shielded: Option, +} +``` +## Conditions +Below, the conditions necessary for a valid shielded or unshielded transfer are outlined: +* A shielded component equal to `None` indicates a transparent Anoma transaction +* Otherwise the shielded component must have the form `Some(x)` where `x` has the transaction encoding specified in the [Multi-Asset Shielded Pool Specication](https://raw.githubusercontent.com/anoma/masp/main/docs/multi-asset-shielded-pool.pdf) +* Hence for a shielded transaction to be valid: + * the `Transfer` must satisfy the usual conditions for Anoma ledger transfers (i.e. sufficient funds, ...) as enforced by token and account validity predicates + * the `Transaction` must satisfy the conditions specified in the [Multi-Asset Shielded Pool Specication](https://raw.githubusercontent.com/anoma/masp/main/docs/multi-asset-shielded-pool.pdf) + * the `Transaction` and `Transfer` together must additionaly satisfy the below boundary conditions intended to ensure consistency between the MASP validity predicate ledger and Anoma ledger + +### Boundary Conditions +Below, the conditions necessary to maintain consistency between the MASP validity predicate ledger and Anoma ledger are outlined: +* If the target address is the MASP validity predicate, then no transparent outputs are permitted in the shielded transaction +* If the target address is not the MASP validity predicate, then: + * there must be exactly one transparent output in the shielded transaction and: + * its public key must be the hash of the target address bytes - this prevents replay attacks altering transfer destinations + * the hash is specifically a RIPEMD-160 of a SHA-256 of the input bytes + * its value must equal that of the containing transfer - this prevents replay attacks altering transfer amounts + * its asset type must be derived from the token address raw bytes - this prevents replay attacks altering transfer asset types + * the derivation must be done as specified in `0.3 Derivation of Asset Generator from Asset Identifer` +* If the source address is the MASP validity predicate, then: + * no transparent inputs are permitted in the shielded transaction + * the transparent transaction value pool's amount must equal the containing wrapper transaction's fee amount + * the transparent transaction value pool's asset type must be derived from the containing wrapper transaction's fee token + * the derivation must be done as specified in `0.3 Derivation of Asset Generator from Asset Identifer` +* If the source address is not the MASP validity predicate, then: + * there must be exactly one transparent input in the shielded transaction and: + * its value must equal that of amount in the containing transfer - this prevents stealing/losing funds from/to the pool + * its asset type must be derived from the token address raw bytes - this prevents stealing/losing funds from/to the pool + * the derivation must be done as specified in `0.3 Derivation of Asset Generator from Asset Identifer` + +## Remarks +Below are miscellaneous remarks on the capabilities and limitations of the current MASP implementation: +* The gas fees for shielded transactions are charged to the signer just like it is done for transparent transactions + * As a consequence, an amount exceeding the gas fees must be available in a transparent account in order to execute an unshielding transaction - this prevents denial of service attacks +* Using the MASP sentinel transaction key for transaction signing indicates that gas be drawn from the transaction's transparent value pool + * In this case, the gas will be taken from the MASP transparent address if the shielded transaction is proven to be valid + +## Multi-Asset Shielded Pool Specification Differences from Zcash Protocol Specification +The [Multi-Asset Shielded Pool Specication](https://raw.githubusercontent.com/anoma/masp/main/docs/multi-asset-shielded-pool.pdf) referenced above is in turn an extension to the [Zcash Protocol Specification](https://zips.z.cash/protocol/protocol.pdf). Below, the changes from the Zcash Protocol Specification assumed to have been integrated into the Multi-Asset Shielded Pool Specification are listed: +* [3.2 Notes](https://zips.z.cash/protocol/protocol.pdf#notes) + * Sapling note tuple must include asset type + * Note commitment must be parameterized by asset type + * [3.2.1 Note Plaintexts and Memo Fields](https://zips.z.cash/protocol/protocol.pdf#noteptconcept) + * Note plaintext tuple must include asset type +* [4.1.8 Commitment](https://zips.z.cash/protocol/protocol.pdf#abstractcommit) + * `NoteCommit` and `ValueCommit` must be parameterized by asset type +* [4.7.2 Sending Notes (Sapling)](https://zips.z.cash/protocol/protocol.pdf#saplingsend) + * Sender must also be able to select asset type + * `NoteCommit` and hence `cm` must be parameterized by asset type + * `ValueCommit` and hence `cv` must be parameterized by asset type + * The note plaintext tuple must include asset type +* [4.8.2 Dummy Notes (Sapling)](https://zips.z.cash/protocol/protocol.pdf#saplingdummynotes) + * A random asset type must also be selected + * `NoteCommit` and hence `cm` must be parameterized by asset type + * `ValueCommit` and hence `cv` must be parameterized by asset type +* [4.13 Balance and Binding Signature (Sapling)](https://zips.z.cash/protocol/protocol.pdf#saplingbalance) + * The Sapling balance value is no longer a scalar but a vector of pairs comprising values and asset types + * Addition, subtraction, and equality checks of Sapling balance values is now done component-wise + * A Sapling balance value is defined to be non-negative iff each of its components is non-negative + * `ValueCommit` and the value base must be parameterized by asset type + * Proofs must be updated to reflect the presence of multiple value bases +* [4.19.1 Encryption (Sapling and Orchard)](https://zips.z.cash/protocol/protocol.pdf#saplingandorchardencrypt) + * The note plaintext tuple must include asset type +* [4.19.2 Decryption using an Incoming Viewing Key (Sapling and Orchard)](https://zips.z.cash/protocol/protocol.pdf#decryptivk) + * The note plaintext extracted from the decryption must include asset type +* [4.19.3 Decryption using a Full Viewing Key (Sapling and Orchard)](https://zips.z.cash/protocol/protocol.pdf#decryptovk) + * The note plaintext extracted from the decryption must include asset type +* [5.4.8.2 Windowed Pedersen commitments](https://zips.z.cash/protocol/protocol.pdf#concretewindowedcommit) + * `NoteCommit` must be parameterized by asset type +* [5.4.8.3 Homomorphic Pedersen commitments (Sapling and Orchard)](https://zips.z.cash/protocol/protocol.pdf#concretehomomorphiccommit) + * `HomomorphicPedersenCommit`, `ValueCommit`, and value base must be parameterized by asset type +* [5.5 Encodings of Note Plaintexts and Memo Fields](https://zips.z.cash/protocol/protocol.pdf#notept) + * The note plaintext tuple must include asset type + * The Sapling note plaintext encoding must use 32 bytes inbetween `d` and `v` to encode asset type + * Hence the total size of a note plaintext encoding should be 596 bytes +* [5.6 Encodings of Addresses and Keys](https://zips.z.cash/protocol/protocol.pdf#addressandkeyencoding) + * Bech32m \[[BIP-0350](https://github.com/bitcoin/bips/blob/master/bip-0350.mediawiki)\] is used instead of Bech32 \[[ZIP-173](https://zips.z.cash/zip-0173)\] to further encode the raw encodings +* [5.6.3.1 Sapling Payment Addresses](https://zips.z.cash/protocol/protocol.pdf#saplingpaymentaddrencoding) + * For payment addresses on the Testnet, the Human-Readable Part is "patest" +* [7.1 Transaction Encoding and Consensus](https://zips.z.cash/protocol/protocol.pdf#txnencoding) + * `valueBalanceSapling` is no longer scalar. Hence it should be replaced by two components: + * `nValueBalanceSapling`: a `compactSize` indicating number of asset types spanned by balance + * a length `nValueBalanceSapling` sequence of 40 byte values where: + * the first 32 bytes encode the asset type + * the last 8 bytes are an `int64` encoding asset value +* [7.4 Output Description Encoding and Consensus](https://zips.z.cash/protocol/protocol.pdf#outputencodingandconsensus) + * The `encCiphertext` field must be 612 bytes in order to make 32 bytes room to encode the asset type + +## Required Changes to ZIP 32: Shielded Hierarchical Deterministic Wallets +Below, the changes from [ZIP 32: Shielded Hierarchical Deterministic Wallets](https://zips.z.cash/zip-0032) assumed to have been integrated into the Multi-Asset Shielded Pool Specification are listed: +* [Specification: Key Encodings](https://zips.z.cash/zip-0032#specification-key-encodings) + * Bech32m \[[BIP-0350](https://github.com/bitcoin/bips/blob/master/bip-0350.mediawiki)\] is used instead of Bech32 \[[BIP-173](https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki)\] to further encode the raw encodings +* [Sapling extended spending keys](https://zips.z.cash/zip-0032#sapling-extended-spending-keys) + * For extended spending keys on the Testnet, the Human-Readable Part is "xsktest" +* [Sapling extended full viewing keys](https://zips.z.cash/zip-0032#sapling-extended-full-viewing-keys) + * For extended full viewing keys on the Testnet, the Human-Readable Part is "xfvktest" diff --git a/documentation/spec/src/masp/trusted-setup-assets/contribution-sequence-diagram.png b/documentation/spec/src/masp/trusted-setup-assets/contribution-sequence-diagram.png new file mode 100644 index 0000000000..ca17ccbd31 Binary files /dev/null and b/documentation/spec/src/masp/trusted-setup-assets/contribution-sequence-diagram.png differ diff --git a/documentation/spec/src/masp/trusted-setup-assets/optimistic-mmorpg-mpc-diagram.png b/documentation/spec/src/masp/trusted-setup-assets/optimistic-mmorpg-mpc-diagram.png new file mode 100644 index 0000000000..daf9433bff Binary files /dev/null and b/documentation/spec/src/masp/trusted-setup-assets/optimistic-mmorpg-mpc-diagram.png differ diff --git a/documentation/spec/src/masp/trusted-setup-assets/trusted-setup-actors.png b/documentation/spec/src/masp/trusted-setup-assets/trusted-setup-actors.png new file mode 100644 index 0000000000..7d8f9ec1dd Binary files /dev/null and b/documentation/spec/src/masp/trusted-setup-assets/trusted-setup-actors.png differ diff --git a/documentation/spec/src/masp/trusted-setup.md b/documentation/spec/src/masp/trusted-setup.md new file mode 100644 index 0000000000..8b1b33a2a7 --- /dev/null +++ b/documentation/spec/src/masp/trusted-setup.md @@ -0,0 +1,170 @@ +# Namada Trusted Setup +This spec assumes that you have some previous knowledge about Trusted Setup Ceremonies. If not, you might want to check the following two articles: [Setup Ceremonies - ZKProof](https://zkproof.org/2021/06/30/setup-ceremonies/) and [Parameter Generation - Zcash](https://z.cash/technology/paramgen/). + +The Namada Trusted Setup (TS) consists of running the phase 2 of the MPC which is a circuit-specific step to construct the multi-asset shielded pool circuit. Our phase 2 takes as input the Powers of Tau (phase 1) ran by Zcash that can be found [here](https://download.z.cash/downloads/powersoftau/). + +## Overview of the contribution flow +NOTE: add CLI flag `--offline` for the contributors that run on an offline machine. The flag will skip all the steps where there is communication with the coordinator and go straight to the generation of parameters in step 14. +1. Contributor downloads the Namada CLI source from GitHub, compiles it, runs it. +2. CLI asks the Contributor a couple of questions: + a) Do you want to participate in the incentivized trusted setup? + - Yes. Asks for personal information: full name and email. + - No. Contribution will be identified as Anonymous. + b) Do you want to take part in the contest? + - Yes. This assumes that the Contributor will generate the parameters with another method than the default implementation and that in any case the contribution file will be uploaded through the CLI. + - No. Pursue with the normal flow. +3. CLI generates a `ed25519` key pair that will serve to communicate and sign requests with the HTTP REST API endpoints and receive any potential rewards. + The private key is generated through [BIP39](https://docs.rs/tiny-bip39/latest/bip39/#) where we use it as a seed for the `ed25519` key pair and a 24 word seed-phrase is presented to the user to back-up. +5. CLI sends request to the HTTP REST API endpoint `contributor/join_queue`. Contributor is added to the queue of the ceremony. +6. CLI polls periodically the HTTP REST API endpoint `contributor/queue_status` to get the current the position in the queue. CLI also sends periodically a heartbeat request to HTTP REST API endpoint `contributor/heartbeat` to tell the Coordinator that it is still connected. CLI shows the current position in the queue to the contributor. +7. When Contributor is in position 0 in the queue, it leaves the queue. CLI can then acquire the lock of the next chunk by sending a request to the HTTP REST API endpoint `contributor/lock_chunk`. +8. As soon as the file is locked on the Coordinator, the CLI asks for more info about the chunk through the endpoint `download/chunk`. This info is later needed to form a new contribution file and send it back to the Coordinator. +9. CLI gets the actual blob challenge file by sending a request to the endpoint `contributor/challenge`. +10. CLI saves challenge file `anoma_challenge_round_{round_number}.params` in the root folder. +11. CLI computes challenge hash. +12. CLI creates contribution file `anoma_contribution_round_{round_number}_public_key_{public_key}.params` in the root folder. +13. Previous challenge hash is appended to the contribution file. +14. Contributor decides whether to do the computation on the same machine or on a different machine. +Do you want to use another machine to run your contribution? +NOTE: be clear that if users choose to generate the parameters on a OFFLINE machine then they will have max. 15 min to do all the operations. +- No. Participant will use the Online Machine to contribute. CLI runs `contribute_masp()` that executes the same functions as in the `contribute()` function from the `masp-mpc` crate. + CLI asks the contributor if he wants to input a custom seed of randomness instead of using the combination of entropy and OS randomness. In both cases, he has to input something. + CLI creates a contribution file signature `ContributionFileSignature` of the contribution. +- Yes. Participant will use an Offline Machine to contribute. CLI display a message with instructions about the challenge and contribution files. Participant can export the Contribution file `anoma_contribution_round_{round_number}_public_key_{public_key}.params` to the Offline Machine and contribute from there. When the Contributor is done, he gives the path to the contribution file. Before continuing, CLI checks if the required files are available on path and if the transformation of the parameters is valid. +NOTE: CLI will display a countdown of 10 min with an extension capability of 5 min. +14. CLI generates a json file saved locally that contains the full name, email, the public key used for the contribution, contribution hash, hash of the contribution file, contribution file signature, plus a signature of the metadata. -> display the signature and message that needs to be posted somewhere over the Internet +15. CLI uploads the chunk to the Coordinator by using the endpoint `upload/chunk`. +16. When the contribution blob was transferred successfully to the Coordinator. CLI notifies the Coordinator that the chunk was uploaded by sending a request to endpoint `contributor/contribute_chunk`. +17. Coordinator verifies that the chunk is valid by executing function `verify_transform()` from the crate `masp-mpc`. If the transformation is correct, it outputs the hash of the contribution. +18. Coordinator calls `try_advance()` function that tries to advance to the next round as soon as all contributions are verified. If it succeeds, it removes the next contributor from the queue and adds him as contribturo to the next round. +19. Repeat. + +## Subcomponents + +![](https://hackmd.io/_uploads/BJ-OIAhD5.png) + +Our implementation of the TS consists of the following subcomponents: +1. A fork of the [Aleo Trusted Setup](https://github.com/AleoHQ/aleo-setup/) where we re-used the Coordinator Library (CL) contained in the `phase1-coordinator` folder. +2. A [HTTP REST API](https://github.com/anoma/trusted-setup-ceremony/blob/master/phase1-coordinator/src/rest.rs) that interfaces with the CL. +3. A [CLI implementation](https://github.com/anoma/trusted-setup-ceremony/tree/master/phase1-cli) that communicates with the HTTP REST API endpoints. +4. An integration of the [`masp-mpc`](https://github.com/heliaxdev/masp-mpc) crypto functions (`initialize`, `contribute` and `verify`) in the CL. + +Let's go through each subcomponents and describe them. + +## 1. Coordinator Library (CL) +### Description +The CL handles the operation steps of the ceremony: adding a new contributor to the queue, authentificating a contributor, sending and receiving challenge files, removing inactive contributors, reattributing challenge file to a new contributor after a contributor dropped, verifying contributions, creating new files... + +### "Historical" context +The CL was originally implemented for the Powers of Tau (phase 1 of the MPC). In this implementation, there was a tentative to optimise the whole operational complexity of the ceremony. In short, to reduce the contribution time to the parameters, the idea was to split the parameters during a round into multiple chunks that can be then distributed to multiple participants in parallel. That way, the computation time would be reduced by some linear factor on a per-round basis. You can read more about it [in this article](https://zkproof.org/2021/06/30/setup-ceremonies/ ). + +### CL in the Namada context +Splitting the parameters into multiple chunks is useful, if it takes hours to contribute. In our case, the contribution time is about some seconds and in the worst case about some minutes. So, we don't need to split the parameters into chunks. Though, since we forked from the Aleo Trusted Setup, we still have some references to "chunked" things like folder, variable or function names. In our implementation, this means that we have one contributor and one chunk per round. For example, the contribution file of a round `i` from a participant will always be located at `transcript/round_{i}/chunk_0/contribution_1.unverified`. +To be able to re-use the CL without heavy refactoring, we decided to keep most of the Aleo code as it is and only change the parts that needed to be changed, more precisely the crypto functions (`initialize`, `contribute` and `verify`) and the coordinator config `environment.rs`. + +## 2. HTTP REST API +### Description +The HTTP REST API is a rocket web server that interfaces with the CL. All requests need to be signed to be accepted by the endpoints. It's the core of the ceremony where the Coordinator is started together with utility functions like `verify_contributions`  and `update_coordinator`. + +### Endpoints +- `contributor/join_queue` +- `contributor/lock_chunk` +- `download/chunk` +- `contributor/challenge` +- `upload/chunk` +- `contributor/contribute_chunk` +- `contributor/heartbeat` +- `contributor/get_tasks_left` + +### Saved files +- `contributors/namada_contributor_info_round_{i}.json` contributor info received from the participant. Same file as described below. +- `contributors.json` list of contributors that can be exposed to a public API to be displayed on the website +```json +[ + { + "public_key":"very random public key", + "is_another_machine":true, + "is_own_seed_of_randomness":true, + "ceremony_round":1, + "contribution_hash":"some hash", + "contribution_hash_signature":"some hash", + // (optional) some timestamps that can be used to calculate and display the contribution time + "timestamp":{ + "start_contribution":1, + "end_contribution":7 + } + }, + // ... + { + "public_key":"very random public key", + "is_another_machine":true, + "is_own_seed_of_randomness":true, + "ceremony_round":42, + "contribution_hash":"some hash", + "contribution_hash_signature":"some hash", + "timestamp":{ + "start_contribution":1, + "end_contribution":7 + } + } +] +``` + +## 3. CLI Implementation +### Description +The CLI communicates with the HTTP REST API accordingly to the [overview of the contribution flow](#overview-of-the-contribution-flow). + +### Saved files +- `namada_challenge_round_{round_number}.params` challenge file downloaded from the Coordinator. +- `namada_contribution_round_{round_number}.params` contribution file that needs to be uploaded to the Coordinator +- `namada_contributor_info_round_{round_number}.json` contributor info that serves to identify participants. +```json +{ + "full_name":"John Cage", + "email":"john@cage.me", + // ed25519 public key + "public_key":"very random public key", + // User participates in incentivized program or not + "is_incentivized":true, + // User expresses his intent to participate or not in the contest for creative contributions + "is_contest_participant":true, + // User can choose to contribute on another machine + "is_another_machine":true, + // User can choose the default method to generate randomness or his own. + "is_own_seed_of_randomness":true, + "ceremony_round":42, + // hash of the contribution run by masp-mpc, contained in the transcript + "contribution_hash":"some hash", + // FIXME: is this necessary? so other user can check the contribution hash against the public key? + "contribution_hash_signature":"signature of the contribution hash", + // hash of the file saved on disk and sent to the coordinator + "contribution_file_hash":"some hash", + "contribution_file_signature":"signature of the contribution file", + // Some timestamps to get performance metrics of the ceremony + "timestamp":{ + // User starts the CLI + "start_contribution":1, + // User has joined the queue + "joined_queue":2, + // User has locked the challenge on the coordinator + "challenge_locked":3, + // User has completed the download of the challenge + "challenge_downloaded":4, + // User starts computation locally or downloads the file to another machine + "start_computation":5, + // User finishes computation locally or uploads the file from another machine + "end_computation":6, + // User attests that the file was uploaded correctly + "end_contribution":7 + }, + "contributor_info_signature":"signature of the above fields and data" +} +``` + +## 4. Integration of the `masp-mpc` +### Description +There are 4 crypto commands available in the CL under `phase1-coordinator/src/commands/`: +1. `aggregations.rs` this was originally used to aggregate the chunks of the parameters. Since we don't have chunks, we don't need to aggregate anything. However, this logic was required and kept to transition between rounds. It doesn't affect any contribution file. +2. `computation.rs` is used by a participant to contribute. The function `contribute_masp()` contains the logic from `masp-mpc/src/bin/contribute.rs`. +3. `initialization.rs` is used to bootstrap the parameters on round 0 by giving as input the [Zcash's Powers of Tau](https://download.z.cash/downloads/powersoftau/). The function `initialize_masp()` contains the logic from `masp-mpc/src/bin/new.rs`. +4. `verification.rs` is used to verify the correct transformation of the parameters between contributions. The function `verify_masp()` contains the logic from `masp-mpc/src/bin/verify_transform.rs`. diff --git a/documentation/spec/src/user-interfaces.md b/documentation/spec/src/user-interfaces.md new file mode 100644 index 0000000000..e912e146da --- /dev/null +++ b/documentation/spec/src/user-interfaces.md @@ -0,0 +1,3 @@ +## User interfaces + +Namada includes a canonical [web interface](./user-interfaces/web-wallet-interface.md), a [block explorer](./user-interfaces/web-explorer-interface.md), and other miscellaneous [external integrations](./user-interfaces/external-integrations.md). \ No newline at end of file diff --git a/documentation/spec/src/user-interfaces/explorer.md b/documentation/spec/src/user-interfaces/explorer.md new file mode 100644 index 0000000000..9c6225d674 --- /dev/null +++ b/documentation/spec/src/user-interfaces/explorer.md @@ -0,0 +1,9 @@ +# Web explorer interface + +* Block explorer + * Display PoS state + * Display governance state + * Display transparent transfers + * Display transfers in and out of the MASP + * Display total values for the MASP + * Allows tx hashes of shielded transfers to be looked up for confirmation diff --git a/documentation/spec/src/user-interfaces/external-integrations.md b/documentation/spec/src/user-interfaces/external-integrations.md new file mode 100644 index 0000000000..f6fd4855b6 --- /dev/null +++ b/documentation/spec/src/user-interfaces/external-integrations.md @@ -0,0 +1,17 @@ +# External integrations + +## Integrations + +* Custodians + * Aegies +* Indexer for Namada to integrate with block explorers +* Validator tooling +* Stakeholder support + * Rosetta integration + * Datahub integration +* WalletConnect integration +* Ledger integration +* External integrations + * Figment + * P2P + * Indexers & Explorers diff --git a/documentation/spec/src/user-interfaces/stakingAndGovernance.png b/documentation/spec/src/user-interfaces/stakingAndGovernance.png new file mode 100644 index 0000000000..65c990beed Binary files /dev/null and b/documentation/spec/src/user-interfaces/stakingAndGovernance.png differ diff --git a/documentation/spec/src/user-interfaces/web-explorer-interface.md b/documentation/spec/src/user-interfaces/web-explorer-interface.md new file mode 100644 index 0000000000..9c6225d674 --- /dev/null +++ b/documentation/spec/src/user-interfaces/web-explorer-interface.md @@ -0,0 +1,9 @@ +# Web explorer interface + +* Block explorer + * Display PoS state + * Display governance state + * Display transparent transfers + * Display transfers in and out of the MASP + * Display total values for the MASP + * Allows tx hashes of shielded transfers to be looked up for confirmation diff --git a/documentation/spec/src/user-interfaces/web-wallet-interface.md b/documentation/spec/src/user-interfaces/web-wallet-interface.md new file mode 100644 index 0000000000..615a74178f --- /dev/null +++ b/documentation/spec/src/user-interfaces/web-wallet-interface.md @@ -0,0 +1,50 @@ +# Web wallet interface + +## Application Features + +The application consist of the an UI that allows the user to perform the following actions in an easy to use and consistent web application: + +### Seed Phrase +[hifi Designs](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=4610%3A5890) +* Can setup a new seed phrase and derive accounts on it [wireframe](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6442%3A5866) +* When creating the seed phrase, the user can export it copied to the clipboard, user has to confirm the saving of the seed phrase [wireframe](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6442%3A6015) [wireframe](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6442%3A6104) +* Restore accounts from a seed phrase +* Can retrieve a forgotten seed phrase, this requires user to enter the main password + +### User accounts +[hifi Designs](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=5165%3A8862) +* When entering the app, the user is being prompted for a password to decrypt the key pair to be able to perform actions [wireframe](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6442%3A5801) +* Can create accounts derived from the master key pair +* Can delete accounts +* User can integrated with Ledger hardware wallet + * Set up flow TBD + * Managing TBD +* Can see an overview of the assets in the account and all derived accounts [wireframe](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6442%3A5492) +* Can see the details of a single asset, containing the following information [wireframe](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6442%3A5681) + * Balance + * All past transactions for the current account and asset + * Button to initiate a new transfer using this asset + +### Transfers +[TBD]() +* Can create transparent transfers +* Can create shielded transfers +* Bi-directional transfer between Namada and ETH + * Supports approving transactions with MetaMask +* Bi-directional transfer between Namada and IBC supported chains + * Supports approving transactions with Keplr + +### Staking & Governance +[TBD]() +* Can bond funds to a list of validators +* Can un-bond funds to a list of validators +* Can submit proposals +* Can vote on proposals +* Can follow up with the current and past proposals and their vote results + +## Tech Stack +### Core Application +* Core application is built on React/TypeScript +* State management with Redux +* Application styling is accomplished with styled-components +* Extensive usage of WASM compiled Rust code from the common Anoma code base is encouraged where ever feasible. diff --git a/documentation/spec/src/user-interfaces/web-wallet.md b/documentation/spec/src/user-interfaces/web-wallet.md new file mode 100644 index 0000000000..370a0a5397 --- /dev/null +++ b/documentation/spec/src/user-interfaces/web-wallet.md @@ -0,0 +1,208 @@ +

Web Wallet UI and Features

+ +- [LockScreen](#lockscreen) + - [LockScreen](#lockscreen-1) +- [AccountOverview](#accountoverview) + - [AccountOverview](#accountoverview-1) + - [AccountOverview/TokenDetails](#accountoverviewtokendetails) + - [AccountOverview/TokenDetails/Receive](#accountoverviewtokendetailsreceive) + - [AccountOverview/TokenDetails/Send](#accountoverviewtokendetailssend) +- [StakingAndGovernance](#stakingandgovernance) + - [StakingAndGovernance](#stakingandgovernance-1) + - [StakingAndGovernance/Staking](#stakingandgovernancestaking) + - [StakingAndGovernance/ValidatorDetails](#stakingandgovernancevalidatordetails) + - [StakingAndGovernance/Proposals](#stakingandgovernanceproposals) + - [StakingAndGovernance/Proposals/AddProposal](#stakingandgovernanceproposalsaddproposal) +- [Settings](#settings) + - [Settings](#settings-1) + - [Settings/WalletSettings](#settingswalletsettings) + - [Settings/Accounts](#settingsaccounts) + - [Settings/Accounts/NewAccount](#settingsaccountsnewaccount) + - [Settings/AccountSettings](#settingsaccountsettings) + +The application is divided to 4 main sections: +* LockScreen +* AccountOverview +* StakingAndGovernance +* Settings + +These are further divided to individual screens or flows (comprising several screens) grouping activities that belong together. For example, under **StakingAndGovernance** we have: + +* **StakingAndGovernance/Staking** - which gives the user the possibility to see all the validators and navigate to a screen where the actual staking is performed. + +Each screen listed below is associated with a high level wireframe design to give a visual presentation of the user interface. Each view is named and being referred with that name through out all communication and in the codebase. + + + +*This screen represents StakingAndGovernance/Staking view* + + +## LockScreen +When the user accesses the wallet for the first time there is a need to create a new account. This screen gives the user to possibility to do so or unlock the wallet by using an existing account. + +### LockScreen +[Wireframe](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6442%3A5801) +User can: +* can to unlock the wallet by entering the master password +* can to start a flow to create a new account + +## AccountOverview +This is the most important part of the application and the part where the user spends the most time. Here the user performs the most common tasks such as creating transactions. Only one account is selected as a time and the selected account is indicated here. + +### AccountOverview +[Wireframe](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6442%3A5492) +User can: +* see the aggregated balance in fiat currency +* can see the currently selected account address +* can navigate to **Settings/Accounts** for changing the account +* can see a listing of all hold tokens and their logos, balances, names + + +### AccountOverview/TokenDetails +[Wireframe](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6442%3A5681) +User can: +* can see the balance of token in native and fiat currency +* can navigate to **AccountOverview/TokenDetails/Receive** for receiving tokens +* can navigate to **AccountOverview/TokenDetails/Send** for sending tokens +* can see a listing of past transaction of the current account and selected token + +### AccountOverview/TokenDetails/Receive +[Wireframe](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6472%3A6476) +User can: +* see QR code of the address +* see address as a string and copy it by clicking button + +### AccountOverview/TokenDetails/Send +[Wireframe 1](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6472%3A9579) +[Wireframe 2](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6472%3A9715) +[Wireframe 3](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6472%3A9797) +User can: +view 1: +* see the balance of the token in current account +* enter details: transfer amount, recipient address, memo +* can select to perform the transaction as shielded + +view 2: +* see a summary of the transaction details +* clear indication whether the transaction is transparent of shielded +* select a gas fee +* see an option in gas fees that is specific for shielded transactions +* see a transaction summary including gas fee + +view 3: +* see a confirmation once the transaction is confirmed +* be abel to navigate to see the new transaction in the block explorer +* be able to navigate back to **AccountOverview/TokenDetails** + + + +## StakingAndGovernance +Aside of **AccountOverview** this is a part that the user is likely visiting quite frequently. All staking and governance related activities are performed here. + +### StakingAndGovernance +[Wireframe](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6496%3A6316) +User can: +* see a dashboard with the most interesting information regarding staking +* see a dashboard with the most interesting information regarding governance +* can navigate to **StakingAndGovernance/Staking** for performing staking actions +* can navigate to **StakingAndGovernance/Proposals** for performing governance actions + +### StakingAndGovernance/Staking +[Wireframe 1](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6496%3A6377) +[Wireframe 2](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6496%3A14001) +[Wireframe 3](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6496%3A14101) +User can: +view 1: +* view a listing of validators +* be able to navigate to aaa for seeing further details about the validator +* select to stake with one of them + +view 2: +* select an amount to stake +* see a summary of the staking transaction + +view 3: +* see a confirmation of a successful staking with the selected validator + +### StakingAndGovernance/ValidatorDetails +[Wireframe](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6496%3A13919) +User can: +* can see all relevant details of the validator + +### StakingAndGovernance/Proposals +[Wireframe](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6496%3A14167) +User can: +* see a listing of all open proposals +* be able to vote for yes, no, no with veto and abstain +* see the current vote share per proposal +* navigate to **StakingAndGovernance/Proposals/AddProposal** for adding a new proposal + +### StakingAndGovernance/Proposals/AddProposal +[Wireframe 1](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6496%3A14286) +[Wireframe 2](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6496%3A14405) +User can: +view 1: +* enter the details (TBD) of the proposal +* see a summary of the proposal +* submit the proposal + +view 2: +* see a confirmation of successfully submitted proposal + +## Settings +This is a part of the application that is visited less often. This is where the user can change settings of select the active account. + +### Settings +[Wireframe](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6496%3A13327) +User can: +* Navigate to **Settings/Accounts** +* Navigate to **Settings/WalletSettings** + +### Settings/WalletSettings +[Wireframe](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6496%3A6235) +User can: +* see and change the fiat currency to display in various locations in the app where amounts are being displayed in fiat currency +* Default fiat currency is USD + +### Settings/Accounts +[Wireframe](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6472%3A9901) +User can: +* select an account by clicking it, when it becomes visibly selected +* can navigate to **Settings/AccountSettings** for changing the settings of certain account +* can navigate to Settings/Accounts/NewAccount/Start for adding a new account to the wallet + + +### Settings/Accounts/NewAccount +[Wireframe 1](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6442%3A5866) +[Wireframe 2](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6442%3A5956) +[Wireframe 3](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6442%3A6015) +[Wireframe 4](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6442%3A6104) +[Wireframe 5](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6442%3A6190) +User can: + +view 1: +* see a welcome screen that explain the flow + +view 2: +* enter an alias to the account +* enter and confirm a password +* select the length of the seed phrase (12 or 24 words) + +view 3: +* see a seed phrase that was generated +* copy the seed phrase to clipboard + +view 4: +* enter a randomly requested word from the set of words. ("please enter word #5") + +view 5: +* see a confirmation that the account was created +* navigate to **AccountOverview** and so that the newly created account becomes the selected account + +### Settings/AccountSettings +[Wireframe](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6472%3A10076) +User can: +* Rename the selected account +* display the seed phrase, user is being prompted for a password +* delete account, user is prompted to input a security text to prevent an accidental deletion +* select the network \ No newline at end of file diff --git a/documentation/spec/src/user-interfaces/web-wallet/client-application.md b/documentation/spec/src/user-interfaces/web-wallet/client-application.md new file mode 100644 index 0000000000..d586fef8e4 --- /dev/null +++ b/documentation/spec/src/user-interfaces/web-wallet/client-application.md @@ -0,0 +1,66 @@ +# Client Application + +### React Web Application + +- Built with TypeScript +- State-management with Redux Toolkit (`@reduxjs/toolkit`) +- CRA (create-react-app) scripts v5 with Craco to enable yarn workspaces (monorepo package management) +- `wasm-react-scripts` - enabling WebAssembly files into the Webpack pipeline +- Styled-Componenents for all application/component styling + +## WebAssembly Library + +Much of the core functionality of the web app requires either direct interfacing with types from the Anoma codebase, or other Rust libraries that provide encryption, key-management, mnemonic-generation, etc., that are more easily and robustly handled in the Rust ecosystem than that of TypeScript. + +The primary functionality that we currently pull from `anoma` involves constructing transactions. The web wallet interface should be able to serialize the data broadcast to the ledger for different transactions, and this requires items to be serialized within the WebAssembly code. We created `anoma-lib`, which houses wrapped Anoma types (wrapped when some work is needed to get it to work well with wasm), and the logic needed for us to be able to interface with it from TypeScript. + +The Rust source code `anoma-lib` is structured as follows: + +```bash +. +├── types +│ ├── address.rs +│ ├── keypair.rs +│ ├── mod.rs +│ ├── transaction.rs +│ ├── tx.rs +│ └── wrapper.rs +├── account.rs +├── lib.rs +├── transfer.rs +├── utils.rs +``` + +Here, we have several types that are essentially built on top of `anoma` types, allowing us to interface easily from the client app, such as `address`, `keypair`, `tx`, and `wrapper`, then a generic `transaction` type that handles the logic common to all transactions. Essentially, we want these types to handle any serialization that the `anoma` types require entirely within the wasm, then later translate the results into something the client can understand. + +Outside of types, we have an `account.rs` file that allows us to call account functions, such as `initialize` (to construct an "init-account" transaction), from the client app. `transfer.rs` is similar, in that it provides the bridge for the client to issue a transfer transaction. Additional transactions can be easily created in this way, with a specific differences being handled in a top level Rust source file, the common logic of transactions handled by `types/transaction`, and any types that need extra work in order to be useful to the client being added as well to `types`. + +## Interfacing between the Client and WebAssembly + +When compiling the `wasm` utilizing `wasm-pack`, we get the associated JavaScript source to interact with the WebAssembly output, as well as a TypeScript type definition file. When we set the `wasm-pack` target to `web`, we get an additional exported `init` function, which is a promise that resolves when the wasm is fully loaded, exposing the `memory` variable. In most cases we shouldn't need to interact directly with the memory of the wasm, but by awaiting the `init()` call, we can immediately execute any of the wasm methods. + +In the case of `anoma-lib`, there is a corresponding class that initializes and exposes the features of the wasm in `anoma-wallet`, called `AnomaClient`. (**NOTE**: This is one use case for wasm, but we may have any number of wasm projects that the wallet can utilize). Exposing the features through a TypeScript class is a good opportunity to move from Rust-style "snake-casing" to camel-casing (most common in TypeScript), and any additional type definitions we can add at this level as well. + +The goal of bridging wasm and the client TypeScript application should be to make its usage as straightforward as any TypeScript class. It should also be fairly easy for the developer to add new features to the Rust source and quickly bring that into the client app. + +### Dealing with Rust types in TypeScript + +One of the challenges of working with WebAssembly is how we might go about handling types from Rust code. We are limited to what JavaScript can handle, and often when serializing output from the wasm, we'll choose a simple type like `string` or `number`, or send the data as a byte array (very common, especially when dealing with numbers larger than JavaScript can handle by default). Sending raw data to the client is often a decent solution, then any encoding we prefer we can enact on the client-side (hexadecimal, base58, base64, etc), and choosing a Rust type like `Vec` makes this straight-forward. _(More to come on this topic in the future)_ + +There is much more nuance to handling types from Rust wasm in TypeScript when working with `wasm-bindgen`, and more information can be found at the following URL: + +https://rustwasm.github.io/wasm-bindgen/reference/types.html + +## Testing with WebAssembly + +The wallet-interface should be able to run within the Jest testing framework. This is made possibly by switching our `wasm-pack` target and rebuilding before the test is run, as tests run within NodeJS. So, instead of the following: + +```bash +wasm-pack build ../anoma-lib/ --out-dir ../anoma-wallet/src/lib/anoma --out-name anoma --target web + +``` +We would issue this in order to support Jest in NodeJS: + +```bash +wasm-pack build ../anoma-lib/ --out-dir ../anoma-wallet/src/lib/anoma --out-name anoma --target nodejs +``` diff --git a/documentation/spec/src/user-interfaces/web-wallet/features.md b/documentation/spec/src/user-interfaces/web-wallet/features.md new file mode 100644 index 0000000000..45f9a8f120 --- /dev/null +++ b/documentation/spec/src/user-interfaces/web-wallet/features.md @@ -0,0 +1,51 @@ +# Web Wallet + +## Application Features + +The application consist of the an UI that allows the user to perform the following actions in an easy to use and consistent web application: + +### Seed Phrase + +[hifi Designs](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=4610%3A5890) + +- Can setup a new seed phrase and derive accounts on it [wireframe](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6442%3A5866) +- When creating the seed phrase, the user can export it copied to the clipboard, user has to confirm the saving of the seed phrase [wireframe](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6442%3A6015) [wireframe](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6442%3A6104) +- Restore accounts from a seed phrase +- Can retrieve a forgotten seed phrase, this requires user to enter the main password + +### User accounts + +[hifi Designs](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=5165%3A8862) + +- When entering the app, the user is being prompted for a password to decrypt the key pair to be able to perform actions [wireframe](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6442%3A5801) +- Can create accounts derived from the master key pair +- Can delete accounts +- User can integrated with Ledger hardware wallet + - Set up flow TBD + - Managing TBD +- Can see an overview of the assets in the account and all derived accounts [wireframe](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6442%3A5492) +- Can see the details of a single asset, containing the following information [wireframe](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6442%3A5681) + - Balance + - All past transactions for the current account and asset + - Button to initiate a new transfer using this asset + +### Transfers + +[TBD]() + +- Can create transparent transfers +- Can create shielded transfers +- Bi-directional transfer between Namada and ETH + - Supports approving transactions with MetaMask +- Bi-directional transfer between Namada and IBC supported chains + - Supports approving transactions with Keplr + +### Staking & Governance + +[TBD]() + +- Can bond funds to a list of validators +- Can un-bond funds to a list of validators +- Can submit proposals +- Can vote on proposals +- Can follow up with the current and past proposals and their vote results diff --git a/documentation/spec/src/user-interfaces/web-wallet/ibc.md b/documentation/spec/src/user-interfaces/web-wallet/ibc.md new file mode 100644 index 0000000000..00364674d8 --- /dev/null +++ b/documentation/spec/src/user-interfaces/web-wallet/ibc.md @@ -0,0 +1,105 @@ +## IBC Protocol + +The web wallet must be able to transfer token amounts to other chains via the Inter-Blockchain Communication Protocol (IBC). + +We need to be able to support the following: + +- Fungible token transfer (ICS020) from Namada to other Anoma chains +- Fungible token transfer (ICS020) from Namada to Cosmos + +What the UI will need to display to the user: + +- Select a chain (chain ID) as destination +- Enter a channel ID for destination (e.g., `channel-0`) +- Specify a receiver address +- Specify a token +- Specify an amount to transfer + +The web wallet will need to construct a `MsgTransfer` struct, which will get wrapped in a normal, signed transaction and broadcasted to the source ledger (this struct is passed into the `Tx` `data`): + +```rust +MsgTransfer { + source_port: String, + source_channel: String, + token: Option, + sender: Signer, + receiver: Signer, + timeout_height: Height, + timeout_timestamp: Timestamp +} +``` + +A populated `MsgTransfer` with a disabled block-height timeout (instead using a timestamp timeout), may look like the following: + +```rust +MsgTransfer { + source_port: PortId("transfer"), + source_channel: ChannelId("channel-0"), + token: Some(Coin { + denom: "atest1v4ehgw36x3prswzxggunzv6pxqmnvdj9xvcyzvpsggeyvs3cg9qnywf589qnwvfsg5erg3fkl09rg5", + amount: "1.23456" + }), + sender: Signer( "atest1v4ehgw36xvmrgdfsg9rrwdzxgfprq32yxvensdjxgcurxwpeg5mrxdpjxfp5gdp3xqu5gs2xd8k4aj" + ), + receiver: Signer( "atest1d9khqw36xu6njwp4x5eyz334g4zrjvz9gyungv6p8yurys3jxymrxvzy89pyzv2pxaprzsfedvglv2" + ), + timeout_height: Height { + revision: 0, + height: 0 + }, + timeout_timestamp: Timestamp { + time: Some(Time(PrimitiveDateTime { + date: Date { + year: 2022, + ordinal: 124 + }, + time: Time { + hour: 14, + minute: 15, + second: 33, + nanosecond: 0 + } + })) + } +} +``` + +**NOTE** Unlike with `tx_transfer`, the amount we pass with the Token is _not_ submitted in micro-units, but as a regular `f32` value. No conversion is needed in the web wallet. + +Once this transaction is unwrapped and validated, `apply_tx` will invoke `IBC.dispatch()` (see: ). + +When this is executed on the source chain, the balance will be deducted on the source account, so we need to reflect this in the interface. If the transaction succeeds, query +the balance for that token and display to the user. + +## Testing + +Instructions for setting up local Namada chains, along with the Hermes relatyer (`ibc-rs`) can be found here: + + + +The wallet UI will need to be configured to connect to the source chain from which you want to transfer tokens. The user will have to enter a valid channel ID +in the interface, in addition to an established address on the destination chain (the receiver). + +## Configuration + +The wallet web app should accept a configuration per-environment that will contain not only the default network, but the possible destination networks that the user can transfer tokens to. We need the following information for each, at a minimum: + +- A user-friendly alias naming the network +- Destination URL +- Destination Port +- A non-default `portId`, if necessary, though in most cases, the default of `transfer` would likely be used. + +## Resources + +- [Anoma Ledger IBC Rust Docs](https://docs.anoma.network/master/rustdoc/anoma/ledger/ibc/) +- [HackMD IBC Summary](https://hackmd.io/H2yGO3IQRLiWCPWwQQdVow) +- [ibc-rs](https://github.com/informalsystems/ibc-rs/) +- [ICS020 - Fungible Token Transfers](https://github.com/cosmos/ibc/blob/master/spec/app/ics-020-fungible-token-transfer/README.md) +- +- +- + +Cosmos relayers: + +- +- diff --git a/documentation/spec/src/user-interfaces/web-wallet/interface-technical-specifications.md b/documentation/spec/src/user-interfaces/web-wallet/interface-technical-specifications.md new file mode 100644 index 0000000000..cef534ccb3 --- /dev/null +++ b/documentation/spec/src/user-interfaces/web-wallet/interface-technical-specifications.md @@ -0,0 +1,66 @@ +# Interface Technical Specifications + +### React Web Application + +- Built with TypeScript +- State-management with Redux Toolkit (`@reduxjs/toolkit`) +- CRA (create-react-app) scripts v5 with Craco to enable yarn workspaces (monorepo package management) +- `wasm-react-scripts` - enabling WebAssembly files into the Webpack pipeline +- Styled-Componenents for all application/component styling + +## WebAssembly Library + +Much of the core functionality of the web app requires either direct interfacing with types from the Anoma codebase, or other Rust libraries that provide encryption, key-management, mnemonic-generation, etc., that are more easily and robustly handled in the Rust ecosystem than that of TypeScript. + +The primary functionality that we currently pull from `anoma` involves constructing transactions. The web wallet interface should be able to serialize the data broadcast to the ledger for different transactions, and this requires items to be serialized within the WebAssembly code. We created `anoma-lib`, which houses wrapped Anoma types (wrapped when some work is needed to get it to work well with wasm), and the logic needed for us to be able to interface with it from TypeScript. + +The Rust source code `anoma-lib` is structured as follows: + +```bash +. +├── types +│ ├── address.rs +│ ├── keypair.rs +│ ├── mod.rs +│ ├── transaction.rs +│ ├── tx.rs +│ └── wrapper.rs +├── account.rs +├── lib.rs +├── transfer.rs +├── utils.rs +``` + +Here, we have several types that are essentially built on top of `anoma` types, allowing us to interface easily from the client app, such as `address`, `keypair`, `tx`, and `wrapper`, then a generic `transaction` type that handles the logic common to all transactions. Essentially, we want these types to handle any serialization that the `anoma` types require entirely within the wasm, then later translate the results into something the client can understand. + +Outside of types, we have an `account.rs` file that allows us to call account functions, such as `initialize` (to construct an "init-account" transaction), from the client app. `transfer.rs` is similar, in that it provides the bridge for the client to issue a transfer transaction. Additional transactions can be easily created in this way, with a specific differences being handled in a top level Rust source file, the common logic of transactions handled by `types/transaction`, and any types that need extra work in order to be useful to the client being added as well to `types`. + +## Interfacing between the Client and WebAssembly + +When compiling the `wasm` utilizing `wasm-pack`, we get the associated JavaScript source to interact with the WebAssembly output, as well as a TypeScript type definition file. When we set the `wasm-pack` target to `web`, we get an additional exported `init` function, which is a promise that resolves when the wasm is fully loaded, exposing the `memory` variable. In most cases we shouldn't need to interact directly with the memory of the wasm, but by awaiting the `init()` call, we can immediately execute any of the wasm methods. + +In the case of `anoma-lib`, there is a corresponding class that initializes and exposes the features of the wasm in `anoma-wallet`, called `AnomaClient`. (**NOTE**: This is one use case for wasm, but we may have any number of wasm projects that the wallet can utilize). Exposing the features through a TypeScript class is a good opportunity to move from Rust-style "snake-casing" to camel-casing (most common in TypeScript), and any additional type definitions we can add at this level as well. + +The goal of bridging wasm and the client TypeScript application should be to make its usage as straightforward as any TypeScript class. It should also be fairly easy for the developer to add new features to the Rust source and quickly bring that into the client app. + +### Dealing with Rust types in TypeScript + +One of the challenges of working with WebAssembly is how we might go about handling types from Rust code. We are limited to what JavaScript can handle, and often when serializing output from the wasm, we'll choose a simple type like `string` or `number`, or send the data as a byte array (very common, especially when dealing with numbers larger than JavaScript can handle by default). Sending raw data to the client is often a decent solution, then any encoding we prefer we can enact on the client-side (hexadecimal, base58, base64, etc), and choosing a Rust type like `Vec` makes this straight-forward. _(More to come on this topic in the future)_ + +There is much more nuance to handling types from Rust wasm in TypeScript when working with `wasm-bindgen`, and more information can be found at the following URL: + +https://rustwasm.github.io/wasm-bindgen/reference/types.html + +## Testing with WebAssembly + +The wallet-interface should be able to run within the Jest testing framework. This is made possibly by switching our `wasm-pack` target and rebuilding before the test is run, as tests run within NodeJS. So, instead of the following: + +```bash +wasm-pack build ../anoma-lib/ --out-dir ../anoma-wallet/src/lib/anoma --out-name anoma --target web + +``` +We would issue this in order to support Jest in NodeJS: + +```bash +wasm-pack build ../anoma-lib/ --out-dir ../anoma-wallet/src/lib/anoma --out-name anoma --target nodejs +``` \ No newline at end of file diff --git a/documentation/spec/src/user-interfaces/web-wallet/interface.md b/documentation/spec/src/user-interfaces/web-wallet/interface.md new file mode 100644 index 0000000000..0d0a2f773c --- /dev/null +++ b/documentation/spec/src/user-interfaces/web-wallet/interface.md @@ -0,0 +1,68 @@ +# Web Wallet + +## Interface Technical Specifications + +### React Web Application + +- Built with TypeScript +- State-management with Redux Toolkit (`@reduxjs/toolkit`) +- CRA (create-react-app) scripts v5 with Craco to enable yarn workspaces (monorepo package management) +- `wasm-react-scripts` - enabling WebAssembly files into the Webpack pipeline +- Styled-Componenents for all application/component styling + +## WebAssembly Library + +Much of the core functionality of the web app requires either direct interfacing with types from the Anoma codebase, or other Rust libraries that provide encryption, key-management, mnemonic-generation, etc., that are more easily and robustly handled in the Rust ecosystem than that of TypeScript. + +The primary functionality that we currently pull from `anoma` involves constructing transactions. The web wallet interface should be able to serialize the data broadcast to the ledger for different transactions, and this requires items to be serialized within the WebAssembly code. We created `anoma-lib`, which houses wrapped Anoma types (wrapped when some work is needed to get it to work well with wasm), and the logic needed for us to be able to interface with it from TypeScript. + +The Rust source code `anoma-lib` is structured as follows: + +```bash +. +├── types +│ ├── address.rs +│ ├── keypair.rs +│ ├── mod.rs +│ ├── transaction.rs +│ ├── tx.rs +│ └── wrapper.rs +├── account.rs +├── lib.rs +├── transfer.rs +├── utils.rs +``` + +Here, we have several types that are essentially built on top of `anoma` types, allowing us to interface easily from the client app, such as `address`, `keypair`, `tx`, and `wrapper`, then a generic `transaction` type that handles the logic common to all transactions. Essentially, we want these types to handle any serialization that the `anoma` types require entirely within the wasm, then later translate the results into something the client can understand. + +Outside of types, we have an `account.rs` file that allows us to call account functions, such as `initialize` (to construct an "init-account" transaction), from the client app. `transfer.rs` is similar, in that it provides the bridge for the client to issue a transfer transaction. Additional transactions can be easily created in this way, with a specific differences being handled in a top level Rust source file, the common logic of transactions handled by `types/transaction`, and any types that need extra work in order to be useful to the client being added as well to `types`. + +## Interfacing between the Client and WebAssembly + +When compiling the `wasm` utilizing `wasm-pack`, we get the associated JavaScript source to interact with the WebAssembly output, as well as a TypeScript type definition file. When we set the `wasm-pack` target to `web`, we get an additional exported `init` function, which is a promise that resolves when the wasm is fully loaded, exposing the `memory` variable. In most cases we shouldn't need to interact directly with the memory of the wasm, but by awaiting the `init()` call, we can immediately execute any of the wasm methods. + +In the case of `anoma-lib`, there is a corresponding class that initializes and exposes the features of the wasm in `anoma-wallet`, called `AnomaClient`. (**NOTE**: This is one use case for wasm, but we may have any number of wasm projects that the wallet can utilize). Exposing the features through a TypeScript class is a good opportunity to move from Rust-style "snake-casing" to camel-casing (most common in TypeScript), and any additional type definitions we can add at this level as well. + +The goal of bridging wasm and the client TypeScript application should be to make its usage as straightforward as any TypeScript class. It should also be fairly easy for the developer to add new features to the Rust source and quickly bring that into the client app. + +### Dealing with Rust types in TypeScript + +One of the challenges of working with WebAssembly is how we might go about handling types from Rust code. We are limited to what JavaScript can handle, and often when serializing output from the wasm, we'll choose a simple type like `string` or `number`, or send the data as a byte array (very common, especially when dealing with numbers larger than JavaScript can handle by default). Sending raw data to the client is often a decent solution, then any encoding we prefer we can enact on the client-side (hexadecimal, base58, base64, etc), and choosing a Rust type like `Vec` makes this straight-forward. _(More to come on this topic in the future)_ + +There is much more nuance to handling types from Rust wasm in TypeScript when working with `wasm-bindgen`, and more information can be found at the following URL: + +https://rustwasm.github.io/wasm-bindgen/reference/types.html + +## Testing with WebAssembly + +The wallet-interface should be able to run within the Jest testing framework. This is made possibly by switching our `wasm-pack` target and rebuilding before the test is run, as tests run within NodeJS. So, instead of the following: + +```bash +wasm-pack build ../anoma-lib/ --out-dir ../anoma-wallet/src/lib/anoma --out-name anoma --target web +``` + +We would issue this in order to support Jest in NodeJS: + +```bash +wasm-pack build ../anoma-lib/ --out-dir ../anoma-wallet/src/lib/anoma --out-name anoma --target nodejs +``` diff --git a/documentation/spec/src/user-interfaces/web-wallet/key-derivation.md b/documentation/spec/src/user-interfaces/web-wallet/key-derivation.md new file mode 100644 index 0000000000..c478319713 --- /dev/null +++ b/documentation/spec/src/user-interfaces/web-wallet/key-derivation.md @@ -0,0 +1,36 @@ +## Key Derivation (transparent addresses) + +Given a master seed (a 12 or 24 word `bip39` mnemonic), the user should be able to derive additional accounts deterministically. + +The wallet currently implements functionality to derive `bip32` addresses following `bip44` paths for [slip-0044](https://github.com/satoshilabs/slips/blob/master/slip-0044.md) registered coin types, using hardened addresses. + +The bulk of this funcionality resides in `anoma-apps/anoma-lib/lib/src/wallet.rs` (https://github.com/heliaxdev/anoma-apps/blob/main/packages/anoma-lib/lib/src/wallet.rs). Creating a new `Wallet` struct with a provided mnemonic generates a seed byte vector and establishes a root extended key. Calling the `derive` method on that `Wallet` providing a derivation path will give us the following struct: + +```rust +pub struct DerivedAccount { + address: String, // p2pkh address + wif: String, // Address in Wallet Import Format (WIF) + private_key: Vec, // Extended Private key + public_key: Vec, // Extended Public key + secret: Vec, // ed25519 secret key + public: Vec, // ed25519 public key +} +``` + +The ed25519 keys can then be used to initialize an account on the ledger to receive an Established Address. + +## Deriving Shielded Addresses + +_TBD_ + +## Resources + +- [BIP32 spec for hierarchical deterministric wallets](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki) +- [BIP39 spec for mnemonic seeds](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki) +- [BIP44 spec for hierarchical deterministic wallets](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki) +- [LedgerHQ - BIP44](https://github.com/LedgerHQ/ledger-live-common/blob/master/docs/derivation.md) +- [SLIP-0044 Registered Coin Types](https://github.com/satoshilabs/slips/blob/master/slip-0044.md) +- [Mnemonic Code Converter](https://iancoleman.io/bip39/) - Useful online utilities to verify derived addresses and keys from specified mnemonic +- [Rust bip32](https://docs.rs/bip32/latest/bip32/) +- [Rust bip0039](https://github.com/koushiro/bip0039) +- [Rust bitcoin](https://github.com/rust-bitcoin/rust-bitcoin) diff --git a/documentation/spec/src/user-interfaces/web-wallet/persistence.md b/documentation/spec/src/user-interfaces/web-wallet/persistence.md new file mode 100644 index 0000000000..eb9c84c903 --- /dev/null +++ b/documentation/spec/src/user-interfaces/web-wallet/persistence.md @@ -0,0 +1,42 @@ +## Persistence of User Wallet + +The state of the user's wallet, consisting of their master seed, along with any accounts derived from that seed, should be stored locally in a safe manner. As this requires the use of `localStorage`, all data should be encrypted. + +Presently, this challenge is being addressed by using the user's password (specified when creating their master seed) to encrypt/decrypt the mnemonic seed, as well as unlocking the state of their wallet. The accounts in the state are being persisted via [redux-persist](https://github.com/rt2zz/redux-persist), with an [ecryption transform](https://github.com/maxdeviant/redux-persist-transform-encrypt) that handles the encrypting and decrypting of all data stored in `localStorage`. + +The mnemonic is stored separately from the accounts data. In `anoma-apps/packages/anoma-lib/lib/types/mnemonic.rs` implementation of `Mnemonic`, we provide the ability to specify a password allowing us to retrieve a storage value of the mnemonic, which is encrypted before saving to `localStorage`. When the wallet is locked, the user must provide a password, which is validated by attempting to decrypt the stored mnemonic. If successful, the password is used to either generate an encrypted Redux persistence layer, or decrypt the existing one, restoring the user's wallet state. + +`redux-persist` gives us the ability to specify which sub-sections of the state should be persisted. Presently, this is only enabled for any derived account data. From the persisted store, we can establish a `persistor`, which can be passed into a `PersistGate` component that will only display its children once the state is retrieved and decrypted from storage. + +If we wanted to export the state of the user's accounts, this would be trivial, and simply a matter of exporting a JSON file containing the `JSON.stringify`ed version of their accounts state. Some work would need to be done in order to restore the data into Redux, however. + +The `localStorage` state is stored in one of three places, depending on your environment: + +- `persist:anoma-wallet` - Production +- `persist:anoma-wallet-dev` - Devnet +- `persist:anoma-wallet-local` - Local ledger + +This allows us to keep our wallet state in sync with multiple ledgers while testing. + +## Restoring the accounts state from file + +The user should have the ability to save the state of their accounts in their wallet to a JSON file. It is relatively trivial to take a snapshot of the accounts state once the user is authenticated. + +Technically, this will likely involve a process by which, following the upload of the file and successful parsing, the existing `persist:anoma-wallet` storage is cleared, and when the store is initialized, we pass the parsed accounts state in to `configureStore` by way of the `preloadedState` parameter. This will only happen once, and on subsequent calls to the `makeStore` function, it should hydrate from the encrypted value in local storage. + +Refer to the following to see how our present `makeStore` Redux store factory functions: + +https://github.com/heliaxdev/anoma-apps/blob/9551d9d0f20b291214357bc7f4a5ddc46bdc8ee0/packages/anoma-wallet/src/store/store.ts#L18-L50 + +This method currently accepts a `secretKey` as required by the `encryptTransform`, and checks the environment variables `REACT_APP_LOCAL` and `NODE_ENV` to determine where the store gets saved in `localStorage`. This is mostly useful for local testing where you may want to switch between connecting to a local ledger or a testnet, and want to keep your local stores in sync with both. + +## Challenges + +As a secret is required to unlock the persisted store, this store must be instantiated dynamically once a password is entered and validated. In the current implementation of the wallet, any routes that will make use of the Redux store are loaded asynchronously. When they are loaded, the store is initialized with the user's password (which is passed in through the Context API in React, separate from the Redux state). + +## Resources + +- [redux-persist](https://github.com/rt2zz/redux-persist) - Redux store persistence +- [redux-persist-transform-encrypt](https://github.com/maxdeviant/redux-persist-transform-encrypt) - Transform to encrypt persisted state +- [Notes on initial data in Redux](https://dev.to/lawrence_eagles/how-to-properly-set-initial-state-in-redux-78m) +- [Notes on clearing persisted Redux state](https://bionicjulia.com/blog/clear-redux-toolkit-state-with-redux-persist-and-typescript) diff --git a/documentation/spec/src/user-interfaces/web-wallet/rpc.md b/documentation/spec/src/user-interfaces/web-wallet/rpc.md new file mode 100644 index 0000000000..b5f2e8b5e3 --- /dev/null +++ b/documentation/spec/src/user-interfaces/web-wallet/rpc.md @@ -0,0 +1,79 @@ +## Using JSON RPC to Communicate with Ledger + +To query values from the ledger, the web-wallet must issue JSON RPC calls to the **Tendermint** `abci_query` endpoint over HTTP, which if running the ledger locally, would look like: + +``` +http://localhost:26657/abci_query/ +``` + +Similarly, when broadcasting transactions, we must communicate with the ledger over websockets to an endpoint such as: + +``` +ws://localhost:26657/websocket/ +``` + +To handle this in the wallet, we can make use of existing functionality from `cosmjs`, namely, the `RpcClient` and `WebsocketClient`. + +### RPC HTTP Client + +Over HTTP, using the `abci_query` endpoint, we can query the ledger by providing a `path` to the storage value we wish to query. Here are some examples: + +- Query balance: `value/#{token_address}/balance/#{owner_address}` +- Query epoch: `epoch` +- Is known address?: `has_key/#{address}/?` + +There are many other types of queries in addition to `abci_query` that can be issued to Tendermint. See [https://docs.tendermint.com/master/rpc/](https://docs.tendermint.com/master/rpc/) for more information. + +### WebSocket Client + +The most interesting type of interaction with the ledger thus far is via WebSockets. The goal of the implementation in `anoma-wallet` is to allow us to provide listeners so that we can update the React app according to activity on the ledger. The core functionality of the implementation on the client is as follows: + +```ts +public async broadcastTx( + hash: string, + tx: Uint8Array, + { onBroadcast, onNext, onError, onComplete }: SubscriptionParams +): Promise { + if (!this._client) { + this.connect(); + } + + try { + const queries = [`tm.event='NewBlock'`, `${TxResponse.Hash}='${hash}'`]; + this.client + ?.execute( + createJsonRpcRequest("broadcast_tx_sync", { tx: toBase64(tx) }) + ) + .then(onBroadcast) + .catch(onError); + + this.client + ?.listen( + createJsonRpcRequest("subscribe", { + query: queries.join(" AND "), + }) + ) + .addListener({ + next: onNext, + error: onError, + complete: onComplete, + }); + + return Promise.resolve(this); + } catch (e) { + return Promise.reject(e); + } +} +``` + +There are a few key things happening here. Once we have constructed a transaction, we receive a transaction `hash` and a `Uint8Array` containing the bytes of the wrapped and signed transaction. We first execute the request to `broadcast_tx_sync`, which can take an `onBroadcast` callback from the client to listen to the initial response from the ledger. We provide the `tx` data in `base64` format as an argument. + +Following that, we subcribe to events on the ledger using a query containing `tm.event='NewBlock' AND applied.hash='transaction_hash_value'`, then then register the following listeners so that we may trigger activity in the front-end app: + +- `onNext` - called when we receive a `NewBlock` event that matches our `hash` +- `onError` - called in the event of an error +- `onComplete` - called when the websocket closes + +The way this library in `anoma-wallet/src/lib/` is implemented, we can also determine when we want to disconnect the WebSocket. For instance, if for some reason we want to issue a series of transactions in succession, we could feasibly leave the connection open, then close after the final transaction is complete. Alternatively, and in most cases, we would simply close the connection when we are finished with a single transaction, which would then trigger the `onComplete` callback. + +See [Transparent Transactions](./transparent-transactions.md) for more information on how the transactions are initially constructed. diff --git a/documentation/spec/src/user-interfaces/web-wallet/stakingAndGovernance.png b/documentation/spec/src/user-interfaces/web-wallet/stakingAndGovernance.png new file mode 100644 index 0000000000..65c990beed Binary files /dev/null and b/documentation/spec/src/user-interfaces/web-wallet/stakingAndGovernance.png differ diff --git a/documentation/spec/src/user-interfaces/web-wallet/transparent-transactions.md b/documentation/spec/src/user-interfaces/web-wallet/transparent-transactions.md new file mode 100644 index 0000000000..6f65c5be63 --- /dev/null +++ b/documentation/spec/src/user-interfaces/web-wallet/transparent-transactions.md @@ -0,0 +1,213 @@ +# Transparent Transactions + +#### Table of Contents + +- [Transfer Transactions](#part-1---token-transfer-transactions) +- [Initialize Account Transactions](#part-2---initialize-account-transaction) +- [Submitting Transactions](#submitting-transparent-transactions) + +## Constructing Transparent Transactions + +The web-wallet will need to support many transactions. As the data that gets submitted to the ledger is most easily constructed from `anoma` types, we perform the assembly of the transaction with in WebAssembly using Rust so that we may natively interact with `anoma`. The role of wasm in this scenario is to provide two pieces of data to the client (which will handle the broadcasting of the transaction), which are: + +1. `hash` - the hash of the transaction +2. `data` - A byte array of the final wrapped and signed transaction + +The following outlines how we can construct these transactions before returning them to the client. + +## Part 1 - Token Transfer Transactions + +There are a few steps involved in creating and signing a transaction: + +1. Create an `anoma::proto::Tx struct` and sign it with a keypair +2. Wrap Tx with a `anoma::types::transaction::WrapperTx` struct which encrypts the transaction +3. Create a new `anoma::proto::Tx` with the new `WrapperTx` as data, and sign it with a keypair (this will be broadcast to the ledger) + +### 1.1 - Creating the `anoma::proto::Tx` struct + +The requirements for creating this struct are as follow: + +- A pre-built wasm in the form of a byte array (this is loaded in the client as a `Uint8Array` type to pass to the wasm) +- A serialized `anoma::types::token::Transfer` object which contains the following: + - `source` - source address derived from keypair + - `target` - target address + - `token` - token address + - `amount` - amount to transfer +- A UTC timestamp. _NOTE_ this is created when calling `proto::Tx::new()`, however, this is incompatible with the wasm in runtime (`time` is undefined). Therefore, we need to get a valid timestamp from `js_sys`: + +```rust +// anoma-lib/src/util.rs + +pub fn get_timestamp() -> DateTimeUtc { + let now = js_sys::Date::new_0(); + + let year = now.get_utc_full_year() as i32; + let month: u32 = now.get_utc_month() + 1; + let day: u32 = now.get_utc_date(); + let hour: u32 = now.get_utc_hours(); + let min: u32 = now.get_utc_minutes(); + let sec: u32 = now.get_utc_seconds(); + + let utc = Utc.ymd(year, month, day).and_hms(hour, min, sec); + DateTimeUtc(utc) +} +``` + +#### Creating the `types::token::Transfer` struct to pass in as data: + +_In wasm:_ + +```rust +// anoma-lib/src/transfer.rs + +let transfer = token::Transfer { + source: source.0, + target: target.0, + token: token.0.clone(), + amount, +}; + +// The data we pass to proto::Tx::new +let data = transfer + .try_to_vec() + .expect("Encoding unsigned transfer shouldn't fail"); +``` + +_In Anoma CLI:_ +https://github.com/anoma/anoma/blob/f6e78278608aaef253617885bb7ef95a50057268/apps/src/lib/client/tx.rs#L406-L411 + + +#### Creating and signing the `proto::Tx` struct + +_In wasm:_ + +```rust +// anoma-lib/src/types/tx.rs + +impl Tx { + pub fn new(tx_code: Vec, data: Vec) -> proto::Tx { + proto::Tx { + code: tx_code, + data: Some(data), + timestamp: utils::get_timestamp(), + } + } +} +``` + +**NOTE** Here we provide a work around to an issue with `proto::Tx::new()` in wasm - instead of calling the method directly on `Tx`, we create a new implementation that returns a `proto::Tx`, with the timestamp being set using `js_sys` in order to make this wasm-compatible. + +_In Anoma CLI:_ +https://github.com/anoma/anoma/blob/f6e78278608aaef253617885bb7ef95a50057268/apps/src/lib/client/tx.rs#L417-L419 + + +### 1.2 - Creating the `anoma::types::transaction::WrapperTx` struct + +The requirements for creating this struct are as follows: + +- A `transaction::Fee` type, which contains: + - `amount` - the Fee amount + - `token` - the address of the token +- `epoch` - The ID of the epoch from query +- `gas_limit` - This contains a `u64` value representing the gas limit +- `tx` - the `proto::Tx` type we created earlier. + +_In wasm:_ + +```rust +// anoma-lib/src/types/wrapper.rs + +transaction::WrapperTx::new( + transaction::Fee { + amount, + token: token.0, + }, + &keypair, + storage::Epoch(u64::from(epoch)), + transaction::GasLimit::from(gas_limit), + tx, +) +``` + +**NOTE** Here we can directly invoke `WrapperTx::new`, so we only need to concern ourselves with convering the JavaScript-provided values into the appropriate types. + +_In Anoma CLI:_ +https://github.com/anoma/anoma/blob/f6e78278608aaef253617885bb7ef95a50057268/apps/src/lib/client/tx.rs#L687-L696 + +#### 1.3 - Create a new `Tx` with `WrapperTx` and sign it + +Here we create a `WrapperTx` type, and with that we create a new `Tx` type (our _wrapped_ `Tx` type) with the `WrapperTx` as the `data`, and empty `vec![]` for `code`, and a new `timestamp`, and then we sign it. + +_In wasm:_ + +```rust +// anoma-lib/src/types/wrapper.rs -> sign() + +(Tx::new( + vec![], + transaction::TxType::Wrapper(wrapper_tx) + .clone() + .try_to_vec().expect("Could not serialize WrapperTx") +)).sign(&keypair) +``` + +We can summarize a high-level overview of the entire process from the `anoma-lib/src/types/transaction.rs` implementation: + +```rust +let source_keypair = Keypair::deserialize(serialized_keypair)?; +let keypair = key::ed25519::Keypair::from_bytes(&source_keypair.to_bytes()) + .expect("Could not create keypair from bytes"); + +let tx = Tx::new( + tx_code, + data, +).sign(&keypair); + +let wrapper_tx = WrapperTx::new( + token, + fee_amount, + &keypair, + epoch, + gas_limit, + tx, +); + +let hash = wrapper_tx.tx_hash.to_string(); +let wrapper_tx = WrapperTx::sign(wrapper_tx, &keypair); +let bytes = wrapper_tx.to_bytes(); + +// Return serialized wrapped & signed transaction as bytes with hash +// in a tuple: +Ok(Transaction { + hash, + bytes, +}) +``` + +_In Anoma CLI:_ +https://github.com/anoma/anoma/blob/f6e78278608aaef253617885bb7ef95a50057268/apps/src/lib/client/tx.rs#L810-L814 + + +## Part 2 - Initialize Account Transaction + +Constructing an Initialize Account transaction follows a similar process to a transfer, however, in addition to providing a `tx_init_account` wasm, we need to provide the `vp_user` wasm as well, as this is required when constructing the transaction: + +```rust +// anoma-lib/src/account.rs + +let vp_code: Vec = vp_code.to_vec(); +let keypair = &Keypair::deserialize(serialized_keypair.clone()) + .expect("Keypair could not be deserialized"); +let public_key = PublicKey::from(keypair.0.public.clone()); + +let data = InitAccount { + public_key, + vp_code: vp_code.clone(), +}; +``` + +Following this, we will pass `data` into to our new transaction as before, along with `tx_code` and required values for `WrapperTx`, returning the final result in a `JsValue` containing the transaction hash and returned byte array. + +## Submitting Transparent Transactions + +See [RPC](./rpc.md) for more information on HTTP and WebSocket RPC interaction with ledger. diff --git a/documentation/spec/src/user-interfaces/web-wallet/user-interfaces.md b/documentation/spec/src/user-interfaces/web-wallet/user-interfaces.md new file mode 100644 index 0000000000..d4047fe34a --- /dev/null +++ b/documentation/spec/src/user-interfaces/web-wallet/user-interfaces.md @@ -0,0 +1,208 @@ +

Namada wallet user interface

+ +- [LockScreen](#lockscreen) + - [LockScreen](#lockscreen-1) +- [AccountOverview](#accountoverview) + - [AccountOverview](#accountoverview-1) + - [AccountOverview/TokenDetails](#accountoverviewtokendetails) + - [AccountOverview/TokenDetails/Receive](#accountoverviewtokendetailsreceive) + - [AccountOverview/TokenDetails/Send](#accountoverviewtokendetailssend) +- [StakingAndGovernance](#stakingandgovernance) + - [StakingAndGovernance](#stakingandgovernance-1) + - [StakingAndGovernance/Staking](#stakingandgovernancestaking) + - [StakingAndGovernance/ValidatorDetails](#stakingandgovernancevalidatordetails) + - [StakingAndGovernance/Proposals](#stakingandgovernanceproposals) + - [StakingAndGovernance/Proposals/AddProposal](#stakingandgovernanceproposalsaddproposal) +- [Settings](#settings) + - [Settings](#settings-1) + - [Settings/WalletSettings](#settingswalletsettings) + - [Settings/Accounts](#settingsaccounts) + - [Settings/Accounts/NewAccount](#settingsaccountsnewaccount) + - [Settings/AccountSettings](#settingsaccountsettings) + +The application is divided to 4 main sections: +* LockScreen +* AccountOverview +* StakingAndGovernance +* Settings + +These are further divided to individual screens or flows (comprising several screens) grouping activities that belong together. For example, under **StakingAndGovernance** we have: + +* **StakingAndGovernance/Staking** - which gives the user the possibility to see all the validators and navigate to a screen where the actual staking is performed. + +Each screen listed below is associated with a high level wireframe design to give a visual presentation of the user interface. Each view is named and being referred with that name through out all communication and in the codebase. + + + +*This screen represents StakingAndGovernance/Staking view* + + +## LockScreen +When the user accesses the wallet for the first time there is a need to create a new account. This screen gives the user to possibility to do so or unlock the wallet by using an existing account. + +### LockScreen +[Wireframe](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6442%3A5801) +User can: +* can to unlock the wallet by entering the master password +* can to start a flow to create a new account + +## AccountOverview +This is the most important part of the application and the part where the user spends the most time. Here the user performs the most common tasks such as creating transactions. Only one account is selected as a time and the selected account is indicated here. + +### AccountOverview +[Wireframe](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6442%3A5492) +User can: +* see the aggregated balance in fiat currency +* can see the currently selected account address +* can navigate to **Settings/Accounts** for changing the account +* can see a listing of all hold tokens and their logos, balances, names + + +### AccountOverview/TokenDetails +[Wireframe](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6442%3A5681) +User can: +* can see the balance of token in native and fiat currency +* can navigate to **AccountOverview/TokenDetails/Receive** for receiving tokens +* can navigate to **AccountOverview/TokenDetails/Send** for sending tokens +* can see a listing of past transaction of the current account and selected token + +### AccountOverview/TokenDetails/Receive +[Wireframe](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6472%3A6476) +User can: +* see QR code of the address +* see address as a string and copy it by clicking button + +### AccountOverview/TokenDetails/Send +[Wireframe 1](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6472%3A9579) +[Wireframe 2](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6472%3A9715) +[Wireframe 3](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6472%3A9797) +User can: +view 1: +* see the balance of the token in current account +* enter details: transfer amount, recipient address, memo +* can select to perform the transaction as shielded + +view 2: +* see a summary of the transaction details +* clear indication whether the transaction is transparent of shielded +* select a gas fee +* see an option in gas fees that is specific for shielded transactions +* see a transaction summary including gas fee + +view 3: +* see a confirmation once the transaction is confirmed +* be abel to navigate to see the new transaction in the block explorer +* be able to navigate back to **AccountOverview/TokenDetails** + + + +## StakingAndGovernance +Aside of **AccountOverview** this is a part that the user is likely visiting quite frequently. All staking and governance related activities are performed here. + +### StakingAndGovernance +[Wireframe](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6496%3A6316) +User can: +* see a dashboard with the most interesting information regarding staking +* see a dashboard with the most interesting information regarding governance +* can navigate to **StakingAndGovernance/Staking** for performing staking actions +* can navigate to **StakingAndGovernance/Proposals** for performing governance actions + +### StakingAndGovernance/Staking +[Wireframe 1](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6496%3A6377) +[Wireframe 2](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6496%3A14001) +[Wireframe 3](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6496%3A14101) +User can: +view 1: +* view a listing of validators +* be able to navigate to aaa for seeing further details about the validator +* select to stake with one of them + +view 2: +* select an amount to stake +* see a summary of the staking transaction + +view 3: +* see a confirmation of a successful staking with the selected validator + +### StakingAndGovernance/ValidatorDetails +[Wireframe](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6496%3A13919) +User can: +* can see all relevant details of the validator + +### StakingAndGovernance/Proposals +[Wireframe](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6496%3A14167) +User can: +* see a listing of all open proposals +* be able to vote for yes, no, no with veto and abstain +* see the current vote share per proposal +* navigate to **StakingAndGovernance/Proposals/AddProposal** for adding a new proposal + +### StakingAndGovernance/Proposals/AddProposal +[Wireframe 1](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6496%3A14286) +[Wireframe 2](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6496%3A14405) +User can: +view 1: +* enter the details (TBD) of the proposal +* see a summary of the proposal +* submit the proposal + +view 2: +* see a confirmation of successfully submitted proposal + +## Settings +This is a part of the application that is visited less often. This is where the user can change settings of select the active account. + +### Settings +[Wireframe](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6496%3A13327) +User can: +* Navigate to **Settings/Accounts** +* Navigate to **Settings/WalletSettings** + +### Settings/WalletSettings +[Wireframe](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6496%3A6235) +User can: +* see and change the fiat currency to display in various locations in the app where amounts are being displayed in fiat currency +* Default fiat currency is USD + +### Settings/Accounts +[Wireframe](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6472%3A9901) +User can: +* select an account by clicking it, when it becomes visibly selected +* can navigate to **Settings/AccountSettings** for changing the settings of certain account +* can navigate to Settings/Accounts/NewAccount/Start for adding a new account to the wallet + + +### Settings/Accounts/NewAccount +[Wireframe 1](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6442%3A5866) +[Wireframe 2](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6442%3A5956) +[Wireframe 3](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6442%3A6015) +[Wireframe 4](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6442%3A6104) +[Wireframe 5](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6442%3A6190) +User can: + +view 1: +* see a welcome screen that explain the flow + +view 2: +* enter an alias to the account +* enter and confirm a password +* select the length of the seed phrase (12 or 24 words) + +view 3: +* see a seed phrase that was generated +* copy the seed phrase to clipboard + +view 4: +* enter a randomly requested word from the set of words. ("please enter word #5") + +view 5: +* see a confirmation that the account was created +* navigate to **AccountOverview** and so that the newly created account becomes the selected account + +### Settings/AccountSettings +[Wireframe](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6472%3A10076) +User can: +* Rename the selected account +* display the seed phrase, user is being prompted for a password +* delete account, user is prompted to input a security text to prevent an accidental deletion +* select the network \ No newline at end of file